From f68ff78db9303dced7ce5b80158a2d02d456bbc9 Mon Sep 17 00:00:00 2001 From: ole1986 Date: Fri, 6 Mar 2020 08:24:08 +0100 Subject: [PATCH 01/12] Initial material-ui change --- .../components/LogView/LogEntryList/index.tsx | 109 +++++++++++++++--- package-lock.json | 73 +++++++++--- package.json | 2 + 3 files changed, 148 insertions(+), 36 deletions(-) diff --git a/browser/src/components/LogView/LogEntryList/index.tsx b/browser/src/components/LogView/LogEntryList/index.tsx index e2cbdb45..9be9f398 100644 --- a/browser/src/components/LogView/LogEntryList/index.tsx +++ b/browser/src/components/LogView/LogEntryList/index.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { LogEntry, Ref } from '../../../definitions'; -import LogEntryView from '../LogEntry'; + +import Chip from '@material-ui/core/Chip'; +import TableCell from '@material-ui/core/TableCell'; +import { AutoSizer, Column, Table } from 'react-virtualized'; interface ResultProps { logEntries: LogEntry[]; @@ -9,22 +12,90 @@ interface ResultProps { onRefAction(logEntry: LogEntry, ref: Ref, name: string): void; } -export default class LogEntryList extends React.Component { - public ref: HTMLDivElement; - public render() { - if (!Array.isArray(this.props.logEntries)) { - return null; - } - - const results = this.props.logEntries.map(entry => ( - - )); - return
(this.ref = ref)}>{results}
; - } +interface LogEntriesState { + headerHeight: number; + rowHeight: number; } + +export default class LogEntryVirtualizedTable extends React.Component { + private columns: any; + + constructor(props?: ResultProps, context?: any) { + super(props, context); + + this.state = { headerHeight: 48, rowHeight: 48 }; + this.columns = [ + { + width: 200, + label: 'Subject', + dataKey: 'subject', + }, + { + width: 200, + label: 'Created', + dataKey: 'date', + } + ]; + } + + headerRenderer = ({ label, columnIndex }) => { + return ( + + {label} + + ); + }; + + cellRenderer = ({ cellData, columnIndex }) => { + return ( + + + {cellData} + + ); + }; + + render() { + return ( + + {({ height, width }) => ( + + {this.columns.map(({ dataKey, ...other }, index) => { + return ( + + this.headerRenderer({ + ...headerProps, + columnIndex: index, + }) + } + cellRenderer={this.cellRenderer} + dataKey={dataKey} + {...other} + /> + ); + })} +
+ )} +
+ ); + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d12a8469..517b310d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35213,8 +35213,7 @@ "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", - "dev": true + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/qs": { "version": "6.9.6", @@ -35244,7 +35243,6 @@ "version": "16.9.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.23.tgz", "integrity": "sha512-SsGVT4E7L2wLN3tPYLiF20hmZTPGuzaayVunfgXzUn1x4uHVsKH6QDJQ/TdpHqwsTLd4CwrmQ2vOgxN7gE24gw==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" @@ -35331,6 +35329,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz", + "integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==", + "requires": { + "@types/react": "*" + } + }, "@types/redux-actions": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@types/redux-actions/-/redux-actions-2.6.1.tgz", @@ -37654,7 +37660,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -38610,6 +38615,11 @@ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, + "clsx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", + "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -39173,8 +39183,7 @@ "core-js": { "version": "2.6.10", "resolved": "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz", - "integrity": "sha1-iluDkfjMcBPacDQRzltYVwYwDX8=", - "dev": true + "integrity": "sha1-iluDkfjMcBPacDQRzltYVwYwDX8=" }, "core-util-is": { "version": "1.0.2", @@ -39386,6 +39395,30 @@ "fastparse": "^1.1.2" } }, + "css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "requires": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, "css-what": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", @@ -39559,8 +39592,7 @@ "csstype": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", - "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==", - "dev": true + "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" }, "cyclist": { "version": "1.0.1", @@ -45868,6 +45900,18 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "realpath-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", + "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -51217,7 +51261,6 @@ "version": "15.7.2", "resolved": "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz", "integrity": "sha1-UsQedbjIfnK52TYOAga5ncv/psU=", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -51877,14 +51920,12 @@ "react-is": { "version": "16.11.0", "resolved": "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz", - "integrity": "sha1-uF3+zUitHORp/1WKiCyo6DE5KPo=", - "dev": true + "integrity": "sha1-uF3+zUitHORp/1WKiCyo6DE5KPo=" }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-modal": { "version": "3.14.3", @@ -52342,8 +52383,7 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=", - "dev": true + "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" }, "regenerator-transform": { "version": "0.10.1", @@ -54086,8 +54126,7 @@ "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "tmp": { "version": "0.1.0", diff --git a/package.json b/package.json index 98b2e840..6e56c4d5 100644 --- a/package.json +++ b/package.json @@ -504,6 +504,8 @@ "webpack-cli": "^3.3.11" }, "dependencies": { + "@material-ui/core": "latest", + "react-virtualized": "latest", "event-stream": "4.0.1", "fs-extra": "^8.1.0", "gravatar": "^1.8.1", From f998efbc54a40228ad5a56ad27ef1a13e092589c Mon Sep 17 00:00:00 2001 From: ole1986 Date: Fri, 22 May 2020 21:16:55 +0200 Subject: [PATCH 02/12] Very first impl of react-virtualized with InfiniteLoader --- .vscode/settings.json | 2 - browser/package.json | 5 +- browser/src/actions/results.ts | 34 +- .../LogView/Commit/Author/index.tsx | 10 +- .../src/components/LogView/LogEntry/index.tsx | 30 +- .../components/LogView/LogEntryList/index.tsx | 148 ++++---- .../src/components/LogView/LogView/index.tsx | 38 +- browser/src/containers/App/index.tsx | 18 +- browser/src/index.tsx | 2 +- browser/src/main.css | 1 - browser/src/reducers/logEntries.ts | 13 +- package-lock.json | 359 +++++++++++------- package.json | 3 +- src/adapter/repository/git.ts | 17 +- src/adapter/repository/gitArgsService.ts | 11 +- src/server/apiController.ts | 19 +- src/types.ts | 4 +- webpack.config.js | 3 +- 18 files changed, 390 insertions(+), 327 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2699852f..c6024a42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,6 @@ "node_modules": true, ".vscode-test": true, "coverage": true, - "out": true, - "dist": true }, "search.exclude": { "out": true, diff --git a/browser/package.json b/browser/package.json index 8ad22d09..988c51c0 100644 --- a/browser/package.json +++ b/browser/package.json @@ -5,5 +5,8 @@ "tests": "npm test", "build": "cd .. && npx webpack -p --progress --colors", "watch": "cd .. && npx webpack --progress --colors --watch" + }, + "devDependencies": { + "@types/react-virtualized": "^9.21.10" } -} \ No newline at end of file +} diff --git a/browser/src/actions/results.ts b/browser/src/actions/results.ts index 04fc4eec..69849527 100644 --- a/browser/src/actions/results.ts +++ b/browser/src/actions/results.ts @@ -112,20 +112,7 @@ export namespace ResultActions { } }; }; - export const getNextCommits = () => { - return (dispatch: Dispatch, getState: () => RootState) => { - const state = getState(); - const pageIndex = state.logEntries.pageIndex + 1; - return fetchCommits(dispatch, state, pageIndex, undefined); - }; - }; - export const getPreviousCommits = () => { - return (dispatch: Dispatch, getState: () => RootState) => { - const state = getState(); - const pageIndex = state.logEntries.pageIndex - 1; - return fetchCommits(dispatch, state, pageIndex, undefined); - }; - }; + export const search = (searchText: string) => { return (dispatch: Dispatch, getState: () => RootState) => { dispatch(updateSettings({ searchText })); @@ -164,12 +151,12 @@ export namespace ResultActions { }; }; - export function getCommits() { + export const getCommits = (startIndex: number, stopIndex: number) => { return (dispatch: Dispatch, getState: () => RootState) => { const state = getState(); - fetchCommits(dispatch, state); + return fetchCommits(dispatch, state, startIndex, stopIndex); }; - } + }; export const getBranches = () => { return (dispatch: Dispatch, getState: () => RootState) => { const state = getState(); @@ -183,12 +170,17 @@ export namespace ResultActions { }; }; } -function fetchCommits(dispatch: Dispatch, store: RootState, pageIndex?: number, pageSize?: number) { +function fetchCommits( + dispatch: Dispatch, + store: RootState, + startIndex?: number, + stopIndex?: number, +): Promise { dispatch(notifyIsLoading()); - post('getLogEntries', { + return post('getLogEntries', { ...store.settings, - pageIndex, - pageSize, + startIndex, + stopIndex, }).then(x => { dispatch(addResults(x)); }); diff --git a/browser/src/components/LogView/Commit/Author/index.tsx b/browser/src/components/LogView/Commit/Author/index.tsx index b447e728..b1d27761 100644 --- a/browser/src/components/LogView/Commit/Author/index.tsx +++ b/browser/src/components/LogView/Commit/Author/index.tsx @@ -29,15 +29,19 @@ export function Author(props: AuthorProps) { - - {props.result.name} + + {props.result?.name} - on {formatDateTime(props.locale, props.result.date)} + on {formatDateTime(props.locale, props.result?.date)} ); } function formatDateTime(locale: string, date?: Date) { + if (date === undefined) { + return ''; + } + if (date && typeof date.toLocaleDateString !== 'function') { return ''; } diff --git a/browser/src/components/LogView/LogEntry/index.tsx b/browser/src/components/LogView/LogEntry/index.tsx index d20afa59..51521da3 100644 --- a/browser/src/components/LogView/LogEntry/index.tsx +++ b/browser/src/components/LogView/LogEntry/index.tsx @@ -13,6 +13,7 @@ import { GoGitCommit, GoClippy, GoPlus, GoFileSymlinkFile, GoFileSymlinkDirector type ResultListProps = { logEntry: LogEntry; + style: any; selected?: LogEntry; isLoadingCommit?: string; onViewCommit(entry: LogEntry): void; @@ -26,7 +27,8 @@ class LogEntryView extends React.Component { } private isLoading() { - return this.props.isLoadingCommit === this.props.logEntry.hash.full; + if (this.props.logEntry === undefined) return true; + return this.props.isLoadingCommit === this.props.logEntry!.hash!.full; } private showLoading() { @@ -54,7 +56,13 @@ class LogEntryView extends React.Component { ); } + private getHash(full = false) { + if (this.props.logEntry === undefined) return ''; + return full ? this.props.logEntry.hash.full : this.props.logEntry.hash.short; + } + private renderRemoteRefs() { + if (this.props.logEntry === undefined) return ; return this.props.logEntry.refs .filter(ref => ref.type === RefType.RemoteHead) .map(ref => ( @@ -67,6 +75,7 @@ class LogEntryView extends React.Component { } private renderHeadRef() { + if (this.props.logEntry === undefined) return ; return this.props.logEntry.refs .filter(ref => ref.type === RefType.Head) .map(ref => ( @@ -80,6 +89,7 @@ class LogEntryView extends React.Component { } private renderTagRef() { + if (this.props.logEntry === undefined) return ; return this.props.logEntry.refs .filter(ref => ref.type === RefType.Tag) .map(ref => ( @@ -114,7 +124,11 @@ class LogEntryView extends React.Component { event.stopPropagation(); } return ( -
this.props.onViewCommit(this.props.logEntry)}> +
this.props.onViewCommit(this.props.logEntry)} + >
@@ -124,12 +138,12 @@ class LogEntryView extends React.Component {
this.props.onViewCommit(this.props.logEntry)}>
- + - {this.props.logEntry.hash.short}  + {this.getHash()}  @@ -208,12 +222,12 @@ class LogEntryView extends React.Component {
-
- {gitmojify(this.props.logEntry.subject)} +
+ {gitmojify(this.props.logEntry?.subject)} {this.isLoading() ? this.showLoading() : ''}
- - + +
diff --git a/browser/src/components/LogView/LogEntryList/index.tsx b/browser/src/components/LogView/LogEntryList/index.tsx index 9be9f398..0dfc0d11 100644 --- a/browser/src/components/LogView/LogEntryList/index.tsx +++ b/browser/src/components/LogView/LogEntryList/index.tsx @@ -1,101 +1,97 @@ import * as React from 'react'; -import { LogEntry, Ref } from '../../../definitions'; -import Chip from '@material-ui/core/Chip'; -import TableCell from '@material-ui/core/TableCell'; -import { AutoSizer, Column, Table } from 'react-virtualized'; +import { List, InfiniteLoader, AutoSizer } from 'react-virtualized'; +import { RootState, LogEntriesState } from '../../../reducers'; +import { connect } from 'react-redux'; +import { ResultActions } from '../../../actions/results'; + +import LogEntryView from '../LogEntry'; +import { LogEntry, Ref } from '../../../../../src/types'; interface ResultProps { - logEntries: LogEntry[]; + logEntries?: LogEntriesState; + getCommits?(startIndex: number, stopIndex: number): Promise; onViewCommit(entry: LogEntry): void; onAction(entry: LogEntry, name: string): void; onRefAction(logEntry: LogEntry, ref: Ref, name: string): void; } -interface LogEntriesState { - headerHeight: number; - rowHeight: number; -} - -export default class LogEntryVirtualizedTable extends React.Component { - private columns: any; +interface LogEntryTableState {} +class LogEntryVirtualizedTable extends React.Component { constructor(props?: ResultProps, context?: any) { super(props, context); - - this.state = { headerHeight: 48, rowHeight: 48 }; - this.columns = [ - { - width: 200, - label: 'Subject', - dataKey: 'subject', - }, - { - width: 200, - label: 'Created', - dataKey: 'date', - } - ]; + this.state = {}; } - headerRenderer = ({ label, columnIndex }) => { - return ( - - {label} - - ); + isRowLoaded = ({ index }) => { + return this.props.logEntries.items[index] !== undefined; }; - cellRenderer = ({ cellData, columnIndex }) => { + loadMoreRows = ({ startIndex, stopIndex }) => { + return this.props.getCommits(startIndex, stopIndex); + }; + + rowRenderer = ({ + key, // Unique key within array of rows + index, // Index of row within collection + isScrolling, // The List is currently being scrolled + isVisible, // This row is visible within the List (eg it is not an overscanned row) + style, // Style object to be applied to row (to position it) + }) => { + const item = this.props.logEntries.items[index]; + return ( - - - {cellData} - + ); }; render() { return ( - - {({ height, width }) => ( - - {this.columns.map(({ dataKey, ...other }, index) => { - return ( - - this.headerRenderer({ - ...headerProps, - columnIndex: index, - }) - } - cellRenderer={this.cellRenderer} - dataKey={dataKey} - {...other} - /> - ); - })} -
+ + {({ onRowsRendered, registerChild }) => ( + + {({ height, width }) => ( + + )} + )} -
+ ); } -} \ No newline at end of file +} + +function mapStateToProps(state: RootState) { + return { + logEntries: state.logEntries, + }; +} + +function mapDispatchToProps(dispatch) { + return { + getCommits: (startIndex: number, stopIndex: number) => + dispatch(ResultActions.getCommits(startIndex, stopIndex)), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(LogEntryVirtualizedTable); diff --git a/browser/src/components/LogView/LogView/index.tsx b/browser/src/components/LogView/LogView/index.tsx index e42ebd48..cd903652 100644 --- a/browser/src/components/LogView/LogView/index.tsx +++ b/browser/src/components/LogView/LogView/index.tsx @@ -9,7 +9,7 @@ import Dialog, { DialogType } from '../../Dialog'; import { IConfiguration } from 'src/reducers/vscode'; type LogViewProps = { - logEntries: LogEntriesState; + logEntries: LogEntries; configuration: IConfiguration; commitsRendered: typeof ResultActions.commitsRendered; onViewCommit: typeof ResultActions.selectCommit; @@ -19,37 +19,18 @@ type LogViewProps = { getNextCommits: typeof ResultActions.getNextCommits; }; -interface LogViewState {} +interface LogViewState { } class LogView extends React.Component { - private ref: React.RefObject; private dialog: Dialog; constructor(props?: LogViewProps, context?: any) { super(props, context); - // this.state = { height: '', width: '', itemHeight: 0 }; - this.ref = React.createRef(); } - public componentDidUpdate() { - const el = this.ref.current.ref; - + public componentWillUpdate(prevProp: LogViewProps) { if (this.props.logEntries.selected) { return; } - - if ( - el.hasChildNodes() && - this.props.logEntries && - !this.props.logEntries.isLoading && - !this.props.logEntries.isLoadingCommit && - Array.isArray(this.props.logEntries.items) && - this.props.logEntries.items.length > 0 - ) { - // use the total height to be more accurate in positioning the dots from BranchGraph - const totalHeight = el.offsetHeight; - const logEntryHeight = totalHeight / this.props.logEntries.items.length; - this.props.commitsRendered(logEntryHeight); - } } public render() { @@ -57,12 +38,10 @@ class LogView extends React.Component {
+ /> (this.dialog = r)} onOk={this.onDialogOk.bind(this)} />
); @@ -132,8 +111,7 @@ class LogView extends React.Component { case 'reset_soft': this.dialog.showConfirm( `Soft reset to ${entry.hash.short}?`, - `

${entry.subject}
${ - entry.author.name + `

${entry.subject}
${entry.author.name } on ${entry.author.date.toISOString()}

All affected files will be merged and kept in local workspace`, DialogType.Info, { entry, name }, @@ -142,8 +120,7 @@ class LogView extends React.Component { case 'reset_hard': this.dialog.showConfirm( `Hard reset commit to ${entry.hash.short}?`, - `

${entry.subject}
${ - entry.author.name + `

${entry.subject}
${entry.author.name } on ${entry.author.date.toISOString()}

This is IRREVERSIBLE TO YOUR CURRENT WORKING SET. UNCOMMITTED LOCAL FILES WILL BE REMOVED
`, DialogType.Warning, { entry, name }, @@ -183,7 +160,6 @@ function mapStateToProps(state: RootState, wrapper: { logEntries: LogEntriesStat function mapDispatchToProps(dispatch) { return { - commitsRendered: (height: number) => dispatch(ResultActions.commitsRendered(height)), onViewCommit: (hash: string) => dispatch(ResultActions.selectCommit(hash)), actionCommit: (logEntry: LogEntry, name: string, value = '') => dispatch(ResultActions.actionCommit(logEntry, name, value)), diff --git a/browser/src/containers/App/index.tsx b/browser/src/containers/App/index.tsx index d377965e..4a3002ba 100644 --- a/browser/src/containers/App/index.tsx +++ b/browser/src/containers/App/index.tsx @@ -9,19 +9,15 @@ import LogView from '../../components/LogView/LogView'; import { ISettings } from '../../definitions'; import { LogEntriesState, RootState } from '../../reducers'; import { IConfiguration } from '../../reducers/vscode'; -import Footer from '../../components/Footer'; type AppProps = { configuration: IConfiguration; settings: ISettings; logEntries: LogEntriesState; - getCommits: typeof ResultActions.getCommits; - getPreviousCommits: typeof ResultActions.getPreviousCommits; - getNextCommits: typeof ResultActions.getNextCommits; search: typeof ResultActions.search; } & typeof ResultActions; -interface AppState {} +interface AppState { } class App extends React.Component { private splitPane; @@ -65,9 +61,6 @@ class App extends React.Component { public render() { const { children } = this.props; - const canGoForward = - this.props.logEntries.count === -1 || - (this.props.logEntries.pageIndex + 1) * this.props.configuration.pageSize < this.props.logEntries.count; return (
@@ -90,12 +83,6 @@ class App extends React.Component {
)} -
0} - canGoForward={canGoForward} - goBack={this.goBack} - goForward={this.goForward} - >
{children}
@@ -114,9 +101,6 @@ function mapStateToProps(state: RootState) { function mapDispatchToProps(dispatch) { return { ...bindActionCreators({ ...ResultActions }, dispatch), - getCommits: () => dispatch(ResultActions.getCommits()), - getNextCommits: () => dispatch(ResultActions.getNextCommits()), - getPreviousCommits: () => dispatch(ResultActions.getPreviousCommits()), search: (text: string) => dispatch(ResultActions.search(text)), }; } diff --git a/browser/src/index.tsx b/browser/src/index.tsx index 2978111c..bc4c6f9e 100644 --- a/browser/src/index.tsx +++ b/browser/src/index.tsx @@ -33,7 +33,7 @@ ReactDOM.render( initialize(window['vscode']); -store.dispatch(ResultActions.getCommits()); +store.dispatch(ResultActions.getCommits(0, 5)); store.dispatch(ResultActions.getBranches()); store.dispatch(ResultActions.getAuthors()); store.dispatch(ResultActions.fetchAvatars()); diff --git a/browser/src/main.css b/browser/src/main.css index 91026c1c..c38ef323 100644 --- a/browser/src/main.css +++ b/browser/src/main.css @@ -700,4 +700,3 @@ header .links { outline: none; border: 1px solid var(--vscode-focusBorder); } - diff --git a/browser/src/reducers/logEntries.ts b/browser/src/reducers/logEntries.ts index e441cca5..738a83b6 100644 --- a/browser/src/reducers/logEntries.ts +++ b/browser/src/reducers/logEntries.ts @@ -9,7 +9,7 @@ const initialState: LogEntriesState = { isLoading: false, isLoadingCommit: undefined, items: [], - pageIndex: 0, + startIndex: 0, }; function fixDates(logEntry: LogEntry) { @@ -24,13 +24,20 @@ function fixDates(logEntry: LogEntry) { export default handleActions( { [Actions.FETCHED_COMMITS]: (state, action: ReduxActions.Action) => { - action.payload!.items.forEach(x => { + action.payload!.items.forEach((x, i) => { fixDates(x); + if (state.items[i + action.payload.startIndex] !== undefined) { + state.items.splice(i + action.payload.startIndex, 1, x); + } else { + state.items.splice(i + action.payload.startIndex, 0, x); + } }); return { ...state, - ...action.payload!, + startIndex: action.payload.startIndex, + stopIndex: action.payload.stopIndex, + count: action.payload.count, selected: undefined, isLoading: false, isLoadingCommit: undefined, diff --git a/package-lock.json b/package-lock.json index 517b310d..1994c4b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35337,6 +35337,15 @@ "@types/react": "*" } }, + "@types/react-virtualized": { + "version": "9.21.10", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.10.tgz", + "integrity": "sha512-f5Ti3A7gGdLkPPFNHTrvKblpsPNBiQoSorOEOD+JPx72g/Ng2lOt4MYfhvQFQNgyIrAro+Z643jbcKafsMW2ag==", + "requires": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, "@types/redux-actions": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@types/redux-actions/-/redux-actions-2.6.1.tgz", @@ -35469,178 +35478,177 @@ } }, "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", "dev": true }, "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", "dev": true }, "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" + "@webassemblyjs/ast": "1.9.0" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" } }, "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" } }, "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", "@xtuc/long": "4.2.2" } }, @@ -36103,6 +36111,14 @@ "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } } }, "assert": { @@ -37816,9 +37832,9 @@ "integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q==" }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==", "dev": true }, "body-parser": { @@ -37981,21 +37997,44 @@ "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", "dev": true, "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "browserify-zlib": { @@ -39232,6 +39271,14 @@ "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } } }, "create-hash": { @@ -39405,9 +39452,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -39907,6 +39954,14 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } } }, "dir-glob": { @@ -42459,13 +42514,27 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "hash.js": { @@ -42543,7 +42612,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, "requires": { "react-is": "^16.7.0" } @@ -43323,6 +43391,11 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -45903,14 +45976,12 @@ "realpath-native": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", - "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", - "dev": true + "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==" }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "supports-color": { "version": "7.1.0", @@ -47810,6 +47881,18 @@ "wrap-ansi": "^3.0.1" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -51337,6 +51420,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } } }, "pump": { @@ -55696,16 +55787,16 @@ "dev": true }, "webpack": { - "version": "4.41.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.6.tgz", - "integrity": "sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.1", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", @@ -55716,14 +55807,22 @@ "loader-utils": "^1.2.3", "memory-fs": "^0.4.1", "micromatch": "^3.1.10", - "mkdirp": "^0.5.1", + "mkdirp": "^0.5.3", "neo-async": "^2.6.1", "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + } } }, "webpack-cli": { diff --git a/package.json b/package.json index 6e56c4d5..2001ca4c 100644 --- a/package.json +++ b/package.json @@ -505,7 +505,7 @@ }, "dependencies": { "@material-ui/core": "latest", - "react-virtualized": "latest", + "@types/react-virtualized": "^9.21.10", "event-stream": "4.0.1", "fs-extra": "^8.1.0", "gravatar": "^1.8.1", @@ -513,6 +513,7 @@ "iconv-lite": "^0.5.1", "inversify": "^5.0.1", "query-string": "^6.13.7", + "react-virtualized": "latest", "reflect-metadata": "^0.1.12", "stack-trace": "0.0.10", "tmp": "0.1.0", diff --git a/src/adapter/repository/git.ts b/src/adapter/repository/git.ts index 99422be7..e30b34b2 100644 --- a/src/adapter/repository/git.ts +++ b/src/adapter/repository/git.ts @@ -3,7 +3,6 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import * as tmp from 'tmp'; import * as vscode from 'vscode'; -import { IWorkspaceService } from '../../application/types/workspace'; import { cache } from '../../common/cache'; import { IServiceContainer } from '../../ioc/types'; import { ActionedUser, Branch, CommittedFile, Hash, IGitService, LogEntries, LogEntry, Ref, FsUri } from '../../types'; @@ -204,8 +203,8 @@ export class Git implements IGitService { @captureTelemetry() public async getLogEntries( - pageIndex = 0, - pageSize = 0, + startIndex = 0, + stopIndex = 0, branches: string[] = [], searchText = '', file?: vscode.Uri, @@ -213,15 +212,11 @@ export class Git implements IGitService { author?: string, ): Promise { branches = Array.isArray(branches) ? branches : []; - if (pageSize <= 0) { - const workspace = this.serviceContainer.get(IWorkspaceService); - pageSize = workspace.getConfiguration('gitHistory').get('pageSize', 100); - } const relativePath = file ? this.getGitRelativePath(file) : undefined; const args = this.gitArgsService.getLogArgs( - pageIndex, - pageSize, + startIndex, + stopIndex, branches, searchText, relativePath, @@ -268,8 +263,8 @@ export class Git implements IGitService { count, branches, file, - pageIndex, - pageSize, + startIndex, + stopIndex, searchText, } as LogEntries; } diff --git a/src/adapter/repository/gitArgsService.ts b/src/adapter/repository/gitArgsService.ts index 54150ad1..8df67c89 100644 --- a/src/adapter/repository/gitArgsService.ts +++ b/src/adapter/repository/gitArgsService.ts @@ -41,8 +41,8 @@ export class GitArgsService implements IGitArgsService { } public getLogArgs( - pageIndex = 0, - pageSize = 100, + startIndex = 0, + stopIndex = 100, branches: string[] = [], searchText = '', relativeFilePath?: string, @@ -52,6 +52,7 @@ export class GitArgsService implements IGitArgsService { const allBranches = branches.length === 0; const currentBranch = branches.length === 1 && branches[0].trim() === '*'; const specificBranch = !allBranches && !currentBranch; + const maxCount = stopIndex - startIndex > 0 ? stopIndex - startIndex : 1; const authorArgs: string[] = []; if (author && author.length > 0) { @@ -84,12 +85,12 @@ export class GitArgsService implements IGitArgsService { }); } - logArgs.push('--date-order', '--decorate=full', `--skip=${pageIndex * pageSize}`, `--max-count=${pageSize}`); + logArgs.push('--date-order', '--decorate=full', `--skip=${startIndex}`, `--max-count=${maxCount}`); fileStatArgs.push( '--date-order', '--decorate=full', - `--skip=${pageIndex * pageSize}`, - `--max-count=${pageSize}`, + `--skip=${stopIndex * startIndex}`, + `--max-count=${maxCount}`, ); // Count only the number of lines in the log diff --git a/src/server/apiController.ts b/src/server/apiController.ts index c68728af..05a6959e 100644 --- a/src/server/apiController.ts +++ b/src/server/apiController.ts @@ -30,29 +30,22 @@ export class ApiController { let searchText = args.searchText; searchText = typeof searchText === 'string' && searchText.length === 0 ? undefined : searchText; - const pageIndex: number | undefined = args.pageIndex ? parseInt(args.pageIndex, 10) : 0; + const startIndex: number | undefined = args.startIndex ? parseInt(args.startIndex) : 0; + const stopIndex: number | undefined = args.stopIndex ? parseInt(args.stopIndex) : 30; const author: string | undefined = typeof args.authorFilter === 'string' ? args.authorFilter : undefined; - const lineNumber: number | undefined = args.line ? parseInt(args.line, 10) : undefined; const branches: string[] = []; if (args.branchSelection != BranchSelection.All) branches.push(args.branchName); - let pageSize: number | undefined = args.pageSize ? parseInt(args.pageSize, 10) : undefined; - // When getting history for a line, then always get 10 pages, cuz `git log -L` also spits out the diff, hence slow - // with git cli version 2.22 "git log -s" may be used to suppress patch output. - // See https://github.com/git/git/commit/9f607cd09c4c953d76de4bd18ba1c9bf6cf383cd - if (typeof lineNumber === 'number') { - pageSize = 10; - } const filePath: string | undefined = args.file; const file = filePath ? Uri.file(filePath) : undefined; const entries = await this.gitService.getLogEntries( - pageIndex, - pageSize, + startIndex, + stopIndex, branches, searchText, file, @@ -62,8 +55,8 @@ export class ApiController { return { ...entries, - pageIndex, - pageSize, + startIndex, + stopIndex, }; } public async getBranches() { diff --git a/src/types.ts b/src/types.ts index 13224d13..115bdfac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,8 +77,8 @@ export type ActionedDetails = ActionedUser & { export type LogEntriesResponse = { items: LogEntry[]; count: number; - pageIndex?: number; - pageSize?: number; + startIndex?: number; + stopIndex?: number; lineNumber?: number; selected?: LogEntry; isLoading?: boolean; diff --git a/webpack.config.js b/webpack.config.js index 92506e04..6c78f69f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,7 +27,7 @@ const browser = { extensions: ['.js', '.ts', '.tsx'], mainFields: ['main'], }, - devtool: isProduction ? false : 'source-map', + devtool: isProduction ? false : 'eval-cheap-module-source-map', module: { rules: [ // .ts, .tsx @@ -41,6 +41,7 @@ const browser = { // scss { test: /\.css$/, + exclude: /(node_modules)/, use: [ { loader: 'file-loader', From 89f307cd0de74e629f444a2ad6b7f869d07350ca Mon Sep 17 00:00:00 2001 From: ole1986 Date: Mon, 1 Jun 2020 08:42:38 +0200 Subject: [PATCH 03/12] Very first implementation of branch graph for InfiniteLoader --- browser/src/actions/messagebus.ts | 23 +- browser/src/actions/results.ts | 24 +- browser/src/components/Header/index.tsx | 23 +- .../components/LogView/BranchGraph/index.tsx | 400 ++++++++++- .../LogView/BranchGraph/svgGenerator.ts | 633 ------------------ .../src/components/LogView/LogEntry/index.tsx | 6 +- .../components/LogView/LogEntryList/index.tsx | 107 ++- .../src/components/LogView/LogView/index.tsx | 8 +- browser/src/constants/resultActions.ts | 1 + browser/src/containers/App/index.tsx | 35 +- browser/src/index.tsx | 9 - browser/src/reducers/graph.ts | 13 +- browser/src/reducers/index.ts | 7 +- browser/src/reducers/logEntries.ts | 11 +- src/adapter/avatar/base.ts | 5 +- src/adapter/parsers/fileStat/parser.ts | 5 +- src/adapter/parsers/log/parser.ts | 3 +- src/adapter/repository/factory.ts | 3 +- src/adapter/repository/git.ts | 7 +- src/commandFactories/commitFactory.ts | 21 +- src/commandFactories/fileCommitFactory.ts | 6 +- .../commit/commitViewExplorer.ts | 3 +- src/commandHandlers/commit/compare.ts | 3 +- .../commit/compareViewExplorer.ts | 3 +- src/commandHandlers/commit/gitCheckout.ts | 3 +- src/commandHandlers/commit/gitCherryPick.ts | 7 +- src/commandHandlers/commit/gitCommit.ts | 13 +- src/commandHandlers/commit/gitMerge.ts | 7 +- src/commandHandlers/commit/gitRebase.ts | 7 +- src/commandHandlers/commit/revert.ts | 7 +- src/commandHandlers/fileCommit/fileHistory.ts | 16 +- src/commandHandlers/handlerManager.ts | 3 +- src/commandHandlers/ref/gitRef.ts | 15 +- src/common/helpers.ts | 5 +- src/common/uiService.ts | 12 +- src/nodes/nodeBuilder.ts | 3 +- src/server/apiController.ts | 45 +- src/server/htmlViewer.ts | 29 +- src/types.ts | 10 +- src/viewers/commitViewer.ts | 3 +- src/viewers/commitViewerFactory.ts | 14 +- 41 files changed, 742 insertions(+), 816 deletions(-) delete mode 100644 browser/src/components/LogView/BranchGraph/svgGenerator.ts diff --git a/browser/src/actions/messagebus.ts b/browser/src/actions/messagebus.ts index 3c7f0630..6e5eaa38 100644 --- a/browser/src/actions/messagebus.ts +++ b/browser/src/actions/messagebus.ts @@ -13,7 +13,10 @@ function uuid() { ); } -function createPromiseFromMessageEvent(requestId): Promise { +function createPromiseFromMessageEvent( + requestId: string, + persistentCallback: (requestId: string, data: any) => any = undefined, +): Promise { return new Promise((resolve, reject) => { const handleEvent = (e: MessageEvent) => { if (requestId === e.data.requestId) { @@ -27,20 +30,32 @@ function createPromiseFromMessageEvent(requestId): Promise { } }; - window.addEventListener('message', handleEvent); + if (persistentCallback !== undefined) { + window.addEventListener('message', e => persistentCallback(requestId, e.data)); + } else { + window.addEventListener('message', handleEvent); + } }); } -export function post(cmd: string, payload: any): Promise { +export function post( + cmd: string, + payload: any, + persistentCallback: (requestId: string, data: any) => any = undefined, +): Promise { const requestId = uuid(); + if (persistentCallback !== undefined) { + payload.requestId = requestId; + } + vsc.postMessage({ requestId, cmd, payload, }); - return createPromiseFromMessageEvent(requestId); + return createPromiseFromMessageEvent(requestId, persistentCallback); } export function initialize(vscodeApi: any) { diff --git a/browser/src/actions/results.ts b/browser/src/actions/results.ts index 69849527..71249b29 100644 --- a/browser/src/actions/results.ts +++ b/browser/src/actions/results.ts @@ -3,7 +3,7 @@ import { createAction } from 'redux-actions'; import * as Actions from '../constants/resultActions'; import { ActionedUser, Avatar, CommittedFile, LogEntriesResponse, LogEntry, Ref } from '../definitions'; import { BranchesState, RootState } from '../reducers'; -import { BranchSelection, Branch } from '../types'; +import { BranchSelection, Branch, Graph } from '../types'; import { post } from '../actions/messagebus'; export const addResults = createAction>(Actions.FETCHED_COMMITS); @@ -11,6 +11,7 @@ export const updateCommit = createAction(Actions.FETCHED_COMMIT); export const updateCommitInList = createAction(Actions.UPDATE_COMMIT_IN_LIST); export const updateSettings = createAction(Actions.UPDATE_SETTINGS); export const updateBranchList = createAction(Actions.FETCHED_BRANCHES); +export const clearCommits = createAction(Actions.CLEAR_COMMITS); export const clearCommitSelection = createAction(Actions.CLEAR_SELECTED_COMMIT); export const goToPreviousPage = createAction(Actions.GO_TO_PREVIOUS_PAGE); export const goToNextPage = createAction(Actions.GO_TO_NEXT_PAGE); @@ -21,8 +22,14 @@ export const fetchedAuthors = createAction(Actions.FETCHED_AUTHO // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ResultActions { - export const commitsRendered = createAction(Actions.COMMITS_RENDERED); + export const commitsRendered = createAction(Actions.COMMITS_RENDERED); + export const onStateChanged = (listener: (requestId: string, data: any) => any) => { + return (dispatch: Dispatch, getState: () => RootState) => { + // register state message handler + return post('registerState', {}, listener); + }; + }; export const actionCommit = (logEntry: LogEntry, name = '', value = '') => { return async (dispatch: Dispatch, getState: () => RootState) => { dispatch(notifyIsFetchingCommit(logEntry.hash.full)); @@ -38,7 +45,6 @@ export namespace ResultActions { switch (name) { case 'reset_soft': case 'reset_hard': - dispatch(ResultActions.refresh()); break; case 'newtag': break; @@ -131,6 +137,8 @@ export namespace ResultActions { return (dispatch: Dispatch, getState: () => RootState) => { //state.settings.branchName = branchName; dispatch(updateSettings({ branchName, branchSelection })); + dispatch(clearCommits()); + dispatch(notifyIsLoading()); const state = getState(); return fetchCommits(dispatch, state, 0, undefined); }; @@ -142,15 +150,6 @@ export namespace ResultActions { return fetchCommits(dispatch, state, 0, undefined); }; }; - export const refresh = () => { - return (dispatch: Dispatch, getState: () => RootState) => { - const state = getState(); - // update branches - fetchBranches(dispatch, state); - return fetchCommits(dispatch, state, undefined, undefined); - }; - }; - export const getCommits = (startIndex: number, stopIndex: number) => { return (dispatch: Dispatch, getState: () => RootState) => { const state = getState(); @@ -176,7 +175,6 @@ function fetchCommits( startIndex?: number, stopIndex?: number, ): Promise { - dispatch(notifyIsLoading()); return post('getLogEntries', { ...store.settings, startIndex, diff --git a/browser/src/components/Header/index.tsx b/browser/src/components/Header/index.tsx index 8b85f45f..4c20f27e 100644 --- a/browser/src/components/Header/index.tsx +++ b/browser/src/components/Header/index.tsx @@ -26,7 +26,10 @@ interface HeaderState { export class Header extends React.Component { constructor(props: HeaderProps) { super(props); - this.state = { isLoading: props.isLoading, searchText: props.searchText }; + this.state = { + isLoading: props.isLoading, + searchText: props.searchText, + }; } static getDerivedStateFromProps(nextProps, prevState) { @@ -40,19 +43,12 @@ export class Header extends React.Component { } }; private onClear = () => { - this.setState({ isLoading: this.state.isLoading }); + this.setState({ isLoading: this.state.isLoading, searchText: '' }); if (!this.state.isLoading) { this.setState({ isLoading: true }); this.props.clearSearch(); } }; - private onRefresh = () => { - this.setState({ isLoading: this.state.isLoading }); - if (!this.state.isLoading) { - this.setState({ isLoading: true }); - this.props.refresh(); - } - }; private remoteLink() { if (this.props.branches.length === 0) { @@ -103,7 +99,10 @@ export class Header extends React.Component { } private handleSearchChange = (e: React.ChangeEvent) => { - this.setState({ isLoading: this.state.isLoading, searchText: e.target.value }); + this.setState({ + isLoading: this.state.isLoading, + searchText: e.target.value, + }); }; private handleKeyDown = (e: React.KeyboardEvent) => { @@ -133,9 +132,6 @@ export class Header extends React.Component { - {this.remoteLink()} ); @@ -158,7 +154,6 @@ function mapDispatchToProps(dispatch) { return { search: (text: string) => dispatch(ResultActions.search(text)), clearSearch: () => dispatch(ResultActions.clearSearch()), - refresh: () => dispatch(ResultActions.refresh()), }; } diff --git a/browser/src/components/LogView/BranchGraph/index.tsx b/browser/src/components/LogView/BranchGraph/index.tsx index beca732a..e69cebe6 100644 --- a/browser/src/components/LogView/BranchGraph/index.tsx +++ b/browser/src/components/LogView/BranchGraph/index.tsx @@ -1,22 +1,392 @@ +import { LogEntry } from '../../../definitions'; import * as React from 'react'; import { connect } from 'react-redux'; import { RootState } from '../../../reducers'; -import { BranchGrapProps, drawGitGraph } from './svgGenerator'; +import { IGraphState } from '../../../reducers/graph'; + +type BranchGrapProps = IGraphState & { + logEntries: LogEntry[]; +}; + +const COLORS = [ + '#ffab1d', + '#fd8c25', + '#f36e4a', + '#fc6148', + '#d75ab6', + '#b25ade', + '#6575ff', + '#7b77e9', + '#4ea8ec', + '#00d0f5', + '#4eb94e', + '#51af23', + '#8b9f1c', + '#d0b02f', + '#d0853a', + '#a4a4a4', + '#ffc51f', + '#fe982c', + '#fd7854', + '#ff705f', + '#e467c3', + '#bd65e9', + '#7183ff', + '#8985f7', + '#55b6ff', + '#10dcff', + '#51cd51', + '#5cba2e', + '#9eb22f', + '#debe3d', + '#e19344', + '#b8b8b8', + '#ffd03b', + '#ffae38', + '#ff8a6a', + '#ff7e7e', + '#ef72ce', + '#c56df1', + '#8091ff', + '#918dff', + '#69caff', + '#3ee1ff', + '#72da72', + '#71cf43', + '#abbf3c', + '#e6c645', + '#eda04e', + '#c5c5c5', + '#ffd84c', + '#ffb946', + '#ff987c', + '#ff8f8f', + '#fb7eda', + '#ce76fa', + '#90a0ff', + '#9c98ff', + '#74cbff', + '#64e7ff', + '#7ce47c', + '#85e357', + '#b8cc49', + '#edcd4c', + '#f9ad58', + '#d0d0d0', + '#ffe651', + '#ffbf51', + '#ffa48b', + '#ff9d9e', + '#ff8de1', + '#d583ff', + '#97a9ff', + '#a7a4ff', + '#82d3ff', + '#76eaff', + '#85ed85', + '#8deb5f', + '#c2d653', + '#f5d862', + '#fcb75c', + '#d7d7d7', + '#fff456', + '#ffc66d', + '#ffb39e', + '#ffabad', + '#ff9de5', + '#da90ff', + '#9fb2ff', + '#b2afff', + '#8ddaff', + '#8bedff', + '#99f299', + '#97f569', + '#cde153', + '#fbe276', + '#ffc160', + '#e1e1e1', + '#fff970', + '#ffd587', + '#ffc2b2', + '#ffb9bd', + '#ffa5e7', + '#de9cff', + '#afbeff', + '#bbb8ff', + '#9fd4ff', + '#9aefff', + '#b3f7b3', + '#a0fe72', + '#dbef6c', + '#fcee98', + '#ffca69', + '#eaeaea', + '#763700', + '#9f241e', + '#982c0e', + '#a81300', + '#80035f', + '#650d90', + '#082fca', + '#3531a3', + '#1d4892', + '#006f84', + '#036b03', + '#236600', + '#445200', + '#544509', + '#702408', + '#343434', + '#9a5000', + '#b33a20', + '#b02f0f', + '#c8210a', + '#950f74', + '#7b23a7', + '#263dd4', + '#4642b4', + '#1d5cac', + '#00849c', + '#0e760e', + '#287800', + '#495600', + '#6c5809', + '#8d3a13', + '#4e4e4e', + '#c36806', + '#c85120', + '#bf3624', + '#df2512', + '#aa2288', + '#933bbf', + '#444cde', + '#5753c5', + '#1d71c6', + '#0099bf', + '#188018', + '#2e8c00', + '#607100', + '#907609', + '#ab511f', + '#686868', + '#e47b07', + '#e36920', + '#d34e2a', + '#ec3b24', + '#ba3d99', + '#9d45c9', + '#4f5aec', + '#615dcf', + '#3286cf', + '#00abca', + '#279227', + '#3a980c', + '#6c7f00', + '#ab8b0a', + '#b56427', + '#757575', + '#ff911a', + '#fc8120', + '#e7623e', + '#fa5236', + '#ca4da9', + '#a74fd3', + '#5a68ff', + '#6d69db', + '#489bd9', + '#00bcde', + '#36a436', + '#47a519', + '#798d0a', + '#c1a120', + '#bf7730', + '#8e8e8e', +]; + +type BranchGraphItem = { path: SVGPathElement; hash: string; level: number }; + +function drawGraph(svg: SVGElement, props: BranchGrapProps) { + const cy = props.itemHeight / 2; + const r = 4; + const cx = 15; + let cyOffset = 0; + let cxOffset = 0; + + const branchLines: BranchGraphItem[] = []; + const circles: SVGCircleElement[] = []; + + let i = 0; + let isNewBranch = true; + let maxLevel = 0; + + branchLines.push({ + hash: props.logEntries[0].hash.full, + path: document.createElementNS('http://www.w3.org/2000/svg', 'path'), + level: 0, + }); + try { + // only up to the necessary entries + for (i; i < props.logEntries.length; i++) { + const hash = props.logEntries[i].hash.full; + const nextEntry = props.logEntries.length > i + 1 ? props.logEntries[i + 1] : null; + const parents = props.logEntries[i].parents.map(x => x.full); + + let branchLine = branchLines.filter(x => x.hash === hash).shift(); + + if (branchLine === undefined) { + throw 'branchLine not found'; + } + + cyOffset += cy; + cxOffset = (branchLine.level + 1) * cx; + + const filtered = branchLines.filter(xc => xc.hash === hash); + + if (filtered.length > 1) { + while (filtered.length > 1) { + const found = filtered.pop(); + const index = branchLines.indexOf(found); + const p = + ' L' + + (found.level + 1) * cx + + ' ' + + (cyOffset - cy).toFixed() + + ' L ' + + cxOffset.toFixed() + + ' ' + + cyOffset.toFixed(); + found.path.setAttribute('d', found.path.getAttribute('d') + p); + svg.appendChild(found.path); + branchLines.splice(index, 1); + } + } + + branchLine.hash = parents[0]; + + if (parents.length > 1) { + if (isNewBranch) { + const activeLineCount = branchLines.length - 1; + branchLine.path.setAttribute( + 'd', + 'M' + + cxOffset.toFixed() + + ' ' + + cyOffset.toFixed() + + ' L' + + ((activeLineCount + 1) * cx).toFixed() + + ' ' + + (cyOffset + cy).toFixed(), + ); + branchLine.path.setAttribute('style', 'stroke:' + COLORS[activeLineCount]); + isNewBranch = false; + } + + // continue the previous + branchLine.path.setAttribute('d', branchLine.path.getAttribute('d') + ' L' + cxOffset + ' ' + cyOffset); + + //parents.shift(); + parents.forEach(x => { + if (branchLines.map(x => x.hash).indexOf(x) > -1) return; + branchLine = { + hash: x, + path: document.createElementNS('http://www.w3.org/2000/svg', 'path'), + level: branchLines.length, + }; + branchLines.push(branchLine); + isNewBranch = true; + }); + } + + maxLevel = maxLevel < branchLines.length - 1 ? branchLines.length - 1 : maxLevel; + + const found = branchLines.filter(x => x.hash === nextEntry?.hash?.full).pop(); + const foundIndex = branchLines.indexOf(found); + + if (isNewBranch) { + const activeLineCount = branchLines.length - 1; + branchLine.path.setAttribute( + 'd', + 'M' + + cxOffset.toFixed() + + ' ' + + cyOffset.toFixed() + + ' L' + + ((activeLineCount + 1) * cx).toFixed() + + ' ' + + (cyOffset + cy).toFixed(), + ); + branchLine.path.setAttribute('style', 'stroke:' + COLORS[activeLineCount]); + isNewBranch = false; + } else { + branchLine.path.setAttribute( + 'd', + branchLine.path.getAttribute('d') + ' L' + cxOffset.toFixed() + ' ' + cyOffset.toFixed(), + ); + } + + const svgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + + svgCircle.setAttribute('cx', cxOffset.toString()); + svgCircle.setAttribute('cy', cyOffset.toString()); + svgCircle.setAttribute('r', r.toString()); + + circles.push(svgCircle); + + if (nextEntry != null && foundIndex === -1) { + // current known max level + const currentMaxLevel = Math.max(...branchLines.map(x => x.level)); + const nextLevel = currentMaxLevel + 1; + // when the parent does not match with the next hash + branchLine = { + hash: nextEntry.hash.full, + path: document.createElementNS('http://www.w3.org/2000/svg', 'path'), + level: nextLevel, + }; + branchLines.push(branchLine); + //const activeLineCount = branchLines.length - 1; + branchLine.path.setAttribute( + 'd', + 'M' + ((nextLevel + 1) * cx).toFixed() + ' ' + (cyOffset + props.itemHeight).toFixed(), + ); + branchLine.path.setAttribute('style', 'stroke:' + COLORS[nextLevel]); + } + + cyOffset += cy; + } + } catch (ex) { + console.error(ex); + } + + branchLines.forEach((x, lvl) => { + const cxOffset = (lvl + 1) * cx; + // draw line until the end + x.path.setAttribute('d', x.path.getAttribute('d') + ' L' + cxOffset + ' ' + cyOffset); + svg.appendChild(x.path); + }); + + circles.forEach(x => svg.appendChild(x)); + + const scrollOffset = props.itemHeight * props.startIndex; + svg.style.top = scrollOffset * -1 + 'px'; + svg.setAttribute('height', (props.height + scrollOffset).toString()); + + // find the React virtualize grid to add some padding + ((svg.nextSibling as HTMLDivElement).firstChild as HTMLDivElement).style.paddingLeft = + (maxLevel + 1) * (cx + r) + 'px'; +} class BrachGraph extends React.Component { - componentDidUpdate(prevProps: BranchGrapProps) { - if (this.props.hideGraph) { - drawGitGraph(this.svg, this.svg.nextSibling as HTMLElement, 0, this.props.itemHeight, [], true); + componentWillUpdate(newProps: BranchGrapProps) { + if (newProps.hideGraph) { + this.svg.innerHTML = ''; return; } - if (prevProps.updateTick === this.props.updateTick) { + if (newProps.updateTick === this.props.updateTick) { return; } - // Hack, first clear before rebuilding. - // Remember, we will need to support apending results, as opposed to clearing page - drawGitGraph(this.svg, this.svg.nextSibling as HTMLElement, 0, this.props.itemHeight, []); - drawGitGraph(this.svg, this.svg.nextSibling as HTMLElement, 0, this.props.itemHeight, this.props.logEntries); + this.svg.innerHTML = ''; + drawGraph(this.svg, newProps); } private svg: SVGSVGElement; @@ -26,19 +396,9 @@ class BrachGraph extends React.Component { } function mapStateToProps(state: RootState): BranchGrapProps { - const hideGraph = - state && - state.logEntries && - ((state.settings.searchText && state.settings.searchText.length > 0) || - (state.settings.file && state.settings.file.length > 0) || - (state.settings.authorFilter && state.settings.authorFilter.length > 0) || - state.logEntries.isLoading); - return { + ...state.graph, logEntries: state.logEntries.items, - hideGraph, - itemHeight: state.graph.itemHeight, - updateTick: state.graph.updateTick, }; } diff --git a/browser/src/components/LogView/BranchGraph/svgGenerator.ts b/browser/src/components/LogView/BranchGraph/svgGenerator.ts deleted file mode 100644 index 9545b9aa..00000000 --- a/browser/src/components/LogView/BranchGraph/svgGenerator.ts +++ /dev/null @@ -1,633 +0,0 @@ -import { LogEntry } from '../../../definitions'; - -export type BranchGrapProps = { - hideGraph: boolean; - logEntries: LogEntry[]; - itemHeight?: number; - updateTick?: number; -}; - -let branches: { hash: string; path: any; x?: number; wasFictional: boolean }[] = []; -let branchColor = 0; -const COLORS = [ - '#ffab1d', - '#fd8c25', - '#f36e4a', - '#fc6148', - '#d75ab6', - '#b25ade', - '#6575ff', - '#7b77e9', - '#4ea8ec', - '#00d0f5', - '#4eb94e', - '#51af23', - '#8b9f1c', - '#d0b02f', - '#d0853a', - '#a4a4a4', - '#ffc51f', - '#fe982c', - '#fd7854', - '#ff705f', - '#e467c3', - '#bd65e9', - '#7183ff', - '#8985f7', - '#55b6ff', - '#10dcff', - '#51cd51', - '#5cba2e', - '#9eb22f', - '#debe3d', - '#e19344', - '#b8b8b8', - '#ffd03b', - '#ffae38', - '#ff8a6a', - '#ff7e7e', - '#ef72ce', - '#c56df1', - '#8091ff', - '#918dff', - '#69caff', - '#3ee1ff', - '#72da72', - '#71cf43', - '#abbf3c', - '#e6c645', - '#eda04e', - '#c5c5c5', - '#ffd84c', - '#ffb946', - '#ff987c', - '#ff8f8f', - '#fb7eda', - '#ce76fa', - '#90a0ff', - '#9c98ff', - '#74cbff', - '#64e7ff', - '#7ce47c', - '#85e357', - '#b8cc49', - '#edcd4c', - '#f9ad58', - '#d0d0d0', - '#ffe651', - '#ffbf51', - '#ffa48b', - '#ff9d9e', - '#ff8de1', - '#d583ff', - '#97a9ff', - '#a7a4ff', - '#82d3ff', - '#76eaff', - '#85ed85', - '#8deb5f', - '#c2d653', - '#f5d862', - '#fcb75c', - '#d7d7d7', - '#fff456', - '#ffc66d', - '#ffb39e', - '#ffabad', - '#ff9de5', - '#da90ff', - '#9fb2ff', - '#b2afff', - '#8ddaff', - '#8bedff', - '#99f299', - '#97f569', - '#cde153', - '#fbe276', - '#ffc160', - '#e1e1e1', - '#fff970', - '#ffd587', - '#ffc2b2', - '#ffb9bd', - '#ffa5e7', - '#de9cff', - '#afbeff', - '#bbb8ff', - '#9fd4ff', - '#9aefff', - '#b3f7b3', - '#a0fe72', - '#dbef6c', - '#fcee98', - '#ffca69', - '#eaeaea', - '#763700', - '#9f241e', - '#982c0e', - '#a81300', - '#80035f', - '#650d90', - '#082fca', - '#3531a3', - '#1d4892', - '#006f84', - '#036b03', - '#236600', - '#445200', - '#544509', - '#702408', - '#343434', - '#9a5000', - '#b33a20', - '#b02f0f', - '#c8210a', - '#950f74', - '#7b23a7', - '#263dd4', - '#4642b4', - '#1d5cac', - '#00849c', - '#0e760e', - '#287800', - '#495600', - '#6c5809', - '#8d3a13', - '#4e4e4e', - '#c36806', - '#c85120', - '#bf3624', - '#df2512', - '#aa2288', - '#933bbf', - '#444cde', - '#5753c5', - '#1d71c6', - '#0099bf', - '#188018', - '#2e8c00', - '#607100', - '#907609', - '#ab511f', - '#686868', - '#e47b07', - '#e36920', - '#d34e2a', - '#ec3b24', - '#ba3d99', - '#9d45c9', - '#4f5aec', - '#615dcf', - '#3286cf', - '#00abca', - '#279227', - '#3a980c', - '#6c7f00', - '#ab8b0a', - '#b56427', - '#757575', - '#ff911a', - '#fc8120', - '#e7623e', - '#fa5236', - '#ca4da9', - '#a74fd3', - '#5a68ff', - '#6d69db', - '#489bd9', - '#00bcde', - '#36a436', - '#47a519', - '#798d0a', - '#c1a120', - '#bf7730', - '#8e8e8e', -]; - -type Point = { x: number; y: number }; - -/** - * Plan is to create a Branch class, that will encapsulate: - * - Branch color - * - Branch information (nodes) - * - SVG element (will draw the paths on the Svg) via the PathGenerator class. - */ -class PathGenerator { - private previousPoint?: Point; - private svgPath?: string; - private points = 0; - public get path() { - return this.svgPath; - } - public addPoint(point: Point) { - if (!this.previousPoint || !this.svgPath) { - this.addFirstPoint(point); - } else if (this.previousPoint.x !== point.x) { - // If the x values are not the same, then lets curve the connection. - this.connectToLineSmoothly(point); - } else { - this.connectToLine(point); - } - this.previousPoint = point; - this.points += 1; - } - private addFirstPoint(point: Point) { - this.svgPath = `M ${point.x} ${point.y} `; - } - /** - * Join two lines using a cubic bezier curve. - */ - private connectToLineSmoothly(point: Point) { - if (!this.previousPoint) { - throw new Error('Previous point not available'); - } - - if (this.points === 1 && point.x > this.previousPoint.x) { - // Merge curves, see here https://github.com/DonJayamanne/gitHistoryVSCode/pull/463#issuecomment-590137053 - const handle = Math.abs((point.x - this.previousPoint.x) / 2); - const startPoint = `${this.previousPoint.x + handle} ${this.previousPoint.y}`; - const controlPoint = `${point.x} ${this.previousPoint.y}`; - const endPoint = `${point.x} ${point.y}`; - this.svgPath += ` C ${startPoint}, ${controlPoint}, ${endPoint}`; - } else { - // Fork curves, see here https://github.com/DonJayamanne/gitHistoryVSCode/pull/463#issue-378655710 - const handle = (point.y - this.previousPoint.y) / 2; - const startPoint = `${this.previousPoint.x} ${this.previousPoint.y + handle}`; - const controlPoint = `${point.x} ${point.y - handle}`; - const endPoint = `${point.x} ${point.y}`; - this.svgPath += ` C ${startPoint}, ${controlPoint}, ${endPoint}`; - } - } - private connectToLine(point: Point) { - this.svgPath += ` L ${point.x} ${point.y}`; - } -} - -// TODO: Think about appending (could be very expensive, but could be something worthwhile) -// Appending could produce a better UX -// Dunno, I think, cuz this way you can see where merges take place, rather than seeing a line vanish off -export function drawGitGraph( - svg: SVGSVGElement, - content: HTMLElement, - startAt: number, - logEntryHeight = 60.8, - entries: LogEntry[], - hideGraph = false, -) { - while (svg.children.length > 0) { - svg.removeChild(svg.children[0]); - } - if (hideGraph) { - for (let i = 0; i < content.children.length; i += 1) { - const element = content.children[i]; - element.setAttribute('style', element.getAttribute('style') + ';padding-left:0px'); - } - svg.style.display = 'none'; - return; - } - if (startAt === 0) { - branches = []; - branchColor = 0; - } - svg.style.display = ''; - const svgPaths = new Map(); - // Draw the graph - const circleOffset = 0; - const standardOffset = (0 + 0.5) * logEntryHeight; - let currentY = (0 + 0.5) * logEntryHeight; - const topMostY = (0 + 0.5) * logEntryHeight; - let maxLeft = 0; - let lastXOffset = 12; - let maxXOffset = 12; - if (startAt === 0) { - branchColor = 0; - } - for (let i = 0; i < startAt; i++) { - content.children[i].className = 'hidden'; - } - // Use this for new orphaned branches - const circlesToAppend: SVGCircleElement[] = []; - let fictionalBranches: { path: string; x?: number }[] = []; - // let fictionalBranch2; - let tabbedOnce = false; - let fictionalBranchesUsed = false; - let branched = false; - for (let i = startAt; i < content.children.length; ++i) { - if (i >= entries.length) { - break; - } - const entry = entries[i]; - const entryElement = content.children[i]; - if (!entry) { - break; - } - let index = 0; - (entryElement as any).branchesOnLeft = branches.length; - - // Find branches to join - let childCount = 0; - const xOffset = 12; - let removedBranches = 0; - let branchFound = i === startAt ? true : false; - let padParentCount = 0; - for (let j = 0; j < branches.length; ) { - const branch = branches[j]; - if (branch.hash === entry.hash.full) { - branchFound = true; - if (childCount === 0) { - // Replace the branch - branch.path.setAttribute('d', branch.path.cmds + currentY); - svgPaths.set(branch.path, branch.path.cmds + currentY); - if (entry.parents.length === 0) { - branches.splice(j, 1); - branched = true; - } else { - branch.hash = entry.parents[0].full; - } - index = j; - ++j; - } else { - // Join the branch - const x = (index + 1) * xOffset; - branch.path.setAttribute( - 'd', - branch.path.cmds + (currentY - logEntryHeight / 2) + ' L ' + x + ' ' + currentY, - ); - svgPaths.set( - branch.path, - branch.path.cmds + (currentY - logEntryHeight / 2) + ' L ' + x + ' ' + currentY, - ); - branches.splice(j, 1); - branched = true; - ++removedBranches; - } - ++childCount; - } else { - if (removedBranches !== 0) { - const x = (j + 1) * xOffset; - branch.path.setAttribute( - 'd', - branch.path.cmds + (currentY - logEntryHeight / 2) + ' L ' + x + ' ' + currentY, - ); - svgPaths.set( - branch.path, - branch.path.cmds + (currentY - logEntryHeight / 2) + ' L ' + x + ' ' + currentY, - ); - } - ++j; - } - } - - // Add new branches - let xFromFictionalBranch = 0; - let j = 0; - for (j = 0; j < entry.parents.length; ++j) { - const parent = entry.parents[j]; - const x = (index + j + 1) * xOffset; - if (j !== 0 || branches.length === 0) { - const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - svgPaths.set(svgPath, ''); - ++branchColor; - if (branchColor === COLORS.length) { - branchColor = 0; - } - svgPath.setAttribute('style', 'stroke:' + COLORS[branchColor]); - const origX = (index + 1) * xOffset; - let origY = currentY === standardOffset ? 0 : currentY; - origY = currentY; - if (entry.isLastCommit && !entry.isThisLastCommitMerged) { - origY = currentY; - } - (svgPath as any).cmds = - 'M ' + origX + ' ' + origY + ' L ' + x + ' ' + (currentY + logEntryHeight / 2) + ' L ' + x + ' '; - svg.appendChild(svgPath); - const obj = { - hash: parent.full, - path: svgPath, - wasFictional: false, - }; - if (fictionalBranches.length === 0 || !fictionalBranchesUsed) { - // Re-set the fictional branches if they haven't been used - // In case we have a merge as the very first step, - // Why? If we have a merge as the first step, then the fictional branch will have to move to the right - // due to the second parent which will take another index - if (!fictionalBranchesUsed) { - fictionalBranches = []; - } - // Generate at least 10 fictional branches, so we can lay them out neatly - for (let counter = 1; counter < 50; counter++) { - const newOrigX = (index + 1 + counter) * xOffset; - const fictionalBranch = - 'M ' + - newOrigX + - ' ' + - currentY + - ' L ' + - newOrigX + - ' ' + - topMostY + - ' L ' + - newOrigX + - ' '; - fictionalBranches.push({ path: fictionalBranch, x: newOrigX }); - } - } - branchFound = true; - branches.splice(index + j, 0, obj); - } - if (!branchFound && i > 0) { - index = branches.length; - - const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - svgPaths.set(svgPath, ''); - ++branchColor; - if (branchColor === COLORS.length) { - branchColor = 0; - } - svgPath.setAttribute('style', 'stroke:' + COLORS[branchColor]); - fictionalBranchesUsed = true; - const fictionalBranch = fictionalBranches.splice(0, 1)[0]; - if (entry.isLastCommit && !entry.isThisLastCommitMerged) { - // Don't start from the very top, this is the last commit for this branch - fictionalBranch.path = - 'M ' + - fictionalBranch.x + - ' ' + - currentY + - ' L ' + - fictionalBranch.x + - ' ' + - currentY + - ' L ' + - fictionalBranch.x + - ' '; - } - if (fictionalBranch.x !== undefined) { - xFromFictionalBranch = fictionalBranch.x; - } - (svgPath as any).cmds = fictionalBranch.path; - svg.appendChild(svgPath); - const obj = { - hash: parent.full, - path: svgPath, - wasFictional: true, - }; - branches.splice(index + j, 0, obj); - // We need to padd all parent log entries to take this into account - padParentCount += 1; - } - - // Incremental updates for debugging - for (let i = 0; i < branches.length; ++i) { - const branch = branches[i]; - branch.path.setAttribute('d', branch.path.cmds + currentY); - svgPaths.set(branch.path, branch.path.cmds + currentY); - } - } - - // What does this do? - let tabBranch = false; - for (j = index + j; j < branches.length; ++j) { - tabBranch = true; - const branch = branches[j]; - const x = (j + 1) * xOffset; - branch.path.cmds += currentY - logEntryHeight / 2 + ' L ' + x + ' ' + currentY + ' L ' + x + ' '; - } - tabBranch = tabBranch ? tabBranch : entry.parents.length > 1 || branched; - if (tabBranch && fictionalBranches.length > 0) { - for (let counter = 0; counter < fictionalBranches.length; counter++) { - const x = (j + 1 + counter) * xOffset; - const fictionalBranch = fictionalBranches[counter]; - if (tabbedOnce) { - fictionalBranch.path += - currentY - logEntryHeight / 2 + ' L ' + x + ' ' + currentY + ' L ' + x + ' '; - } else { - if (currentY <= logEntryHeight) { - fictionalBranch.path += currentY + ' L ' + x + ' ' + logEntryHeight + ' L ' + x + ' '; - } else { - fictionalBranch.path += - currentY + ' L ' + x + ' ' + (currentY + logEntryHeight / 2) + ' L ' + x + ' '; - } - } - fictionalBranch.x = x; - } - tabbedOnce = true; - } - - const svgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - let cx = ((branchFound || i === 0 ? index : branches.length - 1) + 1) * xOffset; - if (xFromFictionalBranch > 0) { - cx = xFromFictionalBranch; - } - - svgCircle.setAttribute('cx', cx.toString()); - svgCircle.setAttribute('cy', (currentY + circleOffset).toString()); - svgCircle.setAttribute('r', '4'); - // Enabled only for debugging - svg.appendChild(svgCircle); - circlesToAppend.push(svgCircle); - - (entryElement as any).branchesOnLeft = Math.max((entryElement as any).branchesOnLeft, branches.length); - maxLeft = Math.max(maxLeft, (entryElement as any).branchesOnLeft); - - currentY += logEntryHeight; - lastXOffset = xOffset; - if (maxXOffset < xOffset) { - maxXOffset = xOffset; - } - - if (padParentCount > 0) { - for (let parentElemtnCounter = startAt; parentElemtnCounter <= i; parentElemtnCounter++) { - if (parentElemtnCounter >= entries.length) { - break; - } - const el = content.children[parentElemtnCounter]; - (el as any).branchesOnLeft += padParentCount; - } - } - } - - branches.forEach(branch => { - const svgPath = branch.path.cmds + currentY; - branch.path.setAttribute('d', svgPath); - svgPaths.set(branch.path, svgPath); - }); - - const lines: Point[][] = []; - svgPaths.forEach((pathOrSvg, svg) => { - try { - const points = getPointsFromPath(pathOrSvg); - lines.push(points); - - // Re-generate the paths with smooth curvy edges. - const pathGenerator = new PathGenerator(); - points.forEach(point => pathGenerator.addPoint(point)); - svg.setAttribute('d', pathGenerator.path); - } catch (ex) { - console.error('Failed to generate SVG line path', ex); - } - }); - - // Sort all points in lines. - lines.forEach((points, i) => { - lines[i] = points.sort((a, b) => a.y - b.y); - }); - for (let i = startAt; i < content.children.length; ++i) { - const element = content.children[i]; - if (i >= entries.length) { - break; - } - const minLeft = Math.min(maxLeft, 3); - const left = element ? Math.max(minLeft, (element as any).branchesOnLeft) : minLeft; - const originalStyle = element.getAttribute('style'); - element.setAttribute('style', originalStyle + ';padding-left:' + (left + 1) * lastXOffset + 'px'); - try { - const pointsAtY = lines.map(points => getPointAtY((i + 1) * logEntryHeight, points)); - const maxX = Math.max(...pointsAtY.map(p => p.x)); - element.setAttribute('style', `${originalStyle};padding-left:${maxX + 12}px`); - } catch (ex) { - console.error('Failed to set padding of commit', ex); - } - } - // calculate the height - if (entries.length > 0 && !isNaN(logEntryHeight)) { - svg.setAttribute('height', (entries.length * logEntryHeight).toString()); - } -} - -function getPointAtY(y: number, points: Point[]): Point { - y = Math.floor(y); - // if the first point has has greater y, then ignore. - // I.e. if this branch/line starts after the required y, then ignore it. - if (points.length === 0) { - return { x: 0, y: 0 }; - } - if (points[0].y > y) { - return { x: 0, y: 0 }; - } - // points = points.sort((a, b) => a.y - b.y); - return points.reduce((previous, current) => { - if (current.y === y) { - return current; - } - if (current.y > y) { - return previous; - } - return current; - }, points[0]); -} -function getPointsFromPath(svgPath: string): Point[] { - const points: Point[] = []; - svgPath = svgPath.trim(); - svgPath = svgPath.startsWith('M') ? svgPath.substring(1) : svgPath; - svgPath - .split('L') - .map(item => item.trim()) - .forEach(path => { - const parts = path.split(' '); - const x = parseInt(parts[0], 10); - const y = parseInt(parts[1], 10); - points.push({ x, y }); - }); - return points; -} diff --git a/browser/src/components/LogView/LogEntry/index.tsx b/browser/src/components/LogView/LogEntry/index.tsx index 51521da3..48ff7be4 100644 --- a/browser/src/components/LogView/LogEntry/index.tsx +++ b/browser/src/components/LogView/LogEntry/index.tsx @@ -159,7 +159,8 @@ class LogEntryView extends React.Component { this.props.onAction(this.props.logEntry, 'reset_soft'), )} > - Soft + + Soft { this.props.onAction(this.props.logEntry, 'reset_hard'), )} > - Hard + + Hard { + private ref: React.RefObject; + private sizer: React.RefObject; + constructor(props?: ResultProps, context?: any) { super(props, context); - this.state = {}; + this.state = { updateGraph: true }; + this.ref = React.createRef(); + this.sizer = React.createRef(); + } + + componentDidUpdate(prevProps: ResultProps) { + if (!prevProps.logEntries.isLoading && this.props.logEntries.isLoading) { + this.props.commitsRendered({ + itemHeight: 59.8, + height: this.sizer.current.state.height, + hideGraph: true, + startIndex: 0, + }); + + this.ref.current.resetLoadMoreRowsCache(true); + setTimeout(() => { + this.props.commitsRendered({ + itemHeight: 59.8, + height: this.sizer.current.state.height, + hideGraph: false, + startIndex: 0, + }); + }, 1000); + } } isRowLoaded = ({ index }) => { @@ -53,30 +82,59 @@ class LogEntryVirtualizedTable extends React.Component { + if (callback) callback(r); + + if (this.timer) { + clearTimeout(this.timer); + this.props.commitsRendered({ + itemHeight: 59.8, + height: height, + hideGraph: true, + startIndex: r.startIndex, + }); + } + + this.timer = setTimeout(() => { + this.props.commitsRendered({ + itemHeight: 59.8, + height: height, + hideGraph: false, + startIndex: r.startIndex, + }); + }, 700); + }; + render() { return ( - - {({ onRowsRendered, registerChild }) => ( - - {({ height, width }) => ( - - )} - - )} - +
+ + + {({ onRowsRendered, registerChild }) => ( + + {({ height, width }) => ( + this.rowsRendered(info, height, onRowsRendered)} + ref={registerChild} + rowCount={this.props.logEntries.count} + rowRenderer={this.rowRenderer} + /> + )} + + )} + +
); } } @@ -91,6 +149,7 @@ function mapDispatchToProps(dispatch) { return { getCommits: (startIndex: number, stopIndex: number) => dispatch(ResultActions.getCommits(startIndex, stopIndex)), + commitsRendered: (graph: Graph) => dispatch(ResultActions.commitsRendered(graph)), }; } diff --git a/browser/src/components/LogView/LogView/index.tsx b/browser/src/components/LogView/LogView/index.tsx index cd903652..b229da0b 100644 --- a/browser/src/components/LogView/LogView/index.tsx +++ b/browser/src/components/LogView/LogView/index.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { ResultActions } from '../../../actions/results'; -import { LogEntry, Ref } from '../../../definitions'; -import { LogEntriesState, RootState } from '../../../reducers'; -import BranchGraph from '../BranchGraph'; +import { LogEntries, LogEntry, Ref } from '../../../definitions'; +import { RootState } from '../../../reducers'; import LogEntryList from '../LogEntryList'; import Dialog, { DialogType } from '../../Dialog'; import { IConfiguration } from 'src/reducers/vscode'; @@ -35,8 +34,7 @@ class LogView extends React.Component { public render() { return ( -
- +
{ constructor(props?: AppProps, context?: any) { super(props, context); + initialize(window['vscode']); + + props.dispatch(ResultActions.getCommits(0, 30)); + props.dispatch(ResultActions.getBranches()); + props.dispatch(ResultActions.getAuthors()); + props.dispatch(ResultActions.fetchAvatars()); + + props.dispatch(ResultActions.onStateChanged(this.hasStateChanged.bind(this))); + this.splitPane = React.createRef(); this.prevSplitterPos = '50%'; } - private goBack = async () => { - await this.props.getPreviousCommits(); - document.getElementById('scrollCnt').scrollTo(0, 0); - }; - private goForward = async () => { - await this.props.getNextCommits(); - document.getElementById('scrollCnt').scrollTo(0, 0); - }; - componentDidUpdate(prevProps, prevState) { if (this.props.logEntries.selected != prevProps.logEntries.selected) { if (!this.props.logEntries.selected) { @@ -59,6 +59,10 @@ class App extends React.Component { this.prevSplitterPos = s; } + public hasStateChanged(requestId: string, data: any) { + console.log('### STATE HAS CHANGED', requestId, data); + } + public render() { const { children } = this.props; return ( @@ -98,11 +102,4 @@ function mapStateToProps(state: RootState) { }; } -function mapDispatchToProps(dispatch) { - return { - ...bindActionCreators({ ...ResultActions }, dispatch), - search: (text: string) => dispatch(ResultActions.search(text)), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(App); +export default connect(mapStateToProps)(App); diff --git a/browser/src/index.tsx b/browser/src/index.tsx index bc4c6f9e..4b906a3c 100644 --- a/browser/src/index.tsx +++ b/browser/src/index.tsx @@ -2,8 +2,6 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter as Router, Route } from 'react-router-dom'; -import { ResultActions } from './actions/results'; -import { initialize } from './actions/messagebus'; import App from './containers/App'; import { ISettings } from './definitions'; import configureStore from './store'; @@ -30,10 +28,3 @@ ReactDOM.render(
, document.getElementById('root'), ); - -initialize(window['vscode']); - -store.dispatch(ResultActions.getCommits(0, 5)); -store.dispatch(ResultActions.getBranches()); -store.dispatch(ResultActions.getAuthors()); -store.dispatch(ResultActions.fetchAvatars()); diff --git a/browser/src/reducers/graph.ts b/browser/src/reducers/graph.ts index b629c7f2..9478873f 100644 --- a/browser/src/reducers/graph.ts +++ b/browser/src/reducers/graph.ts @@ -1,18 +1,25 @@ import { handleActions } from 'redux-actions'; import * as Actions from '../constants/actions'; +import { Graph } from '../../../src/types'; export interface IGraphState { - height?: string; + hideGraph?: boolean; width?: string; + height?: number; itemHeight?: number; updateTick?: number; + startIndex?: number; } const initialState: IGraphState = {}; export default handleActions( { - [Actions.COMMITS_RENDERED]: (state, action: ReduxActions.Action) => { - return { ...state, updateTick: new Date().getTime(), itemHeight: action.payload } as IGraphState; + [Actions.COMMITS_RENDERED]: (state, action: ReduxActions.Action) => { + return { + ...state, + ...action.payload, + updateTick: new Date().getTime(), + } as IGraphState; }, }, initialState, diff --git a/browser/src/reducers/index.ts b/browser/src/reducers/index.ts index cdf91635..65bd476a 100644 --- a/browser/src/reducers/index.ts +++ b/browser/src/reducers/index.ts @@ -14,7 +14,12 @@ export type LogEntriesState = LogEntriesResponse & { isLoadingCommit?: string; }; -export type BranchesState = { name: string; current: boolean; remote: string; remoteType: number }[]; +export type BranchesState = { + name: string; + current: boolean; + remote: string; + remoteType: number; +}[]; export type AuthorsState = ActionedUser[]; export type AvatarsState = Avatar[]; export type RootState = { diff --git a/browser/src/reducers/logEntries.ts b/browser/src/reducers/logEntries.ts index 738a83b6..6c926f62 100644 --- a/browser/src/reducers/logEntries.ts +++ b/browser/src/reducers/logEntries.ts @@ -44,6 +44,12 @@ export default handleActions( }; }, + [Actions.CLEAR_COMMITS]: (state, action) => { + state.items = []; + return { + ...state, + }; + }, [Actions.UPDATE_COMMIT_IN_LIST]: (state, action: ReduxActions.Action) => { const index = state.items.findIndex(item => item.hash.full === action.payload.hash.full); @@ -68,7 +74,10 @@ export default handleActions( }, [Actions.IS_FETCHING_COMMIT]: (state, action: ReduxActions.Action) => { - return { ...state, isLoadingCommit: action.payload } as LogEntriesState; + return { + ...state, + isLoadingCommit: action.payload, + } as LogEntriesState; }, [Actions.CLEAR_SELECTED_COMMIT]: (state, action: any) => { return { ...state, selected: undefined } as LogEntriesState; diff --git a/src/adapter/avatar/base.ts b/src/adapter/avatar/base.ts index 9a46e9a0..7fdbe580 100644 --- a/src/adapter/avatar/base.ts +++ b/src/adapter/avatar/base.ts @@ -38,7 +38,10 @@ export abstract class BaseAvatarProvider implements IAvatarProvider { if (retry) { const avatars = await this.getAvatarsImplementation(repository); - await this.avatarStateStore.set(key, { timestamp: new Date().getTime(), items: avatars }); + await this.avatarStateStore.set(key, { + timestamp: new Date().getTime(), + items: avatars, + }); return avatars; } else if (cachedAvatars) { return cachedAvatars.items; diff --git a/src/adapter/parsers/fileStat/parser.ts b/src/adapter/parsers/fileStat/parser.ts index 1b0e0e71..820fbf8f 100644 --- a/src/adapter/parsers/fileStat/parser.ts +++ b/src/adapter/parsers/fileStat/parser.ts @@ -61,7 +61,10 @@ export class FileStatParser implements IFileStatParser { const current = fileInfo.replace(partWithDifference, parts[1]); const currentPathParts = current.split(/\/|\/\/|\\|\\\\/g).filter(part => part.length > 0); - return { original: path.join(...originalPathParts), current: path.join(...currentPathParts) }; + return { + original: path.join(...originalPathParts), + current: path.join(...currentPathParts), + }; } } diff --git a/src/adapter/parsers/log/parser.ts b/src/adapter/parsers/log/parser.ts index f790a2a8..3fd20541 100644 --- a/src/adapter/parsers/log/parser.ts +++ b/src/adapter/parsers/log/parser.ts @@ -8,7 +8,8 @@ import { IActionDetailsParser, IFileStatParser, ILogParser } from '../types'; export class LogParser implements ILogParser { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IActionDetailsParser) private actionDetailsParser: IActionDetailsParser, + @inject(IActionDetailsParser) + private actionDetailsParser: IActionDetailsParser, ) {} public parse( gitRepoPath: string, diff --git a/src/adapter/repository/factory.ts b/src/adapter/repository/factory.ts index c829b76d..11e51bc4 100644 --- a/src/adapter/repository/factory.ts +++ b/src/adapter/repository/factory.ts @@ -20,7 +20,8 @@ export class GitServiceFactory implements IGitServiceFactory { private workspace: IWorkspaceService; constructor( - @inject(IGitCommandExecutor) private gitCmdExecutor: IGitCommandExecutor, + @inject(IGitCommandExecutor) + private gitCmdExecutor: IGitCommandExecutor, @inject(ILogParser) private logParser: ILogParser, @inject(IGitArgsService) private gitArgsService: IGitArgsService, @inject(IServiceContainer) private serviceContainer: IServiceContainer, diff --git a/src/adapter/repository/git.ts b/src/adapter/repository/git.ts index e30b34b2..7cb390de 100644 --- a/src/adapter/repository/git.ts +++ b/src/adapter/repository/git.ts @@ -19,14 +19,19 @@ import { captureTelemetry } from '../../common/telemetry'; export class Git implements IGitService { private refHashesMap: Map = new Map(); private readonly remotesService: GitRemoteService; + + public onStateChanged: vscode.Event; + constructor( private repo: Repository, @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IGitCommandExecutor) private gitCmdExecutor: IGitCommandExecutor, + @inject(IGitCommandExecutor) + private gitCmdExecutor: IGitCommandExecutor, @inject(ILogParser) private logParser: ILogParser, @inject(IGitArgsService) private gitArgsService: IGitArgsService, ) { this.remotesService = new GitRemoteService(repo, this.gitCmdExecutor); + this.onStateChanged = this.repo.state.onDidChange; } /** diff --git a/src/commandFactories/commitFactory.ts b/src/commandFactories/commitFactory.ts index 624ab093..c8871664 100644 --- a/src/commandFactories/commitFactory.ts +++ b/src/commandFactories/commitFactory.ts @@ -22,13 +22,20 @@ import { ICommitCommandFactory } from './types'; @injectable() export class CommitCommandFactory implements ICommitCommandFactory { constructor( - @inject(IGitCherryPickCommandHandler) private cherryPickHandler: IGitCherryPickCommandHandler, - @inject(IGitCheckoutCommandHandler) private checkoutHandler: IGitCheckoutCommandHandler, - @inject(IGitCompareCommandHandler) private compareHandler: IGitCompareCommandHandler, - @inject(IGitMergeCommandHandler) private mergeHandler: IGitMergeCommandHandler, - @inject(IGitRebaseCommandHandler) private rebaseHandler: IGitRebaseCommandHandler, - @inject(IGitRevertCommandHandler) private revertHandler: IGitRevertCommandHandler, - @inject(IGitCommitViewDetailsCommandHandler) private viewChangeLogHandler: IGitCommitViewDetailsCommandHandler, + @inject(IGitCherryPickCommandHandler) + private cherryPickHandler: IGitCherryPickCommandHandler, + @inject(IGitCheckoutCommandHandler) + private checkoutHandler: IGitCheckoutCommandHandler, + @inject(IGitCompareCommandHandler) + private compareHandler: IGitCompareCommandHandler, + @inject(IGitMergeCommandHandler) + private mergeHandler: IGitMergeCommandHandler, + @inject(IGitRebaseCommandHandler) + private rebaseHandler: IGitRebaseCommandHandler, + @inject(IGitRevertCommandHandler) + private revertHandler: IGitRevertCommandHandler, + @inject(IGitCommitViewDetailsCommandHandler) + private viewChangeLogHandler: IGitCommitViewDetailsCommandHandler, ) {} public async createCommands(commit: CommitDetails): Promise[]> { const commands: ICommand[] = [ diff --git a/src/commandFactories/fileCommitFactory.ts b/src/commandFactories/fileCommitFactory.ts index 72078170..8a5a3032 100644 --- a/src/commandFactories/fileCommitFactory.ts +++ b/src/commandFactories/fileCommitFactory.ts @@ -16,8 +16,10 @@ import { IFileCommitCommandFactory } from './types'; @injectable() export class FileCommitCommandFactory implements IFileCommitCommandFactory { constructor( - @inject(IGitFileHistoryCommandHandler) private fileHistoryCommandHandler: IGitFileHistoryCommandHandler, - @inject(IGitCompareFileCommandHandler) private fileCompareHandler: IGitCompareFileCommandHandler, + @inject(IGitFileHistoryCommandHandler) + private fileHistoryCommandHandler: IGitFileHistoryCommandHandler, + @inject(IGitCompareFileCommandHandler) + private fileCompareHandler: IGitCompareFileCommandHandler, @inject(ICommandManager) private commandManager: ICommandManager, @inject(IServiceContainer) private serviceContainer: IServiceContainer, ) {} diff --git a/src/commandHandlers/commit/commitViewExplorer.ts b/src/commandHandlers/commit/commitViewExplorer.ts index 22c9881b..6de979a4 100644 --- a/src/commandHandlers/commit/commitViewExplorer.ts +++ b/src/commandHandlers/commit/commitViewExplorer.ts @@ -9,7 +9,8 @@ import { IGitCommitViewExplorerCommandHandler } from '../types'; export class GitCommitViewExplorerCommandHandler implements IGitCommitViewExplorerCommandHandler { constructor( @inject(ICommandManager) private commandManager: ICommandManager, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, ) {} @command('git.commit.view.hide', IGitCommitViewExplorerCommandHandler) diff --git a/src/commandHandlers/commit/compare.ts b/src/commandHandlers/commit/compare.ts index 1978e492..a900cf1e 100644 --- a/src/commandHandlers/commit/compare.ts +++ b/src/commandHandlers/commit/compare.ts @@ -14,7 +14,8 @@ export class GitCompareCommitCommandHandler implements IGitCompareCommandHandler constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(ICommandManager) private commandManager: ICommandManager, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private application: IApplicationShell, ) {} diff --git a/src/commandHandlers/commit/compareViewExplorer.ts b/src/commandHandlers/commit/compareViewExplorer.ts index 32755ed3..2c33bb41 100644 --- a/src/commandHandlers/commit/compareViewExplorer.ts +++ b/src/commandHandlers/commit/compareViewExplorer.ts @@ -8,7 +8,8 @@ import { IGitCompareCommitViewExplorerCommandHandler } from '../types'; export class GitCompareCommitViewExplorerCommandHandler implements IGitCompareCommitViewExplorerCommandHandler { constructor( @inject(ICommandManager) private commandManager: ICommandManager, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, ) {} @command('git.commit.compare.view.hide', IGitCompareCommitViewExplorerCommandHandler) diff --git a/src/commandHandlers/commit/gitCheckout.ts b/src/commandHandlers/commit/gitCheckout.ts index 90d0d4b3..a1ed4b56 100644 --- a/src/commandHandlers/commit/gitCheckout.ts +++ b/src/commandHandlers/commit/gitCheckout.ts @@ -11,7 +11,8 @@ import { IGitCheckoutCommandHandler } from '../types'; export class GitCheckoutCommandHandler implements IGitCheckoutCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} diff --git a/src/commandHandlers/commit/gitCherryPick.ts b/src/commandHandlers/commit/gitCherryPick.ts index b5894f8e..a6ca99e9 100644 --- a/src/commandHandlers/commit/gitCherryPick.ts +++ b/src/commandHandlers/commit/gitCherryPick.ts @@ -11,7 +11,8 @@ import { IGitCherryPickCommandHandler } from '../types'; export class GitCherryPickCommandHandler implements IGitCherryPickCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} @@ -25,7 +26,9 @@ export class GitCherryPickCommandHandler implements IGitCherryPickCommandHandler const msg = `Cherry pick ${commit.logEntry.hash.short} into ${currentBranch}?`; const yesNo = showPrompt - ? await this.applicationShell.showQuickPick(['Yes', 'No'], { placeHolder: msg }) + ? await this.applicationShell.showQuickPick(['Yes', 'No'], { + placeHolder: msg, + }) : 'Yes'; if (yesNo === undefined || yesNo === 'No') { diff --git a/src/commandHandlers/commit/gitCommit.ts b/src/commandHandlers/commit/gitCommit.ts index 60c1168e..e77819b3 100644 --- a/src/commandHandlers/commit/gitCommit.ts +++ b/src/commandHandlers/commit/gitCommit.ts @@ -11,7 +11,8 @@ import { IGitCommitCommandHandler } from '../types'; export class GitCommitCommandHandler implements IGitCommitCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} @@ -30,7 +31,10 @@ export class GitCommitCommandHandler implements IGitCommitCommandHandler { const description = 'Please provide a tag name'; newTagName = typeof newTagName !== 'string' || newTagName.trim().length === 0 - ? await this.applicationShell.showInputBox({ placeHolder: msg, prompt: description }) + ? await this.applicationShell.showInputBox({ + placeHolder: msg, + prompt: description, + }) : newTagName; if (typeof newTagName !== 'string' || newTagName.length === 0) { @@ -50,7 +54,10 @@ export class GitCommitCommandHandler implements IGitCommitCommandHandler { const description = 'Please provide a branch name'; newBranchName = typeof newBranchName !== 'string' || newBranchName.trim().length === 0 - ? await this.applicationShell.showInputBox({ placeHolder: msg, prompt: description }) + ? await this.applicationShell.showInputBox({ + placeHolder: msg, + prompt: description, + }) : newBranchName; if (typeof newBranchName !== 'string' || newBranchName.length === 0) { diff --git a/src/commandHandlers/commit/gitMerge.ts b/src/commandHandlers/commit/gitMerge.ts index df331ae4..665283d3 100644 --- a/src/commandHandlers/commit/gitMerge.ts +++ b/src/commandHandlers/commit/gitMerge.ts @@ -11,7 +11,8 @@ import { IGitMergeCommandHandler } from '../types'; export class GitMergeCommandHandler implements IGitMergeCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} @@ -45,7 +46,9 @@ export class GitMergeCommandHandler implements IGitMergeCommandHandler { const msg = `Merge ${type} '${rev}' into ${currentBranch}?`; const yesNo = showPrompt - ? await this.applicationShell.showQuickPick(['Yes', 'No'], { placeHolder: msg }) + ? await this.applicationShell.showQuickPick(['Yes', 'No'], { + placeHolder: msg, + }) : 'Yes'; if (yesNo === undefined || yesNo === 'No') { diff --git a/src/commandHandlers/commit/gitRebase.ts b/src/commandHandlers/commit/gitRebase.ts index 589c710b..0b19559f 100644 --- a/src/commandHandlers/commit/gitRebase.ts +++ b/src/commandHandlers/commit/gitRebase.ts @@ -11,7 +11,8 @@ import { IGitRebaseCommandHandler } from '../types'; export class GitRebaseCommandHandler implements IGitRebaseCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} @@ -25,7 +26,9 @@ export class GitRebaseCommandHandler implements IGitRebaseCommandHandler { const msg = `Rebase ${currentBranch} onto '${commit.logEntry.hash.short}'?`; const yesNo = showPrompt - ? await this.applicationShell.showQuickPick(['Yes', 'No'], { placeHolder: msg }) + ? await this.applicationShell.showQuickPick(['Yes', 'No'], { + placeHolder: msg, + }) : 'Yes'; if (yesNo === undefined || yesNo === 'No') { diff --git a/src/commandHandlers/commit/revert.ts b/src/commandHandlers/commit/revert.ts index 617945d7..0d8c6d7c 100644 --- a/src/commandHandlers/commit/revert.ts +++ b/src/commandHandlers/commit/revert.ts @@ -11,7 +11,8 @@ import { IGitRevertCommandHandler } from '../types'; export class GitRevertCommandHandler implements IGitRevertCommandHandler { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ICommitViewerFactory) private commitViewerFactory: ICommitViewerFactory, + @inject(ICommitViewerFactory) + private commitViewerFactory: ICommitViewerFactory, @inject(IApplicationShell) private applicationShell: IApplicationShell, ) {} @@ -24,7 +25,9 @@ export class GitRevertCommandHandler implements IGitRevertCommandHandler { const msg = `Are you sure you want to revert this '${commit.logEntry.hash.short}' commit?`; const yesNo = showPrompt - ? await this.applicationShell.showQuickPick(['Yes', 'No'], { placeHolder: msg }) + ? await this.applicationShell.showQuickPick(['Yes', 'No'], { + placeHolder: msg, + }) : 'Yes'; if (yesNo === undefined || yesNo === 'No') { diff --git a/src/commandHandlers/fileCommit/fileHistory.ts b/src/commandHandlers/fileCommit/fileHistory.ts index 8a929046..7731c46e 100644 --- a/src/commandHandlers/fileCommit/fileHistory.ts +++ b/src/commandHandlers/fileCommit/fileHistory.ts @@ -18,7 +18,7 @@ export class GitFileHistoryCommandHandler implements IGitFileHistoryCommandHandl @inject(ICommandManager) private commandManager: ICommandManager, @inject(IApplicationShell) private applicationShell: IApplicationShell, @inject(IFileSystem) private fileSystem: IFileSystem, - ) {} + ) { } @command('git.commit.FileEntry.ViewFileContents', IGitFileHistoryCommandHandler) public async viewFile(nodeOrFileCommit: FileNode | FileCommitDetails): Promise { @@ -60,7 +60,9 @@ export class GitFileHistoryCommandHandler implements IGitFileHistoryCommandHandl Uri.file(tmpFile.fsPath), Uri.file(fileCommit.committedFile.uri.path), title, - { preview: true }, + { + preview: true, + }, ); } @@ -150,8 +152,14 @@ export class GitFileHistoryCommandHandler implements IGitFileHistoryCommandHandl const [leftFile, rightFile] = await Promise.all([leftFilePromise, rightFilePromise]); const title = this.getComparisonTitle( - { file: Uri.file(fileCommit.committedFile.uri.path), hash: fileCommit.logEntry.hash }, - { file: Uri.file(fileCommit.committedFile.uri.path), hash: fileCommit.rightCommit.logEntry.hash }, + { + file: Uri.file(fileCommit.committedFile.uri.path), + hash: fileCommit.logEntry.hash, + }, + { + file: Uri.file(fileCommit.committedFile.uri.path), + hash: fileCommit.rightCommit.logEntry.hash, + }, ); await this.commandManager.executeCommand('vscode.diff', leftFile, rightFile, title, { preview: true }); diff --git a/src/commandHandlers/handlerManager.ts b/src/commandHandlers/handlerManager.ts index 18a90582..a9be4cef 100644 --- a/src/commandHandlers/handlerManager.ts +++ b/src/commandHandlers/handlerManager.ts @@ -10,7 +10,8 @@ import { StopWatch } from '../common/stopWatch'; @injectable() export class CommandHandlerManager implements ICommandHandlerManager { constructor( - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, + @inject(IDisposableRegistry) + private disposableRegistry: IDisposableRegistry, @inject(ICommandManager) private commandManager: ICommandManager, @inject(IServiceContainer) private serviceContainer: IServiceContainer, ) {} diff --git a/src/commandHandlers/ref/gitRef.ts b/src/commandHandlers/ref/gitRef.ts index 010d4928..e5a181be 100644 --- a/src/commandHandlers/ref/gitRef.ts +++ b/src/commandHandlers/ref/gitRef.ts @@ -20,7 +20,10 @@ export class GitRefCommandHandler implements IGitRefCommandHandler { tagName = typeof tagName !== 'string' || tagName.trim().length === 0 - ? await this.applicationShell.showInputBox({ placeHolder: msg, prompt: description }) + ? await this.applicationShell.showInputBox({ + placeHolder: msg, + prompt: description, + }) : tagName; if (typeof tagName !== 'string' || tagName.length === 0) { @@ -42,7 +45,10 @@ export class GitRefCommandHandler implements IGitRefCommandHandler { branchName = typeof branchName !== 'string' || branchName.trim().length === 0 - ? await this.applicationShell.showInputBox({ placeHolder: msg, prompt: description }) + ? await this.applicationShell.showInputBox({ + placeHolder: msg, + prompt: description, + }) : branchName; if (typeof branchName !== 'string' || branchName.length === 0) { @@ -64,7 +70,10 @@ export class GitRefCommandHandler implements IGitRefCommandHandler { remoteName = typeof remoteName !== 'string' || remoteName.trim().length === 0 - ? await this.applicationShell.showInputBox({ placeHolder: msg, prompt: description }) + ? await this.applicationShell.showInputBox({ + placeHolder: msg, + prompt: description, + }) : remoteName; if (typeof remoteName !== 'string' || remoteName.length === 0) { diff --git a/src/common/helpers.ts b/src/common/helpers.ts index f7917745..c8efb5d3 100644 --- a/src/common/helpers.ts +++ b/src/common/helpers.ts @@ -14,7 +14,10 @@ export async function createTemporaryFile( if (err) { return reject(err); } - resolve({ filePath: tmpFile, cleanupCallback: cleanupCallback }); + resolve({ + filePath: tmpFile, + cleanupCallback: cleanupCallback, + }); }); }); } diff --git a/src/common/uiService.ts b/src/common/uiService.ts index 5f3fb38e..2da912d1 100644 --- a/src/common/uiService.ts +++ b/src/common/uiService.ts @@ -41,7 +41,11 @@ export class UiService implements IUiService { const commands = await this.serviceContainer .get(IFileCommitCommandFactory) .createCommands(fileCommit); - const options = { matchOnDescription: true, matchOnDetail: true, token: this.selectionActionToken.token }; + const options = { + matchOnDescription: true, + matchOnDetail: true, + token: this.selectionActionToken.token, + }; return this.application.showQuickPick(commands, options); } @@ -53,7 +57,11 @@ export class UiService implements IUiService { const commands = await this.serviceContainer .get(ICommitCommandFactory) .createCommands(commit); - const options = { matchOnDescription: true, matchOnDetail: true, token: this.selectionActionToken.token }; + const options = { + matchOnDescription: true, + matchOnDetail: true, + token: this.selectionActionToken.token, + }; return this.application.showQuickPick(commands, options); } diff --git a/src/nodes/nodeBuilder.ts b/src/nodes/nodeBuilder.ts index e1327c68..a0fd414d 100644 --- a/src/nodes/nodeBuilder.ts +++ b/src/nodes/nodeBuilder.ts @@ -12,7 +12,8 @@ import { DirectoryNode, FileNode, INodeBuilder, INodeFactory } from './types'; @injectable() export class NodeBuilder implements INodeBuilder { constructor( - @inject(IFileCommitCommandFactory) private fileCommandFactory: IFileCommitCommandFactory, + @inject(IFileCommitCommandFactory) + private fileCommandFactory: IFileCommitCommandFactory, private nodeFactory: INodeFactory, @inject(IPlatformService) private platform: IPlatformService, ) {} diff --git a/src/server/apiController.ts b/src/server/apiController.ts index 05a6959e..27c7f436 100644 --- a/src/server/apiController.ts +++ b/src/server/apiController.ts @@ -1,4 +1,4 @@ -import { Uri, Webview } from 'vscode'; +import { Uri, WebviewPanel, Disposable } from 'vscode'; import { IAvatarProvider } from '../adapter/avatar/types'; import { GitOriginType } from '../adapter/repository/index'; import { IApplicationShell } from '../application/types'; @@ -9,21 +9,40 @@ import { IServiceContainer } from '../ioc/types'; import { Avatar, BranchSelection, CommittedFile, IGitService, IPostMessage, LogEntry, Ref, RefType } from '../types'; import { captureTelemetry } from '../common/telemetry'; -export class ApiController { +export class ApiController extends Disposable { + private readonly disposable: Disposable[] = []; private readonly commitViewer: IGitCommitViewDetailsCommandHandler; private readonly applicationShell: IApplicationShell; + private stateRequestId = ''; + constructor( - private webview: Webview, + private webviewPanel: WebviewPanel, private gitService: IGitService, private serviceContainer: IServiceContainer, private commandManager: ICommandManager, ) { + super(() => this.dispose()); + this.commitViewer = this.serviceContainer.get( IGitCommitViewDetailsCommandHandler, ); this.applicationShell = this.serviceContainer.get(IApplicationShell); - this.webview.onDidReceiveMessage(this.postMessageParser.bind(this)); + this.webviewPanel.webview.onDidReceiveMessage(this.postMessageParser.bind(this), null, this.disposable); + this.gitService.onStateChanged( + () => { + this.postMessageParser({ + cmd: 'sendState', + requestId: this.stateRequestId, + payload: {}, + }); + }, + null, + this.disposable, + ); + } + public getWebviewPanel() { + return this.webviewPanel; } public async getLogEntries(args: any) { @@ -81,7 +100,7 @@ export class ApiController { public async getAvatars() { const originType = await this.gitService.getOriginType(); if (!originType) { - this.webview.postMessage({ + this.webviewPanel.webview.postMessage({ cmd: 'getAvatarsResult', error: 'No origin type found', }); @@ -196,19 +215,31 @@ export class ApiController { return committedFile; } + public async registerState(args: any) { + this.stateRequestId = args.requestId; + } + + public async sendState(args: any) { + return args; + } + private postMessageParser = async (message: IPostMessage) => { try { const result = await this[message.cmd].bind(this)(message.payload); - this.webview.postMessage({ + this.webviewPanel.webview.postMessage({ requestId: message.requestId, payload: result, }); } catch (ex) { this.applicationShell.showErrorMessage(ex); - this.webview.postMessage({ + this.webviewPanel.webview.postMessage({ requestId: message.requestId, error: ex, }); } }; + + public dispose() { + this.disposable.forEach(disposable => disposable.dispose()); + } } diff --git a/src/server/htmlViewer.ts b/src/server/htmlViewer.ts index 813fc31e..ef7d1a34 100644 --- a/src/server/htmlViewer.ts +++ b/src/server/htmlViewer.ts @@ -1,7 +1,7 @@ import { inject } from 'inversify'; import * as path from 'path'; import * as querystring from 'query-string'; -import { Disposable, env, Uri, ViewColumn, Webview, WebviewPanel, workspace } from 'vscode'; +import { Disposable, env, Uri, ViewColumn, Webview, workspace } from 'vscode'; import { window } from 'vscode'; import { ICommandManager } from '../application/types'; import { IServiceContainer } from '../ioc/types'; @@ -11,13 +11,14 @@ import { ApiController } from './apiController'; export class HtmlViewer { private readonly disposable: Disposable[] = []; private readonly commandManager: ICommandManager; - private readonly htmlView: Map; + private readonly htmlView: Map; constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IGitServiceFactory) private gitServiceFactory: IGitServiceFactory, + @inject(IGitServiceFactory) + private gitServiceFactory: IGitServiceFactory, private extensionPath: string, ) { - this.htmlView = new Map(); + this.htmlView = new Map(); this.commandManager = serviceContainer.get(ICommandManager); this.disposable.push(this.commandManager.registerCommand('previewHtml', this.onPreviewHtml)); } @@ -32,11 +33,8 @@ export class HtmlViewer { if (this.htmlView.has(uri.toString())) { // skip recreating a webview, when already exist // and reveal it in tab view - const webviewPanel = this.htmlView.get(uri.toString()); - if (webviewPanel) { - webviewPanel.reveal(); - } - + const apiController = this.htmlView.get(uri.toString()); + apiController?.getWebviewPanel().reveal(); return; } @@ -49,14 +47,17 @@ export class HtmlViewer { enableScripts: true, retainContextWhenHidden: true, }); + webviewPanel.iconPath = Uri.file(`${this.extensionPath}/images/icon.png`); - this.htmlView.set(uri.toString(), webviewPanel); const gitService = this.gitServiceFactory.getService(id); - new ApiController(webviewPanel.webview, gitService, this.serviceContainer, this.commandManager); + const apiController = new ApiController(webviewPanel, gitService, this.serviceContainer, this.commandManager); + + this.htmlView.set(uri.toString(), apiController); webviewPanel.onDidDispose(() => { if (this.htmlView.has(uri.toString())) { + this.htmlView.get(uri.toString())?.dispose(); this.htmlView.delete(uri.toString()); } }); @@ -95,9 +96,9 @@ export class HtmlViewer { + webview, + 'dist/browser/bundle.css', + )}' /> Git History