Introduced dumb BranchSelector; moved fetching of branches to BranchRoot

This commit is contained in:
Philipp Czora
2018-10-16 17:04:28 +02:00
parent 53fe671a27
commit e6fb2a8de7
7 changed files with 275 additions and 125 deletions

View File

@@ -5,7 +5,7 @@ import React from "react";
type Props = { type Props = {
options: string[], options: string[],
optionSelected: string => void, optionSelected: string => void,
preselectedOption: string preselectedOption?: string
}; };
class DropDown extends React.Component<Props> { class DropDown extends React.Component<Props> {
@@ -13,7 +13,10 @@ class DropDown extends React.Component<Props> {
const { options, preselectedOption } = this.props; const { options, preselectedOption } = this.props;
return ( return (
<div className="select"> <div className="select">
<select value={preselectedOption} onChange={this.change}> <select
value={preselectedOption ? preselectedOption : ""}
onChange={this.change}
>
<option key="" /> <option key="" />
{options.map(option => { {options.map(option => {
return ( return (
@@ -27,7 +30,7 @@ class DropDown extends React.Component<Props> {
); );
} }
change = event => { change = (event: Event) => {
this.props.optionSelected(event.target.value); this.props.optionSelected(event.target.value);
}; };
} }

View File

@@ -4,30 +4,31 @@ import type { Branch, Repository } from "@scm-manager/ui-types";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { import {
fetchBranches, fetchBranches,
getBranch,
getBranches, getBranches,
getBranchNames,
getFetchBranchesFailure, getFetchBranchesFailure,
isFetchBranchesPending isFetchBranchesPending
} from "../modules/branches"; } from "../modules/branches";
import DropDown from "../components/DropDown"; import DropDown from "../components/DropDown";
import type { History } from "history"; import type { History } from "history";
import { withRouter } from "react-router-dom";
import { ErrorPage, Loading } from "@scm-manager/ui-components"; import { ErrorPage, Loading } from "@scm-manager/ui-components";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
type Props = { type Props = {
repository: Repository, repository: Repository,
branches: Branch[],
fetchBranches: Repository => void,
history: History,
match: any,
selectedBranch?: Branch,
label: string, //TODO: Should this be here? label: string, //TODO: Should this be here?
loading: boolean, onChange: string => void,
branchSelected: string => void,
error: Error,
children: React.Node, children: React.Node,
selected?: string,
// State props
branches: Branch[],
error: Error,
loading: boolean,
// Dispatch props
fetchBranches: Repository => void,
// Context props
t: string => string t: string => string
}; };
type State = { type State = {
@@ -41,12 +42,10 @@ class BranchChooser extends React.Component<Props, State> {
} }
componentDidMount() { componentDidMount() {
console.log("BC CDM");
this.props.fetchBranches(this.props.repository); this.props.fetchBranches(this.props.repository);
} }
render() { render() {
console.log("Branch chooser render");
const { loading, error, t, repository } = this.props; const { loading, error, t, repository } = this.props;
@@ -88,8 +87,7 @@ class BranchChooser extends React.Component<Props, State> {
} }
renderBranchChooser() { renderBranchChooser() {
const { label, match, branches } = this.props; const { label, branches, selected } = this.props;
const selectedBranchName = match.params.branch;
if (!branches || branches.length === 0) { if (!branches || branches.length === 0) {
return null; return null;
@@ -100,22 +98,45 @@ class BranchChooser extends React.Component<Props, State> {
<label className="label">{label}</label> <label className="label">{label}</label>
<DropDown <DropDown
options={branches.map(b => b.name)} options={branches.map(b => b.name)}
preselectedOption={selectedBranchName} preselectedOption={
optionSelected={this.branchSelected} this.state.selectedBranch
? this.state.selectedBranch.name
: selected
}
optionSelected={(branchName: string) => {
this.branchSelected(branchName, true);
}}
/> />
</div> </div>
); );
} }
branchSelected = (branch: string) => { branchSelected = (branch: string, changed: boolean) => {
for (let b of this.props.branches) { for (let b of this.props.branches) {
if (b.name === branch) { if (b.name === branch) {
this.setState({ selectedBranch: b }); this.updateBranch(branch, b, changed);
this.props.branchSelected(b.name);
break; break;
} }
} }
}; };
updateBranch = (branchName: string, branch: Branch, changed: boolean) => {
this.setState(
prevState => {
if (
!prevState.selectedBranch ||
branchName !== prevState.selectedBranch.name
) {
return { selectedBranch: branch };
}
},
() => {
if (changed) {
this.props.onChange(branch.name);
}
}
);
};
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
@@ -127,26 +148,19 @@ const mapDispatchToProps = dispatch => {
}; };
const mapStateToProps = (state: any, ownProps: Props) => { const mapStateToProps = (state: any, ownProps: Props) => {
const { repository, match } = ownProps; const { repository } = ownProps;
const loading = isFetchBranchesPending(state, repository); const loading = isFetchBranchesPending(state, repository);
const error = getFetchBranchesFailure(state, repository); const error = getFetchBranchesFailure(state, repository);
const selectedBranch = getBranch(
state,
repository,
decodeURIComponent(match.params.branch)
);
const branches = getBranches(state, repository); const branches = getBranches(state, repository);
return { return {
// loading, loading,
selectedBranch, error,
// error,
branches branches
}; };
}; };
export default withRouter( export default connect(
connect( mapStateToProps,
mapStateToProps, mapDispatchToProps
mapDispatchToProps )(translate("repos")(BranchChooser));
)(translate("repos")(BranchChooser))
);

View File

@@ -2,82 +2,145 @@
import React from "react"; import React from "react";
import type { Repository, Branch } from "@scm-manager/ui-types"; import type { Repository, Branch } from "@scm-manager/ui-types";
import BranchChooser from "./BranchChooser"; import { Route, Switch, withRouter } from "react-router-dom";
import { Route, withRouter } from "react-router-dom";
import Changesets from "./Changesets"; import Changesets from "./Changesets";
import BranchSelector from "./BranchSelector";
import { connect } from "react-redux";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import {
fetchBranches,
getBranches,
getFetchBranchesFailure,
isFetchBranchesPending
} from "../modules/branches";
import { compose } from "redux";
type Props = { type Props = {
repository: Repository, repository: Repository,
baseUrl: string,
// State props
branches: Branch[],
loading: boolean,
// Dispatch props
fetchBranches: Repository => void,
// Context props
history: History, history: History,
match: any match: any
}; };
class BranchRoot extends React.Component<Props> { type State = {
selectedBranch?: Branch
};
class BranchRoot extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.fetchBranches(this.props.repository);
}
componentDidUpdate(prevProps: Props) {
const { branches, match, loading } = this.props;
console.log("BR did update");
const branchName = decodeURIComponent(match.params.branch);
if (branches) {
if (
(!loading && prevProps.loading) ||
match.url !== prevProps.match.url
) {
this.setState({
selectedBranch: branches.find(b => b.name === branchName)
});
}
}
}
stripEndingSlash = (url: string) => { stripEndingSlash = (url: string) => {
if (url.endsWith("/")) { if (url.endsWith("/")) {
return url.substring(0, url.length - 2); return url.substring(0, url.length - 1);
} }
return url; return url;
}; };
matchedUrl = () => { matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url); return this.stripEndingSlash(this.props.baseUrl);
}; };
branchSelected = (branchName: string) => { branchSelected = (branch: Branch) => {
const url = this.matchedUrl(); const url = this.matchedUrl();
if (branchName === "") { this.props.history.push(
this.props.history.push(`${url}/changesets/`); `${url}/${encodeURIComponent(branch.name)}/changesets/`
} else { );
this.props.history.push(
`${url}/${encodeURIComponent(branchName)}/changesets/`
);
}
}; };
render() { render() {
const { repository } = this.props; const { repository, match, branches, loading } = this.props;
const url = this.matchedUrl(); const url = this.stripEndingSlash(match.url);
if (!repository) {
if (loading) {
return <Loading />;
}
if (!repository || !branches) {
return null; return null;
} }
return ( return (
<BranchChooser <>
repository={this.props.repository} <BranchSelector
label={"Branches"} branches={branches}
branchSelected={this.branchSelected} selected={(b: Branch) => {
> this.branchSelected(b);
<RouteDelegate repository={this.props.repository} url={url} /> }}
</BranchChooser> />
<Route
path={`${url}/changesets/:page?`}
component={() => (
<Changesets
repository={repository}
branch={this.state.selectedBranch}
/>
)}
/>
</>
); );
} }
} }
type RDProps = {
repository: Repository, const mapDispatchToProps = dispatch => {
branch: Branch, return {
url: string fetchBranches: (repo: Repository) => {
dispatch(fetchBranches(repo));
}
};
}; };
class RouteDelegate extends React.Component<RDProps> { const mapStateToProps = (state: any, ownProps: Props) => {
shouldComponentUpdate(nextProps: RDProps, nextState: any) { const { repository } = ownProps;
return ( const loading = isFetchBranchesPending(state, repository);
nextProps.repository !== this.props.repository || const error = getFetchBranchesFailure(state, repository);
nextProps.branch !== this.props.branch ||
nextProps.url !== this.props.url
);
}
render() { const branches = getBranches(state, repository);
const { url, repository, branch } = this.props; return {
return ( loading,
<Route error,
exact branches
path={`${url}/:branch/changesets/:page?`} };
component={() => <Changesets repository={repository} branch={branch} />} };
/>
);
}
}
export default withRouter(BranchRoot); export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withRouter
)(BranchRoot);

View File

@@ -0,0 +1,46 @@
// @flow
import React from "react";
import type { Branch } from "@scm-manager/ui-types";
import DropDown from "../components/DropDown";
type Props = {
branches: Branch[], // TODO: Use generics?
selected?: Branch => void
};
type State = { selectedBranch?: Branch };
class BranchSelector extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
render() {
const { branches } = this.props;
if (branches) {
return (
<>
<DropDown
options={branches.map(b => b.name)}
optionSelected={this.branchSelected}
preselectedOption={
this.state.selectedBranch ? this.state.selectedBranch.name : ""
}
/>
</>
);
}
}
branchSelected = (branchName: string) => {
const { branches, selected } = this.props;
const branch = branches.find(b => b.name === branchName);
if (branch) {
selected(branch);
this.setState({ selectedBranch: branch });
}
};
}
export default BranchSelector;

View File

@@ -46,9 +46,31 @@ class Changesets extends React.Component<Props, State> {
match match
} = this.props; } = this.props;
console.log("branch");
console.log(branch);
const { page } = match.params; const { page } = match.params;
if (!branch) {
return;
}
if (!page) {
fetchChangesetsByBranch(repository, branch);
} else {
fetchChangesetsByBranchAndPage(repository, branch, page);
}
}
componentDidUpdate(prevProps: Props) {
const {
match,
repository,
branch,
fetchChangesetsByBranch,
fetchChangesetsByBranchAndPage
} = this.props;
const { page } = match.params;
if (branch === prevProps.branch) {
return;
}
if (!page) { if (!page) {
fetchChangesetsByBranch(repository, branch); fetchChangesetsByBranch(repository, branch);
} else { } else {
@@ -57,11 +79,7 @@ class Changesets extends React.Component<Props, State> {
} }
render() { render() {
const { repository, branch, changesets, loading, error, t } = this.props; const { changesets, loading, error, t } = this.props;
if (!repository || !branch) {
return null;
}
if (error) { if (error) {
return ( return (

View File

@@ -8,7 +8,7 @@ import {
isFetchRepoPending isFetchRepoPending
} from "../modules/repos"; } from "../modules/repos";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Route } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import type { Repository } from "@scm-manager/ui-types"; import type { Repository } from "@scm-manager/ui-types";
import { import {
ErrorPage, ErrorPage,
@@ -98,36 +98,32 @@ class RepositoryRoot extends React.Component<Props> {
} }
const url = this.matchedUrl(); const url = this.matchedUrl();
// TODO: Changesets need to be adjusted (i.e. sub-routes need to be handled in sub-components) // todo: default branch
return ( return (
<Page title={repository.namespace + "/" + repository.name}> <Page title={repository.namespace + "/" + repository.name}>
<div className="columns"> <div className="columns">
<div className="column is-three-quarters"> <div className="column is-three-quarters">
<Route <Switch>
path={url} <Route
exact path={url}
component={() => <RepositoryDetails repository={repository} />} exact
/> component={() => <RepositoryDetails repository={repository} />}
<Route />
path={`${url}/edit`} <Route
component={() => <Edit repository={repository} />} path={`${url}/edit`}
/> component={() => <Edit repository={repository} />}
{/*<Route*/} />
{/*path={`${url}/changesets/:page?`}*/}
{/*component={() => (*/} <Route
{/*<BranchChooser*/} path={`${url}/branches/:branch`}
{/*repository={repository}*/} render={() => (
{/*label={"Branches"}*/} <BranchRoot
{/*branchSelected={this.branchSelected}*/} repository={repository}
{/*>*/} baseUrl={`${url}/branches`}
{/*<Changesets repository={repository} />*/} />
{/*</BranchChooser>*/} )}
{/*)}*/} />
{/*/>*/} </Switch>
<Route
path={`${url}/branches`}
render={() => <BranchRoot repository={repository} />}
/>
</div> </div>
<div className="column"> <div className="column">
<Navigation> <Navigation>

View File

@@ -1,11 +1,21 @@
// @flow // @flow
import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; import {
import {apiClient} from "@scm-manager/ui-components"; FAILURE_SUFFIX,
import {isPending} from "../../modules/pending"; PENDING_SUFFIX,
import {getFailure} from "../../modules/failure"; SUCCESS_SUFFIX
import {combineReducers} from "redux"; } from "../../modules/types";
import type {Action, Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; import { apiClient } from "@scm-manager/ui-components";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
import { combineReducers } from "redux";
import type {
Action,
Branch,
Changeset,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;