From 891e3587b3601d31e4d290e2e77a57e9f3e20ef4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 27 Feb 2019 11:56:50 +0100 Subject: [PATCH] implemented api for diff annotations --- .../packages/ui-components/src/index.js | 13 ++ .../packages/ui-components/src/repos/Diff.js | 19 ++- .../ui-components/src/repos/DiffFile.js | 114 +++++++++++++----- .../ui-components/src/repos/DiffTypes.js | 61 ++++++++++ .../ui-components/src/repos/LoadingDiff.js | 8 +- .../packages/ui-components/src/repos/diffs.js | 18 +++ .../ui-components/src/repos/diffs.test.js | 76 ++++++++++++ .../packages/ui-components/src/repos/index.js | 15 +++ 8 files changed, 280 insertions(+), 44 deletions(-) create mode 100644 scm-ui-components/packages/ui-components/src/repos/DiffTypes.js create mode 100644 scm-ui-components/packages/ui-components/src/repos/diffs.js create mode 100644 scm-ui-components/packages/ui-components/src/repos/diffs.test.js diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index 5b3cdb4c95..7bd844f0a8 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -36,3 +36,16 @@ export * from "./layout"; export * from "./modals"; export * from "./navigation"; export * from "./repos"; + +// not sure if it is required +export type { + File, + FileChangeType, + Hunk, + Change, + BaseContext, + AnnotationFactory, + AnnotationFactoryContext, + DiffEventHandler, + DiffEventContext +} from "./repos"; diff --git a/scm-ui-components/packages/ui-components/src/repos/Diff.js b/scm-ui-components/packages/ui-components/src/repos/Diff.js index 61fd1ad814..369e812344 100644 --- a/scm-ui-components/packages/ui-components/src/repos/Diff.js +++ b/scm-ui-components/packages/ui-components/src/repos/Diff.js @@ -1,32 +1,27 @@ //@flow import React from "react"; import DiffFile from "./DiffFile"; +import type { DiffObjectProps } from "./DiffTypes"; -type Props = { - diff: any, - sideBySide: boolean +type Props = DiffObjectProps & { + diff: any }; class Diff extends React.Component { - static defaultProps = { sideBySide: false }; - renderFile = (file: any, i: number) => { - const { sideBySide } = this.props; - return ; - }; - render() { - const { diff } = this.props; + const { diff, ...fileProps } = this.props; return ( <> - {diff.map(this.renderFile)} + {diff.map((file, index) => ( + + ))} ); } - } export default Diff; diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js index 71aa4e282c..d5b67184c6 100644 --- a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js +++ b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js @@ -1,9 +1,16 @@ //@flow import React from "react"; -import { Hunk, Diff as DiffComponent } from "react-diff-view"; +import { + Hunk, + Diff as DiffComponent, + getChangeKey, + Change, + DiffObjectProps, + File +} from "react-diff-view"; import injectSheets from "react-jss"; import classNames from "classnames"; -import {translate} from "react-i18next"; +import { translate } from "react-i18next"; const styles = { panel: { @@ -21,20 +28,18 @@ const styles = { } }; -type Props = { - file: any, - sideBySide: boolean, +type Props = DiffObjectProps & { + file: File, // context props classes: any, t: string => string -} +}; type State = { collapsed: boolean -} +}; class DiffFile extends React.Component { - constructor(props: Props) { super(props); this.state = { @@ -43,23 +48,77 @@ class DiffFile extends React.Component { } toggleCollapse = () => { - this.setState((state) => ({ - collapsed: ! state.collapsed + this.setState(state => ({ + collapsed: !state.collapsed })); }; - renderHunk = (hunk: any, i: number) => { + createHunkHeader = (hunk: Hunk, i: number) => { const { classes } = this.props; - let header = null; if (i > 0) { - header =
; + return
; } - return ; + return null; + }; + + collectHunkAnnotations = (hunk: Hunk) => { + const { annotationFactory, file } = this.props; + if (annotationFactory) { + return annotationFactory({ + hunk, + file + }); + } + }; + + handleClickEvent = (change: Change, hunk: Hunk) => { + const { file, onClick } = this.props; + const context = { + changeId: getChangeKey(change), + change, + hunk, + file + }; + if (onClick) { + onClick(context); + } + }; + + createCustomEvents = (hunk: Hunk) => { + const { onClick } = this.props; + if (onClick) { + return { + gutter: { + onClick: (change: Change) => { + this.handleClickEvent(change, hunk); + } + } + }; + } + }; + + renderHunk = (hunk: Hunk, i: number) => { + return ( + + ); }; renderFileTitle = (file: any) => { - if (file.oldPath !== file.newPath && (file.type === "copy" || file.type === "rename")) { - return (<>{file.oldPath} {file.newPath}); + if ( + file.oldPath !== file.newPath && + (file.type === "copy" || file.type === "rename") + ) { + return ( + <> + {file.oldPath} {file.newPath} + + ); } else if (file.type === "delete") { return file.oldPath; } @@ -73,11 +132,7 @@ class DiffFile extends React.Component { if (key === value) { value = file.type; } - return ( - - {value} - - ); + return {value}; }; render() { @@ -92,7 +147,7 @@ class DiffFile extends React.Component { body = (
- { file.hunks.map(this.renderHunk) } + {file.hunks.map(this.renderHunk)}
); @@ -100,21 +155,24 @@ class DiffFile extends React.Component { return (
-
+
- {this.renderFileTitle(file)} -
-
- {this.renderChangeTag(file)} + + + {this.renderFileTitle(file)} +
+
{this.renderChangeTag(file)}
{body}
); } - } export default injectSheets(styles)(translate("repos")(DiffFile)); diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffTypes.js b/scm-ui-components/packages/ui-components/src/repos/DiffTypes.js new file mode 100644 index 0000000000..885a8f2e96 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/repos/DiffTypes.js @@ -0,0 +1,61 @@ +// @flow + +// We place the types here and not in @scm-manager/ui-types, +// because they represent not a real scm-manager related type. +// This types represents only the required types for the Diff related components, +// such as every other component does with its Props. + +export type FileChangeType = "add" | "modify" | "delete" | "copy" | "rename"; + +export type File = { + hunks: Hunk[], + newEndingNewLine: boolean, + newMode?: string, + newPath: string, + newRevision?: string, + oldEndingNewLine: boolean, + oldMode?: string, + oldPath: string, + oldRevision?: string, + type: FileChangeType +}; + +export type Hunk = { + changes: Change[], + content: string +}; + +export type Change = { + content: string, + isNormal: boolean, + newLineNumber: number, + oldLineNumber: number, + type: string +}; + +export type BaseContext = { + hunk: Hunk, + file: File +}; + +export type AnnotationFactoryContext = BaseContext; + +// key = change id, value = react component +export type AnnotationFactory = ( + context: AnnotationFactoryContext +) => { + [string]: any +}; + +export type DiffEventContext = BaseContext & { + changeId: string, + change: Change +}; + +export type DiffEventHandler = (context: DiffEventContext) => void; + +export type DiffObjectProps = { + sideBySide: boolean, + onClick?: DiffEventHandler, + annotationFactory?: AnnotationFactory +}; diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js index 5b51f790e4..ce1a074b41 100644 --- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js @@ -6,10 +6,10 @@ import parser from "gitdiff-parser"; import Loading from "../Loading"; import Diff from "./Diff"; +import type {DiffObjectProps} from "./DiffTypes"; -type Props = { - url: string, - sideBySide: boolean +type Props = DiffObjectProps & { + url: string }; type State = { @@ -71,7 +71,7 @@ class LoadingDiff extends React.Component { return null; } else { - return ; + return ; } } diff --git a/scm-ui-components/packages/ui-components/src/repos/diffs.js b/scm-ui-components/packages/ui-components/src/repos/diffs.js new file mode 100644 index 0000000000..19d6a62b0d --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/repos/diffs.js @@ -0,0 +1,18 @@ +// @flow +import type { BaseContext, File, Hunk } from "./DiffTypes"; + +export function getPath(file: File) { + if (file.type === "delete") { + return file.oldPath; + } + return file.newPath; +} + +export function createHunkIdentifier(file: File, hunk: Hunk) { + const path = getPath(file); + return `${file.type}_${path}_${hunk.content}`; +} + +export function createHunkIdentifierFromContext(ctx: BaseContext) { + return createHunkIdentifier(ctx.file, ctx.hunk); +} diff --git a/scm-ui-components/packages/ui-components/src/repos/diffs.test.js b/scm-ui-components/packages/ui-components/src/repos/diffs.test.js new file mode 100644 index 0000000000..4aeaabe11b --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/repos/diffs.test.js @@ -0,0 +1,76 @@ +// @flow +import type { File, FileChangeType, Hunk } from "./DiffTypes"; +import { + getPath, + createHunkIdentifier, + createHunkIdentifierFromContext +} from "./diffs"; + +describe("tests for diff util functions", () => { + const file = ( + type: FileChangeType, + oldPath: string, + newPath: string + ): File => { + return { + hunks: [], + type: type, + oldPath, + newPath, + newEndingNewLine: true, + oldEndingNewLine: true + }; + }; + + const add = (path: string) => { + return file("add", "/dev/null", path); + }; + + const rm = (path: string) => { + return file("delete", path, "/dev/null"); + }; + + const modify = (path: string) => { + return file("modify", path, path); + }; + + const createHunk = (content: string): Hunk => { + return { + content, + changes: [] + }; + }; + + describe("getPath tests", () => { + it("should pick the new path, for type add", () => { + const file = add("/etc/passwd"); + const path = getPath(file); + expect(path).toBe("/etc/passwd"); + }); + + it("should pick the old path, for type delete", () => { + const file = rm("/etc/passwd"); + const path = getPath(file); + expect(path).toBe("/etc/passwd"); + }); + }); + + describe("createHunkIdentifier tests", () => { + it("should create identifier", () => { + const file = modify("/etc/passwd"); + const hunk = createHunk("@@ -1,18 +1,15 @@"); + const identifier = createHunkIdentifier(file, hunk); + expect(identifier).toBe("modify_/etc/passwd_@@ -1,18 +1,15 @@"); + }); + }); + + describe("createHunkIdentifierFromContext tests", () => { + it("should create identifier", () => { + const identifier = createHunkIdentifierFromContext({ + file: rm("/etc/passwd"), + hunk: createHunk("@@ -1,42 +1,39 @@") + }); + expect(identifier).toBe("delete_/etc/passwd_@@ -1,42 +1,39 @@"); + }); + }); +}); diff --git a/scm-ui-components/packages/ui-components/src/repos/index.js b/scm-ui-components/packages/ui-components/src/repos/index.js index 9ebd1e9c55..473bbb3efc 100644 --- a/scm-ui-components/packages/ui-components/src/repos/index.js +++ b/scm-ui-components/packages/ui-components/src/repos/index.js @@ -1,5 +1,20 @@ // @flow +import * as diffs from "./diffs"; +export { diffs }; export * from "./changesets"; + export { default as Diff } from "./Diff"; export { default as LoadingDiff } from "./LoadingDiff"; + +export type { + File, + FileChangeType, + Hunk, + Change, + BaseContext, + AnnotationFactory, + AnnotationFactoryContext, + DiffEventHandler, + DiffEventContext +} from "./DiffTypes";