diff --git a/scm-ui/ui-components/src/repos/Diff.stories.tsx b/scm-ui/ui-components/src/repos/Diff.stories.tsx index 1f347872ad..2e92c01474 100644 --- a/scm-ui/ui-components/src/repos/Diff.stories.tsx +++ b/scm-ui/ui-components/src/repos/Diff.stories.tsx @@ -29,13 +29,15 @@ import parser from "gitdiff-parser"; import simpleDiff from "../__resources__/Diff.simple"; import hunksDiff from "../__resources__/Diff.hunks"; import binaryDiff from "../__resources__/Diff.binary"; -import { DiffEventContext, File } from "./DiffTypes"; +import {DiffEventContext, File, FileControlFactory} from "./DiffTypes"; import Toast from "../toast/Toast"; import { getPath } from "./diffs"; import DiffButton from "./DiffButton"; import styled from "styled-components"; import { MemoryRouter } from "react-router-dom"; -import {one} from "../__resources__/changesets"; +import {one, two} from "../__resources__/changesets"; +import {Changeset} from "@scm-manager/ui-types"; +import JumpToFileButton from "./JumpToFileButton"; const diffFiles = parser.parse(simpleDiff); @@ -45,16 +47,50 @@ const Container = styled.div` const RoutingDecorator = (story: () => ReactNode) => {story()}; +const fileControlFactory: (changeset: Changeset) => FileControlFactory = (changeset) => (file) => { + const baseUrl = "/repo/hitchhiker/heartOfGold/code/changeset"; + const sourceLink = { + url: `${baseUrl}/${changeset.id}/${file.newPath}/`, + label: "Jump to source" + }; + const targetLink = changeset._embedded?.parents?.length === 1 && { + url: `${baseUrl}/${changeset._embedded.parents[0].id}/${file.oldPath}`, + label: "Jump to target" + }; + + const links = []; + switch (file.type) { + case "add": + links.push(sourceLink); + break; + case "delete": + if (targetLink) { + links.push(targetLink); + } + break; + default: + if (targetLink) { + links.push(sourceLink, targetLink); + } else { + links.push(sourceLink); + } + } + + return links.map(({url, label}) => ); +}; + storiesOf("Diff", module) .addDecorator(RoutingDecorator) .addDecorator(storyFn => {storyFn()}) - .add("Default", () => ) - .add("Side-By-Side", () => ) - .add("Collapsed", () => ) + .add("Default", () => ) + .add("Side-By-Side", () => ) + .add("Collapsed", () => ) .add("File Controls", () => ( ( ( [

Custom File annotation for {file.newPath}

]} /> )) .add("Line Annotation", () => ( { return { N2:

Line Annotation

@@ -94,7 +128,7 @@ storiesOf("Diff", module) return ( <> {changeId && } - + ); }; @@ -102,11 +136,11 @@ storiesOf("Diff", module) }) .add("Hunks", () => { const hunkDiffFiles = parser.parse(hunksDiff); - return ; + return ; }) .add("Binaries", () => { const binaryDiffFiles = parser.parse(binaryDiff); - return ; + return ; }) .add("SyntaxHighlighting", () => { const filesWithLanguage = diffFiles.map((file: File) => { @@ -118,22 +152,20 @@ storiesOf("Diff", module) } return file; }); - return ; + return ; }) .add("CollapsingWithFunction", () => ( - oldPath.endsWith(".java")} /> + oldPath.endsWith(".java")} /> )) .add("Expandable", () => { const filesWithLanguage = diffFiles.map((file: File) => { file._links = { lines: { href: "http://example.com/" } }; return file; }); - return ; + return ; }) .add("WithLinkToFile", () => ( )); diff --git a/scm-ui/ui-components/src/repos/Diff.tsx b/scm-ui/ui-components/src/repos/Diff.tsx index 8978c61d72..627b52027c 100644 --- a/scm-ui/ui-components/src/repos/Diff.tsx +++ b/scm-ui/ui-components/src/repos/Diff.tsx @@ -23,17 +23,15 @@ */ import React from "react"; import DiffFile from "./DiffFile"; -import { DiffObjectProps, File } from "./DiffTypes"; +import {DiffObjectProps, File, FileControlFactory} from "./DiffTypes"; import Notification from "../Notification"; -import { WithTranslation, withTranslation } from "react-i18next"; -import {Changeset} from "@scm-manager/ui-types"; +import {WithTranslation, withTranslation} from "react-i18next"; type Props = WithTranslation & DiffObjectProps & { - diff: File[]; - changeset: Changeset; - baseUrl?: string; - }; + diff: File[]; + fileControlFactory?: FileControlFactory; +}; class Diff extends React.Component { static defaultProps: Partial = { @@ -41,7 +39,7 @@ class Diff extends React.Component { }; render() { - const { diff, t, ...fileProps } = this.props; + const {diff, t, ...fileProps} = this.props; return ( <> {diff.length === 0 ? ( diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index dc5070ddd3..3702cd5817 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -22,34 +22,30 @@ * SOFTWARE. */ import React from "react"; -import { withTranslation, WithTranslation } from "react-i18next"; +import {withTranslation, WithTranslation} from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; // @ts-ignore -import { Decoration, getChangeKey, Hunk } from "react-diff-view"; -import { ButtonGroup } from "../buttons"; +import {Decoration, getChangeKey, Hunk} from "react-diff-view"; +import {ButtonGroup} from "../buttons"; import Tag from "../Tag"; import Icon from "../Icon"; -import { Change, ChangeEvent, DiffObjectProps, File, Hunk as HunkType } from "./DiffTypes"; +import {Change, ChangeEvent, DiffObjectProps, File, Hunk as HunkType} from "./DiffTypes"; import TokenizedDiffView from "./TokenizedDiffView"; import DiffButton from "./DiffButton"; -import { MenuContext } from "@scm-manager/ui-components"; -import DiffExpander, { ExpandableHunk } from "./DiffExpander"; +import {MenuContext} from "@scm-manager/ui-components"; +import DiffExpander, {ExpandableHunk} from "./DiffExpander"; import HunkExpandLink from "./HunkExpandLink"; -import { Modal } from "../modals"; +import {Modal} from "../modals"; import ErrorNotification from "../ErrorNotification"; import HunkExpandDivider from "./HunkExpandDivider"; -import JumpToFileButton from "./JumpToFileButton"; -import {Changeset} from "@scm-manager/ui-types"; const EMPTY_ANNOTATION_FACTORY = {}; type Props = DiffObjectProps & WithTranslation & { - file: File; - changeset: Changeset; - baseUrl?: string; - }; + file: File; +}; type Collapsible = { collapsed?: boolean; @@ -119,7 +115,7 @@ class DiffFile extends React.Component { } defaultCollapse: () => boolean = () => { - const { defaultCollapse, file } = this.props; + const {defaultCollapse, file} = this.props; if (typeof defaultCollapse === "boolean") { return defaultCollapse; } else if (typeof defaultCollapse === "function") { @@ -130,7 +126,7 @@ class DiffFile extends React.Component { }; toggleCollapse = () => { - const { file } = this.state; + const {file} = this.state; if (this.hasContent(file)) { this.setState(state => ({ collapsed: !state.collapsed @@ -161,7 +157,7 @@ class DiffFile extends React.Component { ); @@ -171,19 +167,19 @@ class DiffFile extends React.Component { {" "} ); } } // hunk header must be defined - return ; + return ; }; createHunkFooter = (expandableHunk: ExpandableHunk) => { @@ -194,7 +190,7 @@ class DiffFile extends React.Component { ); @@ -204,19 +200,19 @@ class DiffFile extends React.Component { {" "} ); } } // hunk footer must be defined - return ; + return ; }; createLastHunkFooter = (expandableHunk: ExpandableHunk) => { @@ -226,7 +222,7 @@ class DiffFile extends React.Component { {" "} { ); } // hunk header must be defined - return ; + return ; }; expandHead = (expandableHunk: ExpandableHunk, count: number) => { @@ -259,16 +255,16 @@ class DiffFile extends React.Component { }; diffExpanded = (newFile: File) => { - this.setState({ file: newFile, diffExpander: new DiffExpander(newFile) }); + this.setState({file: newFile, diffExpander: new DiffExpander(newFile)}); }; diffExpansionFailed = (err: any) => { - this.setState({ expansionError: err }); + this.setState({expansionError: err}); }; collectHunkAnnotations = (hunk: HunkType) => { - const { annotationFactory } = this.props; - const { file } = this.state; + const {annotationFactory} = this.props; + const {file} = this.state; if (annotationFactory) { return annotationFactory({ hunk, @@ -280,8 +276,8 @@ class DiffFile extends React.Component { }; handleClickEvent = (change: Change, hunk: HunkType) => { - const { onClick } = this.props; - const { file } = this.state; + const {onClick} = this.props; + const {file} = this.state; const context = { changeId: getChangeKey(change), change, @@ -294,7 +290,7 @@ class DiffFile extends React.Component { }; createGutterEvents = (hunk: HunkType) => { - const { onClick } = this.props; + const {onClick} = this.props; if (onClick) { return { onClick: (event: ChangeEvent) => { @@ -315,7 +311,7 @@ class DiffFile extends React.Component { } else if (i > 0) { items.push( - + ); } @@ -358,7 +354,7 @@ class DiffFile extends React.Component { if (file.oldPath !== file.newPath && (file.type === "copy" || file.type === "rename")) { return ( <> - {file.oldPath} {file.newPath} + {file.oldPath} {file.newPath} ); } else if (file.type === "delete") { @@ -377,7 +373,7 @@ class DiffFile extends React.Component { }; renderChangeTag = (file: File) => { - const { t } = this.props; + const {t} = this.props; if (!file.type) { return; } @@ -389,14 +385,14 @@ class DiffFile extends React.Component { const color = value === "added" ? "success is-outlined" : value === "deleted" ? "danger is-outlined" : "info is-outlined"; - return ; + return ; }; hasContent = (file: File) => file && !file.isBinary && file.hunks && file.hunks.length > 0; render() { - const { fileControlFactory, fileAnnotationFactory, changeset: { id: changesetId, _embedded: { parents } }, baseUrl, t } = this.props; - const { file, collapsed, sideBySide, diffExpander, expansionError } = this.state; + const {fileControlFactory, fileAnnotationFactory, t} = this.props; + const {file, collapsed, sideBySide, diffExpander, expansionError} = this.state; const viewType = sideBySide ? "split" : "unified"; let body = null; @@ -417,37 +413,14 @@ class DiffFile extends React.Component { ); } - const collapseIcon = this.hasContent(file) ? : null; + const collapseIcon = this.hasContent(file) ? : null; const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) : null; - let jumpToSource = null; - let jumpToTarget = null; - if (changesetId && baseUrl) { - const jumpToSourceButton = ; - const jumpToTargetButton = parents?.length === 1 && ; - switch (file.type) { - case "add": - jumpToSource = jumpToSourceButton; - break; - case "delete": - jumpToTarget = jumpToTargetButton; - break; - default: - jumpToSource = jumpToSourceButton; - jumpToTarget = jumpToTargetButton; - } - } const sideBySideToggle = - file.hunks && file.hunks.length > 0 ? ( + file.hunks && file.hunks.length > 0 && ( - {({ setCollapsed }) => ( + {({setCollapsed}) => ( { )} {fileControls} - {jumpToSource} - {jumpToTarget} - - - ) : ( - - - {jumpToSource} - {jumpToTarget} ); @@ -480,8 +444,8 @@ class DiffFile extends React.Component { errorModal = ( this.setState({ expansionError: undefined })} - body={} + closeFunction={() => this.setState({expansionError: undefined})} + body={} active={true} /> ); diff --git a/scm-ui/ui-components/src/repos/LoadingDiff.tsx b/scm-ui/ui-components/src/repos/LoadingDiff.tsx index 5086977ff4..59db938a6a 100644 --- a/scm-ui/ui-components/src/repos/LoadingDiff.tsx +++ b/scm-ui/ui-components/src/repos/LoadingDiff.tsx @@ -22,25 +22,22 @@ * SOFTWARE. */ import React from "react"; -import { apiClient } from "../apiclient"; +import {apiClient} from "../apiclient"; import ErrorNotification from "../ErrorNotification"; // @ts-ignore import parser from "gitdiff-parser"; import Loading from "../Loading"; import Diff from "./Diff"; -import { DiffObjectProps, File } from "./DiffTypes"; -import { NotFoundError } from "../errors"; -import { Notification } from "../index"; -import { withTranslation, WithTranslation } from "react-i18next"; -import {Changeset} from "@scm-manager/ui-types"; +import {DiffObjectProps, File} from "./DiffTypes"; +import {NotFoundError} from "../errors"; +import {Notification} from "../index"; +import {withTranslation, WithTranslation} from "react-i18next"; type Props = WithTranslation & DiffObjectProps & { - url: string; - changeset: Changeset; - baseUrl: string; - }; + url: string; +}; type State = { diff?: File[]; @@ -71,8 +68,8 @@ class LoadingDiff extends React.Component { } fetchDiff = () => { - const { url } = this.props; - this.setState({ loading: true }); + const {url} = this.props; + this.setState({loading: true}); apiClient .get(url) .then(response => { @@ -98,14 +95,14 @@ class LoadingDiff extends React.Component { }; render() { - const { diff, loading, error } = this.state; + const {diff, loading, error} = this.state; if (error) { if (error instanceof NotFoundError) { return {this.props.t("changesets.noChangesets")}; } - return ; + return ; } else if (loading) { - return ; + return ; } else if (!diff) { return null; } else { diff --git a/scm-ui/ui-components/src/repos/changesets/ChangesetDiff.tsx b/scm-ui/ui-components/src/repos/changesets/ChangesetDiff.tsx index ccbbc521ae..8ec86cab41 100644 --- a/scm-ui/ui-components/src/repos/changesets/ChangesetDiff.tsx +++ b/scm-ui/ui-components/src/repos/changesets/ChangesetDiff.tsx @@ -22,15 +22,16 @@ * SOFTWARE. */ import React from "react"; -import { Changeset, Link, Collection } from "@scm-manager/ui-types"; +import {Changeset, Collection, Link} from "@scm-manager/ui-types"; import LoadingDiff from "../LoadingDiff"; import Notification from "../../Notification"; -import { WithTranslation, withTranslation } from "react-i18next"; +import {WithTranslation, withTranslation} from "react-i18next"; +import {FileControlFactory} from "../DiffTypes"; type Props = WithTranslation & { changeset: Changeset; - baseUrl: string; defaultCollapse?: boolean; + fileControlFactory?: FileControlFactory; }; export const isDiffSupported = (changeset: Collection) => { @@ -48,12 +49,12 @@ export const createUrl = (changeset: Collection) => { class ChangesetDiff extends React.Component { render() { - const { changeset, baseUrl, defaultCollapse, t } = this.props; + const {changeset, fileControlFactory, defaultCollapse, t} = this.props; if (!isDiffSupported(changeset)) { return {t("changeset.diffNotSupported")}; } else { const url = createUrl(changeset); - return ; + return ; } } } diff --git a/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx b/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx index 46a30a6fd4..cf8adcebd5 100644 --- a/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx +++ b/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx @@ -35,6 +35,7 @@ import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetTags from "./ChangesetTags"; import ChangesetButtonGroup from "./ChangesetButtonGroup"; import ChangesetDescription from "./ChangesetDescription"; +import {FileControlFactory} from "../DiffTypes"; type Props = WithTranslation & { repository: Repository; diff --git a/scm-ui/ui-components/src/repos/index.ts b/scm-ui/ui-components/src/repos/index.ts index a3551ed6b5..36b26495b8 100644 --- a/scm-ui/ui-components/src/repos/index.ts +++ b/scm-ui/ui-components/src/repos/index.ts @@ -45,11 +45,13 @@ export * from "./changesets"; export { default as Diff } from "./Diff"; export { default as DiffFile } from "./DiffFile"; export { default as DiffButton } from "./DiffButton"; +export { FileControlFactory } from "./DiffTypes"; export { default as LoadingDiff } from "./LoadingDiff"; export { DefaultCollapsed, DefaultCollapsedFunction } from "./defaultCollapsed"; export { default as RepositoryAvatar } from "./RepositoryAvatar"; export { default as RepositoryEntry } from "./RepositoryEntry"; export { default as RepositoryEntryLink } from "./RepositoryEntryLink"; +export { default as JumpToFileButton } from "./JumpToFileButton"; export { File, diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index 078ec13c65..a0f37eefdf 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -224,7 +224,7 @@ }, "diff": { "jumpToSource": "Zur Quelldatei springen", - "jumpToTarget": "Zur Zieldatei springen", + "jumpToTarget": "Zur vorherigen Version der Datei springen", "sideBySide": "Zur zweispaltigen Ansicht wechseln", "combined": "Zur kombinierten Ansicht wechseln", "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden.", diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 121c82e806..f4b9c7c89b 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -231,7 +231,7 @@ "copy": "copied" }, "jumpToSource": "Jump to source file", - "jumpToTarget": "Jump to target file", + "jumpToTarget": "Jump to the previous version of the file", "sideBySide": "Switch to side-by-side view", "combined": "Switch to combined view", "noDiffFound": "No Diff between the selected branches found.", diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx index e807f8d4cf..c5868258c8 100644 --- a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx @@ -43,11 +43,12 @@ import { } from "@scm-manager/ui-components"; import ContributorTable from "./ContributorTable"; import { Link as ReactLink } from "react-router-dom"; +import {FileControlFactory} from "@scm-manager/ui-components"; type Props = WithTranslation & { changeset: Changeset; repository: Repository; - baseUrl: string; + fileControlFactory?: FileControlFactory; }; type State = { @@ -131,6 +132,7 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => { ); } + return ( <> setOpen(!open)}> @@ -158,7 +160,7 @@ class ChangesetDetails extends React.Component { } render() { - const { changeset, repository, baseUrl, t } = this.props; + const { changeset, repository, fileControlFactory, t } = this.props; const { collapsed } = this.state; const description = changesets.parseDescription(changeset.description); @@ -239,7 +241,7 @@ class ChangesetDetails extends React.Component { /> } /> - + ); diff --git a/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx b/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx index 636707c8a6..958221f533 100644 --- a/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx @@ -22,12 +22,12 @@ * SOFTWARE. */ import React from "react"; -import { connect } from "react-redux"; -import { compose } from "redux"; -import { withRouter } from "react-router-dom"; -import { WithTranslation, withTranslation } from "react-i18next"; -import { Changeset, Repository } from "@scm-manager/ui-types"; -import { ErrorPage, Loading } from "@scm-manager/ui-components"; +import {connect} from "react-redux"; +import {compose} from "redux"; +import {withRouter} from "react-router-dom"; +import {WithTranslation, withTranslation} from "react-i18next"; +import {Changeset, Repository} from "@scm-manager/ui-types"; +import {ErrorPage, Loading} from "@scm-manager/ui-components"; import { fetchChangesetIfNeeded, getChangeset, @@ -35,12 +35,13 @@ import { isFetchChangesetPending } from "../modules/changesets"; import ChangesetDetails from "../components/changesets/ChangesetDetails"; +import {FileControlFactory} from "@scm-manager/ui-components"; type Props = WithTranslation & { id: string; changeset: Changeset; repository: Repository; - baseUrl: string; + fileControlFactoryFactory?: (changeset: Changeset) => FileControlFactory; loading: boolean; error: Error; fetchChangesetIfNeeded: (repository: Repository, id: string) => void; @@ -49,27 +50,28 @@ type Props = WithTranslation & { class ChangesetView extends React.Component { componentDidMount() { - const { fetchChangesetIfNeeded, repository, id } = this.props; + const {fetchChangesetIfNeeded, repository, id} = this.props; fetchChangesetIfNeeded(repository, id); } componentDidUpdate(prevProps: Props) { - const { fetchChangesetIfNeeded, repository, id } = this.props; + const {fetchChangesetIfNeeded, repository, id} = this.props; if (prevProps.id !== id) { fetchChangesetIfNeeded(repository, id); } } render() { - const { changeset, loading, error, t, repository, baseUrl } = this.props; + const {changeset, loading, error, t, repository, fileControlFactoryFactory} = this.props; if (error) { - return ; + return ; } - if (!changeset || loading) return ; + if (!changeset || loading) return ; - return ; + return ; } } diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 4a73a19d3a..6235854bf6 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -22,24 +22,24 @@ * SOFTWARE. */ import React from "react"; -import { connect } from "react-redux"; -import { Redirect, Route, Switch, RouteComponentProps } from "react-router-dom"; -import { WithTranslation, withTranslation } from "react-i18next"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; -import { Repository } from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {Redirect, Route, RouteComponentProps, Switch} from "react-router-dom"; +import {WithTranslation, withTranslation} from "react-i18next"; +import {binder, ExtensionPoint} from "@scm-manager/ui-extensions"; +import {Changeset, Repository} from "@scm-manager/ui-types"; import { + CustomQueryFlexWrappedColumns, ErrorPage, Loading, NavLink, Page, - CustomQueryFlexWrappedColumns, PrimaryContentColumn, - SecondaryNavigationColumn, SecondaryNavigation, - SubNavigation, - StateMenuContextProvider + SecondaryNavigationColumn, + StateMenuContextProvider, + SubNavigation } from "@scm-manager/ui-components"; -import { fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending } from "../modules/repos"; +import {fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; import BranchesOverview from "../branches/containers/BranchesOverview"; @@ -49,33 +49,34 @@ import EditRepoNavLink from "../components/EditRepoNavLink"; import BranchRoot from "../branches/containers/BranchRoot"; import PermissionsNavLink from "../components/PermissionsNavLink"; import RepositoryNavLink from "../components/RepositoryNavLink"; -import { getLinks, getRepositoriesLink } from "../../modules/indexResource"; +import {getLinks, getRepositoriesLink} from "../../modules/indexResource"; import CodeOverview from "../codeSection/containers/CodeOverview"; import ChangesetView from "./ChangesetView"; import SourceExtensions from "../sources/containers/SourceExtensions"; +import {FileControlFactory, JumpToFileButton} from "@scm-manager/ui-components"; type Props = RouteComponentProps & WithTranslation & { - namespace: string; - name: string; - repository: Repository; - loading: boolean; - error: Error; - repoLink: string; - indexLinks: object; + namespace: string; + name: string; + repository: Repository; + loading: boolean; + error: Error; + repoLink: string; + indexLinks: object; - // dispatch functions - fetchRepoByName: (link: string, namespace: string, name: string) => void; - }; + // dispatch functions + fetchRepoByName: (link: string, namespace: string, name: string) => void; +}; class RepositoryRoot extends React.Component { componentDidMount() { - const { fetchRepoByName, namespace, name, repoLink } = this.props; + const {fetchRepoByName, namespace, name, repoLink} = this.props; fetchRepoByName(repoLink, namespace, name); } componentDidUpdate(prevProps: Props) { - const { fetchRepoByName, namespace, name, repoLink } = this.props; + const {fetchRepoByName, namespace, name, repoLink} = this.props; if (namespace !== prevProps.namespace || name !== prevProps.name) { fetchRepoByName(repoLink, namespace, name); } @@ -105,7 +106,7 @@ class RepositoryRoot extends React.Component { }; getCodeLinkname = () => { - const { repository } = this.props; + const {repository} = this.props; if (repository?._links?.sources) { return "sources"; } @@ -116,8 +117,8 @@ class RepositoryRoot extends React.Component { }; evaluateDestinationForCodeLink = () => { - const { repository } = this.props; - let url = `${this.matchedUrl()}/code`; + const {repository} = this.props; + const url = `${this.matchedUrl()}/code`; if (repository?._links?.sources) { return `${url}/sources/`; } @@ -125,16 +126,16 @@ class RepositoryRoot extends React.Component { }; render() { - const { loading, error, indexLinks, repository, t } = this.props; + const {loading, error, indexLinks, repository, t} = this.props; if (error) { return ( - + ); } if (!repository || loading) { - return ; + return ; } const url = this.matchedUrl(); @@ -153,66 +154,102 @@ class RepositoryRoot extends React.Component { redirectedUrl = url + "/info"; } + const fileControlFactoryFactory: (changeset: Changeset) => FileControlFactory = (changeset) => (file) => { + const baseUrl = `${url}/code/sources`; + const sourceLink = { + url: `${baseUrl}/${changeset.id}/${file.newPath}/`, + label: t("diff.jumpToSource") + }; + const targetLink = changeset._embedded?.parents?.length === 1 && { + url: `${baseUrl}/${changeset._embedded.parents[0].id}/${file.oldPath}`, + label: t("diff.jumpToTarget") + }; + + const links = []; + switch (file.type) { + case "add": + links.push(sourceLink); + break; + case "delete": + if (targetLink) { + links.push(targetLink); + } + break; + default: + if (targetLink) { + links.push(sourceLink, targetLink); + } else { + links.push(sourceLink); + } + } + + return links.map(({url, label}) => ); + }; + return ( } + afterTitle={} > - + {/* redirect pre 2.0.0-rc2 links */} - - - - - + + + + + - } /> - } /> + }/> + }/> ( - + )} /> } + render={() => } /> } + render={() => } /> } + render={() => } /> } + render={() => } /> } + render={() => } /> } + render={() => } /> - } /> - + }/> + - + { activeOnlyWhenExact={false} title={t("repositoryRoot.menu.sourcesNavLink")} /> - + - - - + + + @@ -259,7 +296,7 @@ class RepositoryRoot extends React.Component { } const mapStateToProps = (state: any, ownProps: Props) => { - const { namespace, name } = ownProps.match.params; + const {namespace, name} = ownProps.match.params; const repository = getRepository(state, namespace, name); const loading = isFetchRepoPending(state, namespace, name); const error = getFetchRepoFailure(state, namespace, name);