From 87e11f939a8f2de26d6c2a6a76f8dba9fbd32101 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 15:56:04 -0800 Subject: [PATCH 001/126] Add Git branch icons --- style/images/git-branch-white.svg | 7 +++++++ style/images/git-branch.svg | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 style/images/git-branch-white.svg create mode 100644 style/images/git-branch.svg diff --git a/style/images/git-branch-white.svg b/style/images/git-branch-white.svg new file mode 100644 index 000000000..ee853f3e5 --- /dev/null +++ b/style/images/git-branch-white.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/style/images/git-branch.svg b/style/images/git-branch.svg new file mode 100644 index 000000000..81508b9cc --- /dev/null +++ b/style/images/git-branch.svg @@ -0,0 +1,3 @@ + + + From 8c0e5a2e6499f497ead8fb13d838bc959a676fad Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 16:03:15 -0800 Subject: [PATCH 002/126] Adjust icons --- style/images/git-branch-white.svg | 8 ++------ style/images/git-branch.svg | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/style/images/git-branch-white.svg b/style/images/git-branch-white.svg index ee853f3e5..9bc29e5aa 100644 --- a/style/images/git-branch-white.svg +++ b/style/images/git-branch-white.svg @@ -1,7 +1,3 @@ - - - - - - + + diff --git a/style/images/git-branch.svg b/style/images/git-branch.svg index 81508b9cc..386a5fb87 100644 --- a/style/images/git-branch.svg +++ b/style/images/git-branch.svg @@ -1,3 +1,3 @@ - - + + From 37ac57bafb7b954c0d00a9671c211366aa8439e8 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 16:05:36 -0800 Subject: [PATCH 003/126] Add icon variables and rearrange variables in alphabetical order --- style/variables.css | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/style/variables.css b/style/variables.css index 1b7d3fef0..fc4c39421 100644 --- a/style/variables.css +++ b/style/variables.css @@ -4,38 +4,40 @@ |----------------------------------------------------------------------------*/ :root { - --jp-icon-discard-file-selected: url('./images/discard-selected.svg'); - --jp-icon-move-file-down-hover: url('./images/move-file-down-hover.svg'); - --jp-icon-move-file-up-hover: url('./images/move-file-up-hover.svg'); --jp-checkmark: url('./images/checkmark-icon.svg'); - --jp-icon-clear-white: url('./images/clear-white.svg'); - --jp-icon-git-clone: url('./images/git-clone-icon.svg'); - --jp-git-diff-deleted-color: rgba(255, 0, 0, 0.2); --jp-git-diff-added-color: rgba(155, 185, 85, 0.2); - --jp-git-diff-output-color: rgba(0, 141, 255, 0.3); + --jp-git-diff-deleted-color: rgba(255, 0, 0, 0.2); --jp-git-diff-output-border-color: rgba(0, 141, 255, 0.7); + --jp-git-diff-output-color: rgba(0, 141, 255, 0.3); + --jp-icon-clear-white: url('./images/clear-white.svg'); + --jp-icon-discard-file-selected: url('./images/discard-selected.svg'); + --jp-icon-git-clone: url('./images/git-clone-icon.svg'); + --jp-icon-move-file-down-hover: url('./images/move-file-down-hover.svg'); + --jp-icon-move-file-up-hover: url('./images/move-file-up-hover.svg'); } [data-jp-theme-light='true'] { --jp-icon-arrow-down: url('./images/arrow-down.svg'); + --jp-icon-diff: url('./images/diff-hover.svg'); --jp-icon-discard-file: url('./images/discard.svg'); + --jp-icon-git-branch: url('./images/git-branch.svg'); --jp-icon-git-pull: url('./images/git-pull.svg'); --jp-icon-git-push: url('./images/git-push.svg'); --jp-icon-move-file-down: url('./images/move-file-down.svg'); --jp-icon-move-file-up: url('./images/move-file-up.svg'); --jp-icon-plus: url('./images/plus.svg'); --jp-icon-rewind: url('./images/rewind.svg'); - --jp-icon-diff: url('./images/diff-hover.svg'); } [data-jp-theme-light='false'] { --jp-icon-arrow-down: url('./images/arrow-down-white.svg'); + --jp-icon-diff: url('./images/diff-hover-dk.svg'); --jp-icon-discard-file: url('./images/discard-selected.svg'); + --jp-icon-git-branch: url('./images/git-branch-white.svg'); --jp-icon-git-pull: url('./images/git-pull-white.svg'); --jp-icon-git-push: url('./images/git-push-white.svg'); --jp-icon-move-file-down: url('./images/move-file-down-hover.svg'); --jp-icon-move-file-up: url('./images/move-file-up-hover.svg'); --jp-icon-plus: url('./images/plus-white.svg'); --jp-icon-rewind: url('./images/rewind-white.svg'); - --jp-icon-diff: url('./images/diff-hover-dk.svg'); } From 2f5d676bf78550cacf95313f0e45b51078568322 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 17:47:06 -0800 Subject: [PATCH 004/126] Rename file --- src/style/{PathHeaderStyle.ts => PathHeader.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/style/{PathHeaderStyle.ts => PathHeader.ts} (100%) diff --git a/src/style/PathHeaderStyle.ts b/src/style/PathHeader.ts similarity index 100% rename from src/style/PathHeaderStyle.ts rename to src/style/PathHeader.ts From e7eee3a618feab75e7bd6aa49ba6fce193b8b548 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 17:47:44 -0800 Subject: [PATCH 005/126] Fix paths --- src/components/PathHeader.tsx | 2 +- tests/test-components/PathHeader.spec.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PathHeader.tsx b/src/components/PathHeader.tsx index 1451b194e..0e6dc8c5f 100644 --- a/src/components/PathHeader.tsx +++ b/src/components/PathHeader.tsx @@ -8,7 +8,7 @@ import { repoPathStyle, repoRefreshStyle, repoStyle -} from '../style/PathHeaderStyle'; +} from '../style/PathHeader'; import { GitCredentialsForm } from '../widgets/CredentialsBox'; import { GitPullPushDialog, Operation } from '../widgets/gitPushPull'; import { IGitExtension } from '../tokens'; diff --git a/tests/test-components/PathHeader.spec.tsx b/tests/test-components/PathHeader.spec.tsx index 550e94c41..15b106247 100644 --- a/tests/test-components/PathHeader.spec.tsx +++ b/tests/test-components/PathHeader.spec.tsx @@ -5,7 +5,7 @@ import { gitPullStyle, gitPushStyle, repoRefreshStyle -} from '../../src/style/PathHeaderStyle'; +} from '../../src/style/PathHeader'; import 'jest'; import { GitExtension } from '../../src/model'; import * as git from '../../src/git'; From 58af80646dc6aaab3e81a99846d36fd07fb69daa Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 18:00:17 -0800 Subject: [PATCH 006/126] Rename component and associated files --- src/components/GitPanel.tsx | 4 +- .../{PathHeader.tsx => Toolbar.tsx} | 52 ++++++++++++++----- src/style/{PathHeader.ts => Toolbar.ts} | 4 +- .../{PathHeader.spec.tsx => Toolbar.spec.tsx} | 12 ++--- 4 files changed, 50 insertions(+), 22 deletions(-) rename src/components/{PathHeader.tsx => Toolbar.tsx} (76%) rename src/style/{PathHeader.ts => Toolbar.ts} (96%) rename tests/test-components/{PathHeader.spec.tsx => Toolbar.spec.tsx} (89%) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 2046a67ff..5d5055bf7 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -12,7 +12,7 @@ import { decodeStage } from '../utils'; import { BranchHeader } from './BranchHeader'; import { FileList } from './FileList'; import { HistorySideBar } from './HistorySideBar'; -import { PathHeader } from './PathHeader'; +import { Toolbar } from './Toolbar'; /** Interface for GitPanel component state */ export interface IGitSessionNodeState { @@ -228,7 +228,7 @@ export class GitPanel extends React.Component< return (
- { await this.refreshBranch(); diff --git a/src/components/PathHeader.tsx b/src/components/Toolbar.tsx similarity index 76% rename from src/components/PathHeader.tsx rename to src/components/Toolbar.tsx index 0e6dc8c5f..a51c73c09 100644 --- a/src/components/PathHeader.tsx +++ b/src/components/Toolbar.tsx @@ -5,27 +5,43 @@ import { classes } from 'typestyle'; import { gitPullStyle, gitPushStyle, - repoPathStyle, + repoPathClass, repoRefreshStyle, - repoStyle -} from '../style/PathHeader'; + pathHeaderClass +} from '../style/Toolbar'; import { GitCredentialsForm } from '../widgets/CredentialsBox'; import { GitPullPushDialog, Operation } from '../widgets/gitPushPull'; import { IGitExtension } from '../tokens'; -export interface IPathHeaderProps { +/** + * Interface describing component properties. + */ +export interface IToolbarProps { + /** + * Git extension data model. + */ model: IGitExtension; + + /** + * Callback to invoke in order to refresh a repository. + * + * @returns promise which refreshes a repository + */ + refresh: () => Promise; } -export class PathHeader extends React.Component { - constructor(props: IPathHeaderProps) { +/** + * React component for rendering a panel toolbar. + */ +export class Toolbar extends React.Component { + constructor(props: IToolbarProps) { super(props); } render() { return ( -
+
{ newValue: this.props.model.pathRepository }} > - {(_, change) => ( - - {PathExt.basename(change.newValue || '')} - - )} + {this._onRepositoryPathChange}
); @@ -90,12 +85,11 @@ export class Toolbar extends React.Component { /** * Callback invoked upon a change to the repository path. * - * @private - * @param _ - event object - * @param change - change + * @param _ - unused argument + * @param change - change object * @returns React component */ - private _onRepositoryPathChange = (_, change) => { + private _onRepositoryPathChange = (_: any, change: any) => { return ( {PathExt.basename(change.newValue || '')} @@ -104,24 +98,58 @@ export class Toolbar extends React.Component { }; /** - * Displays the error dialog when the Git Push/Pull operation fails. - * @param title the title of the error dialog - * @param body the message to be shown in the body of the modal. + * Callback invoked upon clicking a button to pull the latest changes. + * + * @param event - event object + */ + private _onPullClick = () => { + this._showDialog(Operation.Pull).catch(reason => { + console.error( + `Encountered an error when pulling changes. Error: ${reason}` + ); + }); + }; + + /** + * Callback invoked upon clicking a button to push the latest changes. + * + * @param event - event object + */ + private _onPushClick = () => { + this._showDialog(Operation.Push).catch(reason => { + console.error( + `Encountered an error when pushing changes. Error: ${reason}` + ); + }); + }; + + /** + * Callback invoked upon clicking a button to refresh a repository. + * + * @param event - event object + */ + private _onRefreshClick = () => { + this.props.refresh(); + }; + + /** + * Displays an error dialog when a Git operation fails. + * + * @param operation - Git operation name + * @returns Promise for displaying a dialog */ - private async showGitPushPullDialog( - model: IGitExtension, - operation: Operation - ): Promise { + private async _showDialog(operation: Operation): Promise { + const title = `Git ${operation}`; let result = await showDialog({ - title: `Git ${operation}`, - body: new GitPullPushDialog(model, operation), + title: title, + body: new GitPullPushDialog(this.props.model, operation), buttons: [Dialog.okButton({ label: 'DISMISS' })] }); let retry = false; while (!result.button.accept) { retry = true; - let response = await showDialog({ + const response = await showDialog({ title: 'Git credentials required', body: new GitCredentialsForm( 'Enter credentials for remote repository', @@ -131,10 +159,13 @@ export class Toolbar extends React.Component { }); if (response.button.accept) { - // user accepted attempt to login result = await showDialog({ - title: `Git ${operation}`, - body: new GitPullPushDialog(model, operation, response.value), + title: title, + body: new GitPullPushDialog( + this.props.model, + operation, + response.value + ), buttons: [Dialog.okButton({ label: 'DISMISS' })] }); } else { From 2527a7e074ae4eb8c322c80d7a108db7e30ac1c7 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 18:32:04 -0800 Subject: [PATCH 008/126] Remove empty lines --- src/components/Toolbar.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 4710c2a14..158e1f939 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -27,7 +27,6 @@ export interface IToolbarProps { * * @returns promise which refreshes a repository */ - refresh: () => Promise; } @@ -148,7 +147,6 @@ export class Toolbar extends React.Component { let retry = false; while (!result.button.accept) { retry = true; - const response = await showDialog({ title: 'Git credentials required', body: new GitCredentialsForm( @@ -157,7 +155,6 @@ export class Toolbar extends React.Component { ), buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })] }); - if (response.button.accept) { result = await showDialog({ title: title, From f957b78c8f48878e2d0aa62ee65067106bad84dc Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Wed, 1 Jan 2020 18:35:55 -0800 Subject: [PATCH 009/126] Rename exports --- src/components/Toolbar.tsx | 12 ++++++------ src/style/Toolbar.ts | 6 +++--- tests/test-components/Toolbar.spec.tsx | 20 ++++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 158e1f939..c99a2b26d 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -3,10 +3,10 @@ import { PathExt } from '@jupyterlab/coreutils'; import * as React from 'react'; import { classes } from 'typestyle'; import { - gitPullStyle, - gitPushStyle, + pullButtonClass, + pushButtonClass, + refreshButtonClass, repoPathClass, - repoRefreshStyle, toolbarClass } from '../style/Toolbar'; import { GitCredentialsForm } from '../widgets/CredentialsBox'; @@ -63,17 +63,17 @@ export class Toolbar extends React.Component { {this._onRepositoryPathChange}
); } @@ -190,7 +193,7 @@ export class Toolbar extends React.Component { let retry = false; while (!result.button.accept) { retry = true; - const response = await showDialog({ + const credentials = await showDialog({ title: 'Git credentials required', body: new GitCredentialsForm( 'Enter credentials for remote repository', @@ -198,19 +201,18 @@ export class Toolbar extends React.Component { ), buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })] }); - if (response.button.accept) { - result = await showDialog({ - title: title, - body: new GitPullPushDialog( - this.props.model, - operation, - response.value - ), - buttons: [Dialog.okButton({ label: 'DISMISS' })] - }); - } else { + if (!credentials.button.accept) { break; } + result = await showDialog({ + title: title, + body: new GitPullPushDialog( + this.props.model, + operation, + credentials.value + ), + buttons: [Dialog.okButton({ label: 'DISMISS' })] + }); } } } From 462f3d924f8022db3027253e931e44cbc5291d61 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Thu, 2 Jan 2020 14:55:41 -0800 Subject: [PATCH 017/126] Stub BranchMenu component --- src/components/BranchMenu.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/components/BranchMenu.tsx diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx new file mode 100644 index 000000000..6ba2ca1a0 --- /dev/null +++ b/src/components/BranchMenu.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; + +/** + * Interface describing component properties. + */ +export interface IBranchMenuProps {} + +/** + * Interface describing component state. + */ +export interface IBranchMenuState {} + +/** + * React component for rendering a branch menu. + */ +export class BranchMenu extends React.Component< + IBranchMenuProps, + IBranchMenuState +> { + /** + * Returns a React component for rendering a branch menu. + * + * @param props - component properties + * @returns React component + */ + constructor(props: IBranchMenuProps) { + super(props); + } +} From 072b0331eb6960841cba29392a4c75214c5c8cbb Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Thu, 2 Jan 2020 14:58:22 -0800 Subject: [PATCH 018/126] Add render method --- src/components/BranchMenu.tsx | 9 +++++++++ src/components/Toolbar.tsx | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 6ba2ca1a0..2250e6533 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -26,4 +26,13 @@ export class BranchMenu extends React.Component< constructor(props: IBranchMenuProps) { super(props); } + + /** + * Renders the component. + * + * @returns fragment + */ + render() { + return ; + } } diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 57097653d..3a7887bdd 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -14,6 +14,7 @@ import { import { GitCredentialsForm } from '../widgets/CredentialsBox'; import { GitPullPushDialog, Operation } from '../widgets/gitPushPull'; import { IGitExtension } from '../tokens'; +import { BranchMenu } from './BranchMenu'; /** * Interface describing component properties. @@ -105,9 +106,7 @@ export class Toolbar extends React.Component { title={'Refresh the repository to detect local and remote changes'} onClick={this._onRefreshClick} /> - {this.state.branchMenu - ? null // TODO - : null} + {this.state.branchMenu ? : null}
); } From c15546c93d6befb318d4105af2df002cebc63c72 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Thu, 2 Jan 2020 15:03:12 -0800 Subject: [PATCH 019/126] Bind the extension model --- src/components/BranchMenu.tsx | 8 +++++++- src/components/Toolbar.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 2250e6533..1e9b93395 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -1,9 +1,15 @@ import * as React from 'react'; +import { IGitExtension } from '../tokens'; /** * Interface describing component properties. */ -export interface IBranchMenuProps {} +export interface IBranchMenuProps { + /** + * Git extension data model. + */ + model: IGitExtension; +} /** * Interface describing component state. diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 3a7887bdd..d3950ef16 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -106,7 +106,7 @@ export class Toolbar extends React.Component { title={'Refresh the repository to detect local and remote changes'} onClick={this._onRefreshClick} /> - {this.state.branchMenu ? : null} + {this.state.branchMenu ? : null} ); } From 4286a95390e9710f811499a9fc3fbfed86f031a5 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Thu, 2 Jan 2020 15:13:35 -0800 Subject: [PATCH 020/126] Listen to change event directly --- src/components/Toolbar.tsx | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index d3950ef16..7c0e0c330 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -1,4 +1,4 @@ -import { Dialog, showDialog, UseSignal } from '@jupyterlab/apputils'; +import { Dialog, showDialog } from '@jupyterlab/apputils'; import { PathExt } from '@jupyterlab/coreutils'; import * as React from 'react'; import { classes } from 'typestyle'; @@ -41,6 +41,11 @@ export interface IToolbarState { * Boolean indicating whether a branch menu is shown. */ branchMenu: boolean; + + /** + * Current repository path. + */ + repository: string; } /** @@ -55,8 +60,10 @@ export class Toolbar extends React.Component { */ constructor(props: IToolbarProps) { super(props); + this.props.model.repositoryChanged.connect(this._onRepositoryChange); this.state = { - branchMenu: false + branchMenu: false, + repository: '' }; } @@ -68,16 +75,12 @@ export class Toolbar extends React.Component { render() { return (
- - {this._onRepositoryPathChange} - + {PathExt.basename(this.state.repository)} +
+
+ + {this.state.repoMenu ? null : null} +
+
+ + {this.state.branchMenu ? ( + + ) : null} +
); } /** * Callback invoked upon a change to the repository path. - * - * @param _ - unused argument - * @param change - event object - * @returns React component */ - private _onRepositoryChange = (_: any, change: any) => { - this.setState({ - repository: change.newValue || '' - }); + private _onRepositoryChange = () => { + this.forceUpdate(); + }; + + /** + * Callback invoked upon a change to the current HEAD. + */ + private _onHeadChange = () => { + this.forceUpdate(); }; /** @@ -133,7 +207,7 @@ export class Toolbar extends React.Component { * @param event - event object */ private _onPullClick = () => { - this._showDialog(Operation.Pull).catch(reason => { + showGitOperationDialog(this.props.model, Operation.Pull).catch(reason => { console.error( `Encountered an error when pulling changes. Error: ${reason}` ); @@ -146,13 +220,25 @@ export class Toolbar extends React.Component { * @param event - event object */ private _onPushClick = () => { - this._showDialog(Operation.Push).catch(reason => { + showGitOperationDialog(this.props.model, Operation.Push).catch(reason => { console.error( `Encountered an error when pushing changes. Error: ${reason}` ); }); }; + /** + * Callback invoked upon clicking a button to change the current repository. + * + * @param event - event object + */ + private _onRepositoryClick = () => { + // Toggle the repository menu: + this.setState({ + repoMenu: !this.state.repoMenu + }); + }; + /** * Callback invoked upon clicking a button to change the current branch. * @@ -173,43 +259,4 @@ export class Toolbar extends React.Component { private _onRefreshClick = () => { this.props.refresh(); }; - - /** - * Displays an error dialog when a Git operation fails. - * - * @param operation - Git operation name - * @returns Promise for displaying a dialog - */ - private async _showDialog(operation: Operation): Promise { - const title = `Git ${operation}`; - let result = await showDialog({ - title: title, - body: new GitPullPushDialog(this.props.model, operation), - buttons: [Dialog.okButton({ label: 'DISMISS' })] - }); - let retry = false; - while (!result.button.accept) { - retry = true; - const credentials = await showDialog({ - title: 'Git credentials required', - body: new GitCredentialsForm( - 'Enter credentials for remote repository', - retry ? 'Incorrect username or password.' : '' - ), - buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })] - }); - if (!credentials.button.accept) { - break; - } - result = await showDialog({ - title: title, - body: new GitPullPushDialog( - this.props.model, - operation, - credentials.value - ), - buttons: [Dialog.okButton({ label: 'DISMISS' })] - }); - } - } } diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index f1c1e5363..54399c9cf 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -1,16 +1,50 @@ import { style } from 'typestyle'; export const toolbarClass = style({ + display: 'flex', + flexDirection: 'column', + + backgroundColor: 'var(--jp-layout-color1)' +}); + +export const toolbarNavClass = style({ display: 'flex', flexDirection: 'row', + flexWrap: 'wrap', minHeight: '35px', lineHeight: 'var(--jp-private-running-item-height)', - backgroundColor: 'var(--jp-layout-color1)' + backgroundColor: 'var(--jp-layout-color1)', + + borderBottomStyle: 'solid', + borderBottomWidth: 'var(--jp-border-width)', + borderBottomColor: 'var(--jp-border-color2)' +}); + +export const repoMenuWrapperClass = style({}); + +export const repoMenuButtonClass = style({ + marginRight: '4px', + paddingLeft: '4px', + + lineHeight: '33px', + verticalAlign: 'middle', + + overflow: 'hidden', + + fontSize: 'var(--jp-ui-font-size1)', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' }); -export const repoPathClass = style({ +export const repoMenuTitleClass = style({}); + +export const repoMenuCurrentClass = style({}); + +export const branchMenuWrapperClass = style({}); + +export const branchMenuButtonClass = style({ marginRight: '4px', paddingLeft: '4px', @@ -24,6 +58,10 @@ export const repoPathClass = style({ whiteSpace: 'nowrap' }); +export const branchMenuTitleClass = style({}); + +export const branchMenuCurrentClass = style({}); + export const toolbarButtonClass = style({ boxSizing: 'border-box', height: '24px', @@ -63,7 +101,7 @@ export const pushButtonClass = style({ backgroundPosition: 'center' }); -export const branchButtonClass = style({ +export const branchIconClass = style({ background: 'var(--jp-layout-color1)', backgroundImage: 'var(--jp-icon-git-branch)', backgroundSize: '16px', From e66886ae64c787f7a93efb84f52a17df71fd80a1 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 15:24:36 -0800 Subject: [PATCH 025/126] Adjust styling --- src/components/Toolbar.tsx | 32 ++++++++++------------ src/style/Toolbar.ts | 54 ++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index b02a863d4..f743c18ec 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -5,19 +5,15 @@ import { classes } from 'typestyle'; import { // NOTE: keep in alphabetical order branchIconClass, - branchMenuButtonClass, - branchMenuCurrentClass, - branchMenuTitleClass, - branchMenuWrapperClass, pullButtonClass, pushButtonClass, refreshButtonClass, - repoMenuButtonClass, - repoMenuCurrentClass, - repoMenuTitleClass, - repoMenuWrapperClass, toolbarButtonClass, toolbarClass, + toolbarMenuButtonClass, + toolbarMenuButtonSubtitleClass, + toolbarMenuButtonTitleClass, + toolbarMenuWrapperClass, toolbarNavClass } from '../style/Toolbar'; import { GitCredentialsForm } from '../widgets/CredentialsBox'; @@ -156,28 +152,28 @@ export class Toolbar extends React.Component { onClick={this._onRefreshClick} /> -
+
{this.state.repoMenu ? null : null}
-
+
{this.state.branchMenu ? ( diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 54399c9cf..5381646c0 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -22,45 +22,41 @@ export const toolbarNavClass = style({ borderBottomColor: 'var(--jp-border-color2)' }); -export const repoMenuWrapperClass = style({}); - -export const repoMenuButtonClass = style({ - marginRight: '4px', - paddingLeft: '4px', - - lineHeight: '33px', - verticalAlign: 'middle', - - overflow: 'hidden', - - fontSize: 'var(--jp-ui-font-size1)', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' +export const toolbarMenuWrapperClass = style({ + borderBottomStyle: 'solid', + borderBottomWidth: 'var(--jp-border-width)', + borderBottomColor: 'var(--jp-border-color2)' }); -export const repoMenuTitleClass = style({}); - -export const repoMenuCurrentClass = style({}); - -export const branchMenuWrapperClass = style({}); +export const toolbarMenuButtonClass = style({ + boxSizing: 'border-box', + width: '100%', -export const branchMenuButtonClass = style({ - marginRight: '4px', - paddingLeft: '4px', + padding: '4px 11px 4px', - lineHeight: '33px', - verticalAlign: 'middle', + lineHeight: '1.5em', - overflow: 'hidden', + border: 'none', + borderRadius: 0, + textAlign: 'left', fontSize: 'var(--jp-ui-font-size1)', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' + + $nest: { + '&:hover': { + backgroundColor: 'var(--jp-layout-color2)' + }, + '&:active': { + backgroundColor: 'var(--jp-layout-color3)' + } + } }); -export const branchMenuTitleClass = style({}); +export const toolbarMenuButtonTitleClass = style({}); -export const branchMenuCurrentClass = style({}); +export const toolbarMenuButtonSubtitleClass = style({ + fontWeight: 700 +}); export const toolbarButtonClass = style({ boxSizing: 'border-box', From 7abd65eaa314f54702080ac7223032c89691553f Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 15:26:37 -0800 Subject: [PATCH 026/126] Update component state when updating filter --- src/components/BranchMenu.tsx | 4 +++- src/components/Toolbar.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 51666dcfa..2853c2411 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -97,7 +97,9 @@ export class BranchMenu extends React.Component< * @param event - event object */ private _onFilterChange = (event: any) => { - console.log(event.target.value); + this.setState({ + filter: event.target.value + }); }; /** diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index f743c18ec..034c98a4d 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -155,7 +155,7 @@ export class Toolbar extends React.Component {
{this._renderItems()} @@ -110,4 +118,13 @@ export class BranchMenu extends React.Component< filter: '' }); }; + + /** + * Callback invoked upon clicking a button to create a new branch. + * + * @param event - event object + */ + private _onNewBranchClick = () => { + console.log('Create a new branch...'); + }; } diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 996aeae9d..bb070ad17 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -1,13 +1,45 @@ import { style } from 'typestyle'; -export const branchMenuWrapperClass = style({}); +export const branchMenuWrapperClass = style({ + marginTop: '6px', + marginBottom: '10px' +}); + +export const branchMenuFilterClass = style({ + padding: '4px 11px 4px' +}); + +export const branchMenuFilterInputClass = style({ + boxSizing: 'border-box', + width: 'calc(100% - 7.7em - 11px)', // full_width - button_width - right_margin + height: '2em', + + marginRight: '11px', + padding: '1px 7px 2px', -export const branchMenuFilterClass = style({}); + color: 'var(--jp-ui-font-color0)', + fontSize: 'var(--jp-ui-font-size1)', + fontWeight: 300, -export const branchMenuFilterInputClass = style({}); + borderRadius: '3px', + border: 'var(--jp-border-width) solid var(--jp-border-color2)' +}); export const branchMenuFilterClearClass = style({}); +export const branchMenuNewBranchButtonClass = style({ + boxSizing: 'border-box', + width: '7.7em', + height: '2em', + + color: 'white', + fontSize: 'var(--jp-ui-font-size1)', + + backgroundColor: 'var(--jp-brand-color1)', + border: '0', + borderRadius: '3px' +}); + export const branchMenuListWrapperClass = style({ display: 'block', width: '100%' From c70fd9a2f919d12bf1bba9cff3f2bbbbc7982437 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 17:08:22 -0800 Subject: [PATCH 028/126] Fix filter clear icon layout --- src/components/BranchMenu.tsx | 35 ++++++++++++--------- src/style/BranchMenu.ts | 59 ++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index abe103c8e..122d11a6a 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -6,6 +6,7 @@ import { branchMenuFilterClass, branchMenuFilterClearClass, branchMenuFilterInputClass, + branchMenuFilterWrapperClass, branchMenuListWrapperClass, branchMenuNewBranchButtonClass, branchMenuWrapperClass @@ -59,22 +60,26 @@ export class BranchMenu extends React.Component< render() { return (
-
- - {this.state.filter ? ( - +
+ - ) : null} + {this.state.filter ? ( + + ) : null} +
Date: Fri, 3 Jan 2020 18:24:08 -0800 Subject: [PATCH 029/126] Adjust margin --- src/style/Toolbar.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 5381646c0..5cc8e08c7 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -23,6 +23,8 @@ export const toolbarNavClass = style({ }); export const toolbarMenuWrapperClass = style({ + background: 'var(--jp-layout-color1)', + borderBottomStyle: 'solid', borderBottomWidth: 'var(--jp-border-width)', borderBottomColor: 'var(--jp-border-color2)' @@ -34,13 +36,14 @@ export const toolbarMenuButtonClass = style({ padding: '4px 11px 4px', + fontSize: 'var(--jp-ui-font-size1)', lineHeight: '1.5em', + textAlign: 'left', border: 'none', borderRadius: 0, - textAlign: 'left', - fontSize: 'var(--jp-ui-font-size1)', + background: 'var(--jp-layout-color1)', $nest: { '&:hover': { @@ -80,7 +83,7 @@ export const toolbarButtonClass = style({ }); export const pullButtonClass = style({ - margin: 'auto 0 auto auto', + marginLeft: 'auto', background: 'var(--jp-layout-color1)', backgroundImage: 'var(--jp-icon-git-pull)', @@ -106,6 +109,8 @@ export const branchIconClass = style({ }); export const refreshButtonClass = style({ + marginRight: '4px', + background: 'var(--jp-layout-color1)', backgroundImage: 'var(--jp-icon-refresh)', backgroundSize: '16px', From fafdc95d92383a02cbf9777d6319cca0c7dcbb7a Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 18:24:45 -0800 Subject: [PATCH 030/126] Add icon to each list item --- src/components/BranchMenu.tsx | 49 +++++++++++++++++++++++++++++++++-- src/style/BranchMenu.ts | 24 ++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 122d11a6a..70bce662e 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -1,12 +1,15 @@ import * as React from 'react'; import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; import ClearIcon from '@material-ui/icons/Clear'; -import { IGitExtension } from '../tokens'; +import { Git, IGitExtension } from '../tokens'; import { branchMenuFilterClass, branchMenuFilterClearClass, branchMenuFilterInputClass, branchMenuFilterWrapperClass, + branchMenuListItemClass, + branchMenuListItemIconClass, branchMenuListWrapperClass, branchMenuNewBranchButtonClass, branchMenuWrapperClass @@ -101,7 +104,28 @@ export class BranchMenu extends React.Component< * @returns fragment */ private _renderItems = () => { - return
  • ; + return this.props.model.branches.map(this._renderItem); + }; + + /** + * Renders a menu item. + * + * @param branch - branch + * @param idx - item index + * @returns fragment + */ + private _renderItem = (branch: Git.IBranch, idx: number) => { + return ( + + + {branch.name} + + ); }; /** @@ -132,4 +156,25 @@ export class BranchMenu extends React.Component< private _onNewBranchClick = () => { console.log('Create a new branch...'); }; + + /** + * Returns a callback which is invoked upon clicking a branch name. + * + * @param branch - branch name + * @returns callback + */ + private _onBranchClickFactory = (branch: string) => { + // const self = this; + return onClick; + + /** + * Callback invoked upon clicking a branch name. + * + * @private + * @param event - event object + */ + function onClick() { + console.log(branch); + } + }; } diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index b3e5447b2..615b16414 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -87,5 +87,27 @@ export const branchMenuNewBranchButtonClass = style({ export const branchMenuListWrapperClass = style({ display: 'block', - width: '100%' + width: '100%', + maxHeight: '400px', + + overflow: 'hidden', + overflowY: 'scroll' +}); + +export const branchMenuListItemClass = style({ + paddingTop: '4px', + paddingBottom: '4px', + paddingLeft: '10px' +}); + +export const branchMenuListItemIconClass = style({ + width: '16px', + height: '16px', + + marginRight: '4px', + + backgroundImage: 'var(--jp-icon-git-branch)', + backgroundSize: '16px', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center' }); From 7b0f661d04d703f20d12ae39ca25f235eb09a8ad Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 18:33:47 -0800 Subject: [PATCH 031/126] Fix font color in dark mode and adjust padding --- src/style/BranchMenu.ts | 6 +++--- src/style/Toolbar.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 615b16414..3306d37cb 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -95,9 +95,9 @@ export const branchMenuListWrapperClass = style({ }); export const branchMenuListItemClass = style({ - paddingTop: '4px', - paddingBottom: '4px', - paddingLeft: '10px' + paddingTop: '4px!important', + paddingBottom: '4px!important', + paddingLeft: '11px!important' }); export const branchMenuListItemIconClass = style({ diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 5cc8e08c7..e090bbe69 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -38,6 +38,7 @@ export const toolbarMenuButtonClass = style({ fontSize: 'var(--jp-ui-font-size1)', lineHeight: '1.5em', + color: 'var(--jp-ui-font-color0)', textAlign: 'left', border: 'none', From 97a0bd470c771fdc25bed2de87638e6361d68968 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 19:26:36 -0800 Subject: [PATCH 032/126] Add desktop icon --- style/images/desktop-white.svg | 1 + style/images/desktop.svg | 1 + style/variables.css | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 style/images/desktop-white.svg create mode 100644 style/images/desktop.svg diff --git a/style/images/desktop-white.svg b/style/images/desktop-white.svg new file mode 100644 index 000000000..7dace5079 --- /dev/null +++ b/style/images/desktop-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/images/desktop.svg b/style/images/desktop.svg new file mode 100644 index 000000000..fd943060e --- /dev/null +++ b/style/images/desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style/variables.css b/style/variables.css index fc4c39421..460eb97d7 100644 --- a/style/variables.css +++ b/style/variables.css @@ -23,6 +23,7 @@ --jp-icon-git-branch: url('./images/git-branch.svg'); --jp-icon-git-pull: url('./images/git-pull.svg'); --jp-icon-git-push: url('./images/git-push.svg'); + --jp-icon-git-repo: url('./images/desktop.svg'); --jp-icon-move-file-down: url('./images/move-file-down.svg'); --jp-icon-move-file-up: url('./images/move-file-up.svg'); --jp-icon-plus: url('./images/plus.svg'); @@ -36,6 +37,7 @@ --jp-icon-git-branch: url('./images/git-branch-white.svg'); --jp-icon-git-pull: url('./images/git-pull-white.svg'); --jp-icon-git-push: url('./images/git-push-white.svg'); + --jp-icon-git-repo: url('./images/desktop-white.svg'); --jp-icon-move-file-down: url('./images/move-file-down-hover.svg'); --jp-icon-move-file-up: url('./images/move-file-up-hover.svg'); --jp-icon-plus: url('./images/plus-white.svg'); From 0b67d59560a9229082deb6cd0db7470b772df612 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 19:26:51 -0800 Subject: [PATCH 033/126] Add icon to menu button --- src/components/Toolbar.tsx | 21 ++++++++++++++++++--- src/style/Toolbar.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 034c98a4d..3c48df337 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -1,16 +1,18 @@ -import { Dialog, showDialog } from '@jupyterlab/apputils'; -import { PathExt } from '@jupyterlab/coreutils'; import * as React from 'react'; import { classes } from 'typestyle'; +import { Dialog, showDialog } from '@jupyterlab/apputils'; +import { PathExt } from '@jupyterlab/coreutils'; import { // NOTE: keep in alphabetical order branchIconClass, pullButtonClass, pushButtonClass, refreshButtonClass, + repoIconClass, toolbarButtonClass, toolbarClass, toolbarMenuButtonClass, + toolbarMenuButtonIconClass, toolbarMenuButtonSubtitleClass, toolbarMenuButtonTitleClass, toolbarMenuWrapperClass, @@ -158,6 +160,13 @@ export class Toolbar extends React.Component { title={`Current repository: ${repo}`} onClick={this._onRepositoryClick} > +

    Current Repository

    {PathExt.basename(repo)} @@ -171,7 +180,13 @@ export class Toolbar extends React.Component { title={`Change the current branch: ${branch}`} onClick={this._onBranchClick} > - +

    Current Branch

    {branch}

    diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index e090bbe69..0009b59d0 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -32,9 +32,14 @@ export const toolbarMenuWrapperClass = style({ export const toolbarMenuButtonClass = style({ boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + flexWrap: 'wrap', + width: '100%', + height: '55px', - padding: '4px 11px 4px', + padding: '0 11px 0', fontSize: 'var(--jp-ui-font-size1)', lineHeight: '1.5em', @@ -56,9 +61,21 @@ export const toolbarMenuButtonClass = style({ } }); -export const toolbarMenuButtonTitleClass = style({}); +export const toolbarMenuButtonIconClass = style({ + width: '16px', + height: '100%', + + /* top | right | bottom | left */ + margin: 'auto 4px auto 0' +}); + +export const toolbarMenuButtonTitleClass = style({ + marginTop: 'auto' +}); export const toolbarMenuButtonSubtitleClass = style({ + marginBottom: 'auto', + fontWeight: 700 }); @@ -101,8 +118,14 @@ export const pushButtonClass = style({ backgroundPosition: 'center' }); +export const repoIconClass = style({ + backgroundImage: 'var(--jp-icon-git-repo)', + backgroundSize: '16px', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center' +}); + export const branchIconClass = style({ - background: 'var(--jp-layout-color1)', backgroundImage: 'var(--jp-icon-git-branch)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', From 6e0ca4980eec4ba222aac3c94ecf02793cf7602a Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 19:29:43 -0800 Subject: [PATCH 034/126] Adjust margin --- src/style/Toolbar.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 0009b59d0..148af3fc2 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -66,7 +66,7 @@ export const toolbarMenuButtonIconClass = style({ height: '100%', /* top | right | bottom | left */ - margin: 'auto 4px auto 0' + margin: 'auto 8px auto 0' }); export const toolbarMenuButtonTitleClass = style({ @@ -118,25 +118,25 @@ export const pushButtonClass = style({ backgroundPosition: 'center' }); -export const repoIconClass = style({ - backgroundImage: 'var(--jp-icon-git-repo)', +export const refreshButtonClass = style({ + marginRight: '4px', + + background: 'var(--jp-layout-color1)', + backgroundImage: 'var(--jp-icon-refresh)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }); -export const branchIconClass = style({ - backgroundImage: 'var(--jp-icon-git-branch)', +export const repoIconClass = style({ + backgroundImage: 'var(--jp-icon-git-repo)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }); -export const refreshButtonClass = style({ - marginRight: '4px', - - background: 'var(--jp-layout-color1)', - backgroundImage: 'var(--jp-icon-refresh)', +export const branchIconClass = style({ + backgroundImage: 'var(--jp-icon-git-branch)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' From 19451adde23c98f0ede8ce379a3df4d8b1052a5b Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 19:33:30 -0800 Subject: [PATCH 035/126] Add support for basic branch name filtering --- src/components/BranchMenu.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 70bce662e..9ad8477fb 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -115,6 +115,9 @@ export class BranchMenu extends React.Component< * @returns fragment */ private _renderItem = (branch: Git.IBranch, idx: number) => { + if (this.state.filter && !branch.name.includes(this.state.filter)) { + return null; + } return ( Date: Fri, 3 Jan 2020 19:35:11 -0800 Subject: [PATCH 036/126] Add note --- src/components/BranchMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 9ad8477fb..f3b7714f0 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -115,6 +115,7 @@ export class BranchMenu extends React.Component< * @returns fragment */ private _renderItem = (branch: Git.IBranch, idx: number) => { + // Perform a "simple" filter... (TODO: consider implementing fuzzy filtering) if (this.state.filter && !branch.name.includes(this.state.filter)) { return null; } From 0c42994ba64082913ee23751584f163d452865fa Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Fri, 3 Jan 2020 20:01:03 -0800 Subject: [PATCH 037/126] Add menu and close icons --- src/components/Toolbar.tsx | 26 ++++++++++++++++++++------ src/style/Toolbar.ts | 26 ++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 3c48df337..bafc3d587 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -5,6 +5,8 @@ import { PathExt } from '@jupyterlab/coreutils'; import { // NOTE: keep in alphabetical order branchIconClass, + closeMenuIconClass, + openMenuIconClass, pullButtonClass, pushButtonClass, refreshButtonClass, @@ -15,6 +17,7 @@ import { toolbarMenuButtonIconClass, toolbarMenuButtonSubtitleClass, toolbarMenuButtonTitleClass, + toolbarMenuButtonTitleWrapperClass, toolbarMenuWrapperClass, toolbarNavClass } from '../style/Toolbar'; @@ -167,10 +170,12 @@ export class Toolbar extends React.Component { 'jp-Icon-16' )} /> -

    Current Repository

    -

    - {PathExt.basename(repo)} -

    +
    +

    Current Repository

    +

    + {PathExt.basename(repo)} +

    +
    {this.state.repoMenu ? null : null}
  • @@ -187,8 +192,17 @@ export class Toolbar extends React.Component { 'jp-Icon-16' )} /> -

    Current Branch

    -

    {branch}

    +
    +

    Current Branch

    +

    {branch}

    +
    + {this.state.branchMenu ? ( diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 148af3fc2..3bf61c94a 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -33,7 +33,7 @@ export const toolbarMenuWrapperClass = style({ export const toolbarMenuButtonClass = style({ boxSizing: 'border-box', display: 'flex', - flexDirection: 'column', + flexDirection: 'row', flexWrap: 'wrap', width: '100%', @@ -63,16 +63,20 @@ export const toolbarMenuButtonClass = style({ export const toolbarMenuButtonIconClass = style({ width: '16px', - height: '100%', + height: '16px', /* top | right | bottom | left */ margin: 'auto 8px auto 0' }); -export const toolbarMenuButtonTitleClass = style({ - marginTop: 'auto' +export const toolbarMenuButtonTitleWrapperClass = style({ + marginTop: 'auto', + marginBottom: 'auto', + marginRight: 'auto' }); +export const toolbarMenuButtonTitleClass = style({}); + export const toolbarMenuButtonSubtitleClass = style({ marginBottom: 'auto', @@ -141,3 +145,17 @@ export const branchIconClass = style({ backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }); + +export const openMenuIconClass = style({ + backgroundImage: 'var(--jp-icon-caretdown)', + backgroundSize: '20px', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center' +}); + +export const closeMenuIconClass = style({ + backgroundImage: 'var(--jp-icon-caretup)', + backgroundSize: '20px', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center' +}); From 947de305112d0873233cfa10fc2bb36002f79e69 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 12:01:30 -0800 Subject: [PATCH 038/126] Add support for switching the current branch --- src/components/BranchMenu.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index f3b7714f0..ea33d26f9 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ClearIcon from '@material-ui/icons/Clear'; +import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { branchMenuFilterClass, @@ -159,6 +160,8 @@ export class BranchMenu extends React.Component< */ private _onNewBranchClick = () => { console.log('Create a new branch...'); + + // TODO: implement via a dialog }; /** @@ -168,7 +171,7 @@ export class BranchMenu extends React.Component< * @returns callback */ private _onBranchClickFactory = (branch: string) => { - // const self = this; + const self = this; return onClick; /** @@ -177,8 +180,13 @@ export class BranchMenu extends React.Component< * @private * @param event - event object */ - function onClick() { - console.log(branch); + async function onClick() { + const result = await self.props.model.checkout({ + branchname: branch + }); + if (result.code !== 0) { + showErrorMessage('Error switching branch', result.message); + } } }; } From 8c216add9eea832c8ad775793ccf3f4b3a6c4aef Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 12:20:29 -0800 Subject: [PATCH 039/126] Use explicit promise handling --- src/components/BranchMenu.tsx | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index ea33d26f9..43018d5ae 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -180,13 +180,36 @@ export class BranchMenu extends React.Component< * @private * @param event - event object */ - async function onClick() { - const result = await self.props.model.checkout({ + function onClick() { + const opts = { branchname: branch - }); + }; + self.props.model + .checkout(opts) + .then(onResolve) + .catch(onError); + } + + /** + * Callback invoked upon promise resolution. + * + * @private + * @param result - result + */ + function onResolve(result: any) { if (result.code !== 0) { showErrorMessage('Error switching branch', result.message); } } + + /** + * Callback invoked upon encountering an error. + * + * @private + * @param err - error + */ + function onError(err: any) { + showErrorMessage('Error switching branch', err.message); + } }; } From 931aae6c975b0282560bb996fc81f30bcf237ff2 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 12:35:31 -0800 Subject: [PATCH 040/126] Adjust styles for small panel widths --- src/style/Toolbar.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 3bf61c94a..c63927cf1 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -37,7 +37,7 @@ export const toolbarMenuButtonClass = style({ flexWrap: 'wrap', width: '100%', - height: '55px', + minHeight: '55px', padding: '0 11px 0', @@ -72,7 +72,10 @@ export const toolbarMenuButtonIconClass = style({ export const toolbarMenuButtonTitleWrapperClass = style({ marginTop: 'auto', marginBottom: 'auto', - marginRight: 'auto' + marginRight: 'auto', + + overflow: 'hidden', + whiteSpace: 'nowrap' }); export const toolbarMenuButtonTitleClass = style({}); From 658d4f78783d91518659e7a632c987d60405c231 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 12:52:16 -0800 Subject: [PATCH 041/126] Adjust layout for narrow panel widths --- src/style/Toolbar.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index c63927cf1..77606e5ea 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -39,7 +39,8 @@ export const toolbarMenuButtonClass = style({ width: '100%', minHeight: '55px', - padding: '0 11px 0', + /* top | right | bottom | left */ + padding: '4px 11px 4px 11px', fontSize: 'var(--jp-ui-font-size1)', lineHeight: '1.5em', @@ -70,12 +71,12 @@ export const toolbarMenuButtonIconClass = style({ }); export const toolbarMenuButtonTitleWrapperClass = style({ + flexBasis: 0, + flexGrow: 1, + marginTop: 'auto', marginBottom: 'auto', - marginRight: 'auto', - - overflow: 'hidden', - whiteSpace: 'nowrap' + marginRight: 'auto' }); export const toolbarMenuButtonTitleClass = style({}); From ae3fe933941c564fe7bb8fef23e9090a8cfcadc9 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 12:55:25 -0800 Subject: [PATCH 042/126] Add menu icon for repo menu dropdown --- src/components/Toolbar.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index bafc3d587..26c234143 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -176,6 +176,13 @@ export class Toolbar extends React.Component { {PathExt.basename(repo)}

    + {this.state.repoMenu ? null : null}
    From bf72d938e8701ea9096ee78aece41f949d492596 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 13:23:36 -0800 Subject: [PATCH 043/126] Highlight the active branch in the branch menu --- src/components/BranchMenu.tsx | 9 ++++++++- src/style/BranchMenu.ts | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 43018d5ae..925e740fd 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; +import { classes } from 'typestyle'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ClearIcon from '@material-ui/icons/Clear'; import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { + branchMenuActiveListItemClass, branchMenuFilterClass, branchMenuFilterClearClass, branchMenuFilterInputClass, @@ -123,7 +125,12 @@ export class BranchMenu extends React.Component< return ( diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 3306d37cb..4b1f255f9 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -100,6 +100,12 @@ export const branchMenuListItemClass = style({ paddingLeft: '11px!important' }); +export const branchMenuActiveListItemClass = style({ + color: 'white!important', + + backgroundColor: 'var(--jp-brand-color1)!important' +}); + export const branchMenuListItemIconClass = style({ width: '16px', height: '16px', From ca99f931b7e43012a5ce8127d1e108ee73a9baad Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 16:03:51 -0800 Subject: [PATCH 044/126] Add support for showing a dialog for creating a new branch (WIP) --- src/components/BranchMenu.tsx | 124 ++++++++++++++++++++++++++++++++-- src/style/BranchMenu.ts | 61 +++++++++++++++++ 2 files changed, 181 insertions(+), 4 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 925e740fd..70f1bd1ca 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -2,10 +2,19 @@ import * as React from 'react'; import { classes } from 'typestyle'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; import ClearIcon from '@material-ui/icons/Clear'; import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { + branchDialogButtonClass, + branchDialogCancelButtonClass, + branchDialogClass, + branchDialogCloseClass, + branchDialogCreateButtonClass, + branchDialogTitleClass, + branchDialogTitleWrapperClass, branchMenuActiveListItemClass, branchMenuFilterClass, branchMenuFilterClearClass, @@ -36,6 +45,11 @@ export interface IBranchMenuState { * Menu filter. */ filter: string; + + /** + * Boolean indicating whether to show a dialog to create a new branch. + */ + branchDialog: boolean; } /** @@ -54,7 +68,8 @@ export class BranchMenu extends React.Component< constructor(props: IBranchMenuProps) { super(props); this.state = { - filter: '' + filter: '', + branchDialog: false }; } @@ -97,6 +112,47 @@ export class BranchMenu extends React.Component<
    {this._renderItems()}
    + +
    +

    Create a Branch

    + +
    + + + + +
    ); } @@ -166,9 +222,9 @@ export class BranchMenu extends React.Component< * @param event - event object */ private _onNewBranchClick = () => { - console.log('Create a new branch...'); - - // TODO: implement via a dialog + this.setState({ + branchDialog: true + }); }; /** @@ -219,4 +275,64 @@ export class BranchMenu extends React.Component< showErrorMessage('Error switching branch', err.message); } }; + + /** + * Callback invoked upon closing a dialog to create a new branch. + * + * @param event - event object + */ + private _onBranchDialogClose = () => { + this.setState({ + branchDialog: false + }); + }; + + /** + * Callback invoked upon clicking a button to create a new branch. + * + * @param event - event object + */ + private _onBranchDialogCreate = () => { + this._onBranchDialogClose(); + let foo = this._createBranch; // FIXME: invoke + console.log(foo); + }; + + /** + * Creates a new branch. + * + * @param branch - branch name + */ + private _createBranch = (branch: string) => { + const opts = { + newBranch: true, + branchname: branch + }; + this.props.model + .checkout(opts) + .then(onResolve) + .catch(onError); + + /** + * Callback invoked upon promise resolution. + * + * @private + * @param result - result + */ + function onResolve(result: any) { + if (result.code !== 0) { + showErrorMessage('Error creating branch', result.message); + } + } + + /** + * Callback invoked upon encountering an error. + * + * @private + * @param err - error + */ + function onError(err: any) { + showErrorMessage('Error creating branch', err.message); + } + }; } diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 4b1f255f9..869f43315 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -117,3 +117,64 @@ export const branchMenuListItemIconClass = style({ backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }); + +export const branchDialogClass = style({ + width: '400px', + + borderRadius: '0!important' +}); + +export const branchDialogCloseClass = style({ + position: 'absolute', + top: '12px', + right: '12px', + + height: '30px', + width: '30px', + + border: 'none', + borderRadius: '50%', + + $nest: { + '&:hover': { + backgroundColor: 'var(--jp-toolbar-active-background)' + }, + '&:active': { + backgroundColor: 'var(--jp-toolbar-active-background)' + } + } +}); + +export const branchDialogTitleWrapperClass = style({ + boxSizing: 'border-box', + position: 'relative', + + padding: '15px', + + borderBottom: 'var(--jp-border-width) solid var(--jp-border-color2)' +}); + +export const branchDialogTitleClass = style({ + fontWeight: 700 +}); + +export const branchDialogButtonClass = style({ + boxSizing: 'border-box', + + width: '9em', + height: '2em', + + color: 'white', + fontSize: 'var(--jp-ui-font-size1)', + + border: '0', + borderRadius: '3px' +}); + +export const branchDialogCancelButtonClass = style({ + backgroundColor: 'var(--jp-inverse-layout-color4)' +}); + +export const branchDialogCreateButtonClass = style({ + backgroundColor: 'var(--jp-brand-color1)' +}); From bd3eeee2043b6cab6f4e2c6214b9be8711b3d869 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 16:10:49 -0800 Subject: [PATCH 045/126] Adjust dialog styles --- src/style/BranchMenu.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 869f43315..588e64e17 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -121,17 +121,19 @@ export const branchMenuListItemIconClass = style({ export const branchDialogClass = style({ width: '400px', - borderRadius: '0!important' + borderRadius: '3px!important' }); export const branchDialogCloseClass = style({ position: 'absolute', - top: '12px', + top: '10px', right: '12px', height: '30px', width: '30px', + padding: 0, + border: 'none', borderRadius: '50%', @@ -151,7 +153,7 @@ export const branchDialogTitleWrapperClass = style({ padding: '15px', - borderBottom: 'var(--jp-border-width) solid var(--jp-border-color2)' + borderBottom: 'var(--jp-border-width) solid #e0e0e0' }); export const branchDialogTitleClass = style({ From a75b83da285439efa8abd3a3f87853cbaa356927 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 17:04:42 -0800 Subject: [PATCH 046/126] Provide visual indication when input element focused --- src/style/CommitBox.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/style/CommitBox.ts b/src/style/CommitBox.ts index d6843fdf0..d2ae482c9 100644 --- a/src/style/CommitBox.ts +++ b/src/style/CommitBox.ts @@ -30,7 +30,16 @@ export const commitSummaryClass = style({ backgroundColor: 'var(--jp-layout-color1)', border: 'var(--jp-border-width) solid var(--jp-border-color2)', - borderRadius: '3px' + borderRadius: '3px', + + $nest: { + '&:active': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:focus': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + } + } }); export const commitDescriptionClass = style({ @@ -53,7 +62,12 @@ export const commitDescriptionClass = style({ $nest: { '&:focus': { - outline: 'none' + outline: 'none', + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:active': { + outline: 'none', + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' }, '&::placeholder': { color: 'var(--jp-ui-font-color3)' From a94698fe98a1c0fb579a79c41b398cf11c91cf61 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 17:05:16 -0800 Subject: [PATCH 047/126] Adjust styles --- src/components/BranchMenu.tsx | 50 +++++++++++++++++++++++++++++++-- src/style/BranchMenu.ts | 53 ++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 70f1bd1ca..0a737765f 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -8,10 +8,13 @@ import ClearIcon from '@material-ui/icons/Clear'; import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { + branchDialogActionsWrapperClass, + branchDialogBranchNameInputClass, branchDialogButtonClass, branchDialogCancelButtonClass, branchDialogClass, branchDialogCloseClass, + branchDialogContentWrapperClass, branchDialogCreateButtonClass, branchDialogTitleClass, branchDialogTitleWrapperClass, @@ -50,6 +53,11 @@ export interface IBranchMenuState { * Boolean indicating whether to show a dialog to create a new branch. */ branchDialog: boolean; + + /** + * Branch name. + */ + branchName: string; } /** @@ -69,7 +77,8 @@ export class BranchMenu extends React.Component< super(props); this.state = { filter: '', - branchDialog: false + branchDialog: false, + branchName: '' }; } @@ -130,7 +139,19 @@ export class BranchMenu extends React.Component< />
    - +
    +

    Name

    + +

    Create branch based on...

    +
    + { + this.setState({ + branchName: event.target.value + }); + }; + /** * Callback invoked upon closing a dialog to create a new branch. * @@ -293,9 +325,21 @@ export class BranchMenu extends React.Component< * @param event - event object */ private _onBranchDialogCreate = () => { + const branch = this.state.branchName; + + // Close the branch dialog: this._onBranchDialogClose(); + + // Reset the branch name: + this.setState({ + branchName: '' + }); + let foo = this._createBranch; // FIXME: invoke - console.log(foo); + if (!foo) { + console.log('Huh?'); + } + console.log(branch); }; /** diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 588e64e17..6acb53d57 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -37,7 +37,16 @@ export const branchMenuFilterInputClass = style({ backgroundColor: 'var(--jp-layout-color1)', border: 'var(--jp-border-width) solid var(--jp-border-color2)', - borderRadius: '3px' + borderRadius: '3px', + + $nest: { + '&:active': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:focus': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + } + } }); export const branchMenuFilterClearClass = style({ @@ -160,6 +169,48 @@ export const branchDialogTitleClass = style({ fontWeight: 700 }); +export const branchDialogContentWrapperClass = style({ + padding: '15px', + + $nest: { + p: { + marginBottom: '10px' + }, + input: { + marginBottom: '16px' + } + } +}); + +export const branchDialogBranchNameInputClass = style({ + boxSizing: 'border-box', + + width: '100%', + height: '2em', + + /* top | right | bottom | left */ + padding: '1px 18px 2px 7px', + + fontSize: 'var(--jp-ui-font-size1)', + fontWeight: 300, + + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', + + $nest: { + '&:active': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:focus': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + } + } +}); + +export const branchDialogActionsWrapperClass = style({ + borderTop: 'var(--jp-border-width) solid #e0e0e0' +}); + export const branchDialogButtonClass = style({ boxSizing: 'border-box', From 8c874af033d8adaee9bc9c6044db7832c2630444 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 19:37:33 -0800 Subject: [PATCH 048/126] Move dialog logic to separate component --- src/components/BranchMenu.tsx | 211 ++++------------------ src/components/NewBranchDialog.tsx | 278 +++++++++++++++++++++++++++++ src/style/BranchMenu.ts | 125 ++----------- src/style/NewBranchDialog.ts | 117 ++++++++++++ 4 files changed, 441 insertions(+), 290 deletions(-) create mode 100644 src/components/NewBranchDialog.tsx create mode 100644 src/style/NewBranchDialog.ts diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 0a737765f..f59a278e4 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -2,33 +2,22 @@ import * as React from 'react'; import { classes } from 'typestyle'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; import ClearIcon from '@material-ui/icons/Clear'; import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { - branchDialogActionsWrapperClass, - branchDialogBranchNameInputClass, - branchDialogButtonClass, - branchDialogCancelButtonClass, - branchDialogClass, - branchDialogCloseClass, - branchDialogContentWrapperClass, - branchDialogCreateButtonClass, - branchDialogTitleClass, - branchDialogTitleWrapperClass, - branchMenuActiveListItemClass, - branchMenuFilterClass, - branchMenuFilterClearClass, - branchMenuFilterInputClass, - branchMenuFilterWrapperClass, - branchMenuListItemClass, - branchMenuListItemIconClass, - branchMenuListWrapperClass, - branchMenuNewBranchButtonClass, - branchMenuWrapperClass + activeListItemClass, + filterClass, + filterClearClass, + filterInputClass, + filterWrapperClass, + listItemClass, + listItemIconClass, + listWrapperClass, + newBranchButtonClass, + wrapperClass } from '../style/BranchMenu'; +import { NewBranchDialog } from './NewBranchDialog'; /** * Interface describing component properties. @@ -53,11 +42,6 @@ export interface IBranchMenuState { * Boolean indicating whether to show a dialog to create a new branch. */ branchDialog: boolean; - - /** - * Branch name. - */ - branchName: string; } /** @@ -77,8 +61,7 @@ export class BranchMenu extends React.Component< super(props); this.state = { filter: '', - branchDialog: false, - branchName: '' + branchDialog: false }; } @@ -89,11 +72,11 @@ export class BranchMenu extends React.Component< */ render() { return ( -
    -
    -
    +
    +
    +
    {this.state.filter ? ( -
    -
    +
    {this._renderItems()}
    - -
    -

    Create a Branch

    - -
    -
    -

    Name

    - -

    Create branch based on...

    -
    - - - - -
    + model={this.props.model} + onClose={this._onNewBranchDialogClose} + />
    ); } @@ -181,7 +116,7 @@ export class BranchMenu extends React.Component< /** * Renders menu items. * - * @returns fragment + * @returns fragment array */ private _renderItems = () => { return this.props.model.branches.map(this._renderItem); @@ -203,15 +138,15 @@ export class BranchMenu extends React.Component< - + {branch.name} ); @@ -248,6 +183,15 @@ export class BranchMenu extends React.Component< }); }; + /** + * Callback invoked upon closing a dialog to create a new branch. + */ + private _onNewBranchDialogClose = () => { + this.setState({ + branchDialog: false + }); + }; + /** * Returns a callback which is invoked upon clicking a branch name. * @@ -296,87 +240,4 @@ export class BranchMenu extends React.Component< showErrorMessage('Error switching branch', err.message); } }; - - /** - * Callback invoked upon a change to the branch name input element. - * - * @param event - event object - */ - private _onBranchNameChange = (event: any) => { - this.setState({ - branchName: event.target.value - }); - }; - - /** - * Callback invoked upon closing a dialog to create a new branch. - * - * @param event - event object - */ - private _onBranchDialogClose = () => { - this.setState({ - branchDialog: false - }); - }; - - /** - * Callback invoked upon clicking a button to create a new branch. - * - * @param event - event object - */ - private _onBranchDialogCreate = () => { - const branch = this.state.branchName; - - // Close the branch dialog: - this._onBranchDialogClose(); - - // Reset the branch name: - this.setState({ - branchName: '' - }); - - let foo = this._createBranch; // FIXME: invoke - if (!foo) { - console.log('Huh?'); - } - console.log(branch); - }; - - /** - * Creates a new branch. - * - * @param branch - branch name - */ - private _createBranch = (branch: string) => { - const opts = { - newBranch: true, - branchname: branch - }; - this.props.model - .checkout(opts) - .then(onResolve) - .catch(onError); - - /** - * Callback invoked upon promise resolution. - * - * @private - * @param result - result - */ - function onResolve(result: any) { - if (result.code !== 0) { - showErrorMessage('Error creating branch', result.message); - } - } - - /** - * Callback invoked upon encountering an error. - * - * @private - * @param err - error - */ - function onError(err: any) { - showErrorMessage('Error creating branch', err.message); - } - }; } diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx new file mode 100644 index 000000000..164147e5c --- /dev/null +++ b/src/components/NewBranchDialog.tsx @@ -0,0 +1,278 @@ +import * as React from 'react'; +import { classes } from 'typestyle'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import ClearIcon from '@material-ui/icons/Clear'; +import { showErrorMessage } from '@jupyterlab/apputils'; +import { Git, IGitExtension } from '../tokens'; +import { + actionsWrapperClass, + branchDialogClass, + buttonClass, + cancelButtonClass, + closeButtonClass, + contentWrapperClass, + createButtonClass, + menuListItemClass, + menuListItemDescClass, + menuListItemIconClass, + menuListItemTitleClass, + menuWrapperClass, + nameInputClass, + titleClass, + titleWrapperClass +} from '../style/NewBranchDialog'; + +/** + * Interface describing component properties. + */ +export interface INewBranchDialogProps { + /** + * Git extension data model. + */ + model: IGitExtension; + + /** + * Boolean indicating whether to show the dialog. + */ + open: boolean; + + /** + * Callback to invoke upon closing the dialog. + */ + onClose: () => void; +} + +/** + * Interface describing component state. + */ +export interface INewBranchDialogState { + /** + * Branch name. + */ + name: string; + + /** + * Base branch. + */ + base: string; +} + +/** + * React component for rendering a dialog to create a new branch. + */ +export class NewBranchDialog extends React.Component< + INewBranchDialogProps, + INewBranchDialogState +> { + /** + * Returns a React component for rendering a branch menu. + * + * @param props - component properties + * @returns React component + */ + constructor(props: INewBranchDialogProps) { + super(props); + this.state = { + name: '', + base: this.props.model.currentBranch.name + }; + } + + /** + * Renders the component. + * + * @returns fragment + */ + render() { + return ( + +
    +

    Create a Branch

    + +
    +
    +

    Name

    + +

    Create branch based on...

    +
    + {this._renderItems()} +
    +
    + + + + +
    + ); + } + + /** + * Renders branch menu items. + * + * @returns fragment array + */ + private _renderItems = () => { + return this.props.model.branches.map(this._renderItem); + }; + + /** + * Renders a branch menu item. + * + * @param branch - branch + * @param idx - item index + * @returns fragment + */ + private _renderItem = (branch: Git.IBranch, idx: number) => { + // TODO: consider allowing users to branch from any branch, rather than just the current branch... + if (branch.name !== this.props.model.currentBranch.name) { + return null; + } + return ( + + +

    branch.name

    + {branch.name === this.props.model.currentBranch.name ? ( +

    + The current branch. Pick this if you want to build on work done in + this branch. +

    + ) : null} +
    + ); + }; + + /** + * Returns a callback which is invoked upon clicking a branch name. + * + * @param branch - branch name + * @returns callback + */ + private _onBranchClickFactory = (branch: string) => { + const self = this; + return onClick; + + /** + * Callback invoked upon clicking a branch name. + * + * @private + * @param event - event object + */ + function onClick() { + self.setState({ + base: branch + }); + } + }; + + /** + * Callback invoked upon a change to the branch name input element. + * + * @param event - event object + */ + private _onNameChange = (event: any) => { + this.setState({ + name: event.target.value + }); + }; + + /** + * Callback invoked upon clicking a button to create a new branch. + * + * @param event - event object + */ + private _onCreate = () => { + const branch = this.state.name; + + // Close the branch dialog: + this.props.onClose(); + + // Reset the branch name: + this.setState({ + name: '' + }); + + let foo = this._createBranch; // FIXME: invoke + if (!foo) { + console.log('Huh?'); + } + console.log(branch); + }; + + /** + * Creates a new branch. + * + * @param branch - branch name + */ + private _createBranch = (branch: string) => { + const opts = { + newBranch: true, + branchname: branch + }; + this.props.model + .checkout(opts) + .then(onResolve) + .catch(onError); + + /** + * Callback invoked upon promise resolution. + * + * @private + * @param result - result + */ + function onResolve(result: any) { + if (result.code !== 0) { + showErrorMessage('Error creating branch', result.message); + } + } + + /** + * Callback invoked upon encountering an error. + * + * @private + * @param err - error + */ + function onError(err: any) { + showErrorMessage('Error creating branch', err.message); + } + }; +} diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 6acb53d57..1ae26d510 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -1,15 +1,15 @@ import { style } from 'typestyle'; -export const branchMenuWrapperClass = style({ +export const wrapperClass = style({ marginTop: '6px', marginBottom: '10px' }); -export const branchMenuFilterWrapperClass = style({ +export const filterWrapperClass = style({ padding: '4px 11px 4px' }); -export const branchMenuFilterClass = style({ +export const filterClass = style({ boxSizing: 'border-box', display: 'inline-block', position: 'relative', @@ -21,7 +21,7 @@ export const branchMenuFilterClass = style({ fontSize: 'var(--jp-ui-font-size1)' }); -export const branchMenuFilterInputClass = style({ +export const filterInputClass = style({ boxSizing: 'border-box', width: '100%', @@ -49,7 +49,7 @@ export const branchMenuFilterInputClass = style({ } }); -export const branchMenuFilterClearClass = style({ +export const filterClearClass = style({ position: 'absolute', right: '5px', top: '0.6em', @@ -80,7 +80,7 @@ export const branchMenuFilterClearClass = style({ } }); -export const branchMenuNewBranchButtonClass = style({ +export const newBranchButtonClass = style({ boxSizing: 'border-box', width: '7.7em', @@ -94,7 +94,7 @@ export const branchMenuNewBranchButtonClass = style({ borderRadius: '3px' }); -export const branchMenuListWrapperClass = style({ +export const listWrapperClass = style({ display: 'block', width: '100%', maxHeight: '400px', @@ -103,19 +103,19 @@ export const branchMenuListWrapperClass = style({ overflowY: 'scroll' }); -export const branchMenuListItemClass = style({ +export const listItemClass = style({ paddingTop: '4px!important', paddingBottom: '4px!important', paddingLeft: '11px!important' }); -export const branchMenuActiveListItemClass = style({ +export const activeListItemClass = style({ color: 'white!important', backgroundColor: 'var(--jp-brand-color1)!important' }); -export const branchMenuListItemIconClass = style({ +export const listItemIconClass = style({ width: '16px', height: '16px', @@ -126,108 +126,3 @@ export const branchMenuListItemIconClass = style({ backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }); - -export const branchDialogClass = style({ - width: '400px', - - borderRadius: '3px!important' -}); - -export const branchDialogCloseClass = style({ - position: 'absolute', - top: '10px', - right: '12px', - - height: '30px', - width: '30px', - - padding: 0, - - border: 'none', - borderRadius: '50%', - - $nest: { - '&:hover': { - backgroundColor: 'var(--jp-toolbar-active-background)' - }, - '&:active': { - backgroundColor: 'var(--jp-toolbar-active-background)' - } - } -}); - -export const branchDialogTitleWrapperClass = style({ - boxSizing: 'border-box', - position: 'relative', - - padding: '15px', - - borderBottom: 'var(--jp-border-width) solid #e0e0e0' -}); - -export const branchDialogTitleClass = style({ - fontWeight: 700 -}); - -export const branchDialogContentWrapperClass = style({ - padding: '15px', - - $nest: { - p: { - marginBottom: '10px' - }, - input: { - marginBottom: '16px' - } - } -}); - -export const branchDialogBranchNameInputClass = style({ - boxSizing: 'border-box', - - width: '100%', - height: '2em', - - /* top | right | bottom | left */ - padding: '1px 18px 2px 7px', - - fontSize: 'var(--jp-ui-font-size1)', - fontWeight: 300, - - border: 'var(--jp-border-width) solid var(--jp-border-color2)', - borderRadius: '3px', - - $nest: { - '&:active': { - border: 'var(--jp-border-width) solid var(--jp-brand-color1)' - }, - '&:focus': { - border: 'var(--jp-border-width) solid var(--jp-brand-color1)' - } - } -}); - -export const branchDialogActionsWrapperClass = style({ - borderTop: 'var(--jp-border-width) solid #e0e0e0' -}); - -export const branchDialogButtonClass = style({ - boxSizing: 'border-box', - - width: '9em', - height: '2em', - - color: 'white', - fontSize: 'var(--jp-ui-font-size1)', - - border: '0', - borderRadius: '3px' -}); - -export const branchDialogCancelButtonClass = style({ - backgroundColor: 'var(--jp-inverse-layout-color4)' -}); - -export const branchDialogCreateButtonClass = style({ - backgroundColor: 'var(--jp-brand-color1)' -}); diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts new file mode 100644 index 000000000..ab75cc7be --- /dev/null +++ b/src/style/NewBranchDialog.ts @@ -0,0 +1,117 @@ +import { style } from 'typestyle'; + +export const branchDialogClass = style({ + width: '400px', + + borderRadius: '3px!important' +}); + +export const closeButtonClass = style({ + position: 'absolute', + top: '10px', + right: '12px', + + height: '30px', + width: '30px', + + padding: 0, + + border: 'none', + borderRadius: '50%', + + $nest: { + '&:hover': { + backgroundColor: 'var(--jp-toolbar-active-background)' + }, + '&:active': { + backgroundColor: 'var(--jp-toolbar-active-background)' + } + } +}); + +export const titleWrapperClass = style({ + boxSizing: 'border-box', + position: 'relative', + + padding: '15px', + + borderBottom: 'var(--jp-border-width) solid #e0e0e0' +}); + +export const titleClass = style({ + fontWeight: 700 +}); + +export const contentWrapperClass = style({ + padding: '15px', + + $nest: { + '> p': { + marginBottom: '10px' + } + } +}); + +export const nameInputClass = style({ + boxSizing: 'border-box', + + width: '100%', + height: '2em', + + marginBottom: '16px', + + /* top | right | bottom | left */ + padding: '1px 18px 2px 7px', + + fontSize: 'var(--jp-ui-font-size1)', + fontWeight: 300, + + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', + + $nest: { + '&:active': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:focus': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + } + } +}); + +export const menuWrapperClass = style({}); + +export const menuListItemClass = style({}); + +export const menuListItemDescClass = style({}); + +export const menuListItemIconClass = style({}); + +export const menuListItemTitleClass = style({}); + +export const actionsWrapperClass = style({ + padding: '15px!important', + + borderTop: 'var(--jp-border-width) solid #e0e0e0' +}); + +export const buttonClass = style({ + boxSizing: 'border-box', + + width: '9em', + height: '2em', + + color: 'white', + fontSize: 'var(--jp-ui-font-size1)', + + border: '0', + borderRadius: '3px' +}); + +export const cancelButtonClass = style({ + backgroundColor: 'var(--jp-inverse-layout-color4)' +}); + +export const createButtonClass = style({ + backgroundColor: 'var(--jp-brand-color1)' +}); From cd8c32d603f5576c033827b51bc809bd6ae9aadf Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 19:56:38 -0800 Subject: [PATCH 049/126] Rename classes and adjust styles --- src/components/NewBranchDialog.tsx | 37 ++++++++++------- src/style/NewBranchDialog.ts | 65 +++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 164147e5c..ec20775de 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -9,17 +9,19 @@ import { showErrorMessage } from '@jupyterlab/apputils'; import { Git, IGitExtension } from '../tokens'; import { actionsWrapperClass, + activeListItemClass, branchDialogClass, buttonClass, cancelButtonClass, closeButtonClass, contentWrapperClass, createButtonClass, - menuListItemClass, - menuListItemDescClass, - menuListItemIconClass, - menuListItemTitleClass, - menuWrapperClass, + listItemClass, + listItemContentClass, + listItemDescClass, + listItemIconClass, + listItemTitleClass, + listWrapperClass, nameInputClass, titleClass, titleWrapperClass @@ -117,7 +119,7 @@ export class NewBranchDialog extends React.Component< title="Enter a branch name" />

    Create branch based on...

    -
    +
    {this._renderItems()}
    @@ -165,18 +167,23 @@ export class NewBranchDialog extends React.Component< return ( - -

    branch.name

    - {branch.name === this.props.model.currentBranch.name ? ( -

    - The current branch. Pick this if you want to build on work done in - this branch. -

    - ) : null} + +
    +

    {branch.name}

    + {branch.name === this.props.model.currentBranch.name ? ( +

    + The current branch. Pick this if you want to build on work done in + this branch. +

    + ) : null} +
    ); }; diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index ab75cc7be..ad6cced10 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -79,15 +79,70 @@ export const nameInputClass = style({ } }); -export const menuWrapperClass = style({}); +export const listWrapperClass = style({ + display: 'block', + width: '100%', + maxHeight: '400px', + + overflow: 'hidden', + overflowY: 'scroll' +}); + +export const listItemClass = style({ + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + + width: '100%', + minHeight: '55px', + + /* top | right | bottom | left */ + padding: '4px 11px 4px 11px', + + fontSize: 'var(--jp-ui-font-size1)', + lineHeight: '1.5em', + textAlign: 'left', + + border: 'none', + borderRadius: 0 +}); + +export const activeListItemClass = style({ + color: 'white!important', + + backgroundColor: 'var(--jp-brand-color1)!important' +}); -export const menuListItemClass = style({}); +export const listItemContentClass = style({ + flexBasis: 0, + flexGrow: 1, -export const menuListItemDescClass = style({}); + marginTop: 'auto', + marginBottom: 'auto', + marginRight: 'auto' +}); + +export const listItemDescClass = style({ + marginBottom: 'auto' +}); -export const menuListItemIconClass = style({}); +export const listItemIconClass = style({ + width: '16px', + height: '16px', -export const menuListItemTitleClass = style({}); + /* top | right | bottom | left */ + margin: 'auto 8px auto 0', + + backgroundImage: 'var(--jp-icon-git-branch)', + backgroundSize: '16px', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center' +}); + +export const listItemTitleClass = style({ + fontWeight: 700 +}); export const actionsWrapperClass = style({ padding: '15px!important', From 561a26c87c661bc2a59d2be496aa429c26449149 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 19:58:28 -0800 Subject: [PATCH 050/126] Enable branch creation --- src/components/NewBranchDialog.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index ec20775de..fa4f2dd75 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -238,11 +238,8 @@ export class NewBranchDialog extends React.Component< name: '' }); - let foo = this._createBranch; // FIXME: invoke - if (!foo) { - console.log('Huh?'); - } - console.log(branch); + // Create the branch: + this._createBranch(branch); }; /** From cf8a7095675cac962e2b96e66059c277d7587af2 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 20:09:20 -0800 Subject: [PATCH 051/126] Remove obsolete component and logic --- src/components/BranchHeader.tsx | 130 ++------------------------------ src/components/NewBranchBox.tsx | 74 ------------------ src/style/NewBranchBoxStyle.ts | 56 -------------- 3 files changed, 7 insertions(+), 253 deletions(-) delete mode 100644 src/components/NewBranchBox.tsx delete mode 100644 src/style/NewBranchBoxStyle.ts diff --git a/src/components/BranchHeader.tsx b/src/components/BranchHeader.tsx index 1c87019e0..0c2b59b73 100644 --- a/src/components/BranchHeader.tsx +++ b/src/components/BranchHeader.tsx @@ -2,30 +2,19 @@ import { showErrorMessage } from '@jupyterlab/apputils'; import * as React from 'react'; import { classes } from 'typestyle'; import { - branchDropdownButtonStyle, branchHeaderCenterContent, - branchLabelStyle, - branchListItemStyle, branchStyle, - branchTrackingLabelStyle, - expandedBranchStyle, - headerButtonDisabledStyle, historyLabelStyle, - newBranchButtonStyle, openHistorySideBarButtonStyle, selectedHeaderStyle, unSelectedHeaderStyle } from '../style/BranchHeaderStyle'; import { Git, IGitExtension } from '../tokens'; -import { NewBranchBox } from './NewBranchBox'; const CHANGES_ERR_MSG = 'You have files with changes in current branch. Please commit or discard changed files before'; -export interface IBranchHeaderState { - dropdownOpen: boolean; - showNewBranchBox: boolean; -} +export interface IBranchHeaderState {} export interface IBranchHeaderProps { model: IGitExtension; @@ -45,40 +34,15 @@ export class BranchHeader extends React.Component< > { constructor(props: IBranchHeaderProps) { super(props); - this.state = { - dropdownOpen: false, - showNewBranchBox: false - }; - } - - /** Switch current working branch */ - async switchBranch(branchName: string) { - const result = await this.props.model.checkout({ branchname: branchName }); - if (result.code !== 0) { - showErrorMessage('Error switching branch', result.message); - } - - this.toggleSelect(); + this.state = {}; } - createNewBranch = async (branchName: string) => { - const result = await this.props.model.checkout({ - newBranch: true, - branchname: branchName - }); - if (result.code !== 0) { - showErrorMessage('Error creating new branch', result.message); - } - - this.toggleNewBranchBox(); - }; - toggleSelect() { this.props.refresh(); if (!this.props.disabled) { - this.setState({ - dropdownOpen: !this.state.dropdownOpen - }); + // this.setState({ + // dropdownOpen: !this.state.dropdownOpen + // }); } else { showErrorMessage( 'Switching branch disabled', @@ -87,29 +51,6 @@ export class BranchHeader extends React.Component< } } - getBranchStyle() { - if (this.state.dropdownOpen) { - return classes(branchStyle, expandedBranchStyle); - } else { - return branchStyle; - } - } - - toggleNewBranchBox = (): void => { - this.props.refresh(); - if (!this.props.disabled) { - this.setState({ - showNewBranchBox: !this.state.showNewBranchBox, - dropdownOpen: false - }); - } else { - showErrorMessage( - 'Creating new branch disabled', - CHANGES_ERR_MSG + 'creating a new branch.' - ); - } - }; - getHistoryHeaderStyle() { if (this.props.sideBarExpanded) { return classes(openHistorySideBarButtonStyle, selectedHeaderStyle); @@ -126,7 +67,7 @@ export class BranchHeader extends React.Component< render() { return ( -
    +
    -

    {this.props.currentBranch}

    -
    this.toggleSelect()} - /> - {!this.state.showNewBranchBox && ( -
    this.toggleNewBranchBox()} - /> - )} - {this.state.showNewBranchBox && ( - - )} - {this.props.upstreamBranch != null && - this.props.upstreamBranch !== '' && ( -

    - {this.props.upstreamBranch} -

    - )} +

    Changes

    - {!this.props.sideBarExpanded && ( - - {this.state.dropdownOpen && ( -
    - {this.props.data.map( - (branch: Git.IBranch, branchIndex: number) => ( -
  • this.switchBranch(branch.name)} - > - {branch.name} -
  • - ) - )} -
    - )} - {this.state.showNewBranchBox && ( -
    Branching from {this.props.currentBranch}
    - )} -
    - )}
    ); } diff --git a/src/components/NewBranchBox.tsx b/src/components/NewBranchBox.tsx deleted file mode 100644 index adab23386..000000000 --- a/src/components/NewBranchBox.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import { classes } from 'typestyle'; -import { - buttonStyle, - cancelNewBranchButtonStyle, - newBranchBoxStyle, - newBranchButtonStyle, - newBranchInputAreaStyle -} from '../style/NewBranchBoxStyle'; - -export interface ICommitBoxProps { - createNewBranch: (branchName: string) => Promise; - toggleNewBranchBox: () => void; -} - -export interface ICommitBoxState { - value: string; -} - -export class NewBranchBox extends React.Component< - ICommitBoxProps, - ICommitBoxState -> { - constructor(props: ICommitBoxProps) { - super(props); - this.state = { - value: '' - }; - } - - /** Prevent enter key triggered 'submit' action during input */ - onKeyPress(event: any): void { - if (event.which === 13) { - event.preventDefault(); - this.setState({ value: this.state.value + '\n' }); - } - } - - /** Handle input inside commit message box */ - handleChange = (event: any): void => { - this.setState({ - value: event.target.value - }); - }; - - render() { - return ( -
    this.onKeyPress(event)} - > - - this.props.createNewBranch(this.state.value)} - /> - this.props.toggleNewBranchBox()} - /> -
    - ); - } -} diff --git a/src/style/NewBranchBoxStyle.ts b/src/style/NewBranchBoxStyle.ts deleted file mode 100644 index 88324df5c..000000000 --- a/src/style/NewBranchBoxStyle.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { style, keyframes } from 'typestyle'; - -export const newBranchInputAreaStyle = style({ - verticalAlign: 'middle' -}); - -export const slideAnimation = keyframes({ - from: { - width: '0', - left: '0' - }, - to: { - width: '84px', - left: '0' - } -}); - -export const newBranchBoxStyle = style({ - width: '84px', - height: '17px', - boxSizing: 'border-box', - margin: '0', - padding: '2px', - verticalAlign: 'middle', - animationDuration: '0.5s', - animationTimingFunction: 'ease-out', - animationName: slideAnimation, - outline: 'none', - - $nest: { - '&:focus': { - border: '1px var(--jp-brand-color2) solid' - } - } -}); - -export const buttonStyle = style({ - backgroundSize: '100%', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - width: '17px', - height: '17px', - verticalAlign: 'middle', - outline: 'none', - border: 'none' -}); - -export const newBranchButtonStyle = style({ - backgroundImage: 'var(--jp-icon-plus-white)', - backgroundColor: 'var(--jp-brand-color1)' -}); - -export const cancelNewBranchButtonStyle = style({ - backgroundImage: 'var(--jp-icon-clear-white)', - backgroundColor: 'var(--jp-layout-color4)' -}); From 19fd461a91243539431e6c3db341abf29bc377f7 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 4 Jan 2020 20:24:41 -0800 Subject: [PATCH 052/126] Add support for disabling branching --- src/components/BranchHeader.tsx | 15 --------------- src/components/BranchMenu.tsx | 16 ++++++++++++++++ src/components/GitPanel.tsx | 13 ++++++------- src/components/Toolbar.tsx | 10 +++++++++- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/components/BranchHeader.tsx b/src/components/BranchHeader.tsx index 0c2b59b73..a6c6ff2fc 100644 --- a/src/components/BranchHeader.tsx +++ b/src/components/BranchHeader.tsx @@ -1,4 +1,3 @@ -import { showErrorMessage } from '@jupyterlab/apputils'; import * as React from 'react'; import { classes } from 'typestyle'; import { @@ -11,9 +10,6 @@ import { } from '../style/BranchHeaderStyle'; import { Git, IGitExtension } from '../tokens'; -const CHANGES_ERR_MSG = - 'You have files with changes in current branch. Please commit or discard changed files before'; - export interface IBranchHeaderState {} export interface IBranchHeaderProps { @@ -23,7 +19,6 @@ export interface IBranchHeaderProps { stagedFiles: Git.IStatusFileResult[]; data: Git.IBranch[]; refresh: () => Promise; - disabled: boolean; toggleSidebar: () => void; sideBarExpanded: boolean; } @@ -39,16 +34,6 @@ export class BranchHeader extends React.Component< toggleSelect() { this.props.refresh(); - if (!this.props.disabled) { - // this.setState({ - // dropdownOpen: !this.state.dropdownOpen - // }); - } else { - showErrorMessage( - 'Switching branch disabled', - CHANGES_ERR_MSG + 'switching to another branch.' - ); - } } getHistoryHeaderStyle() { diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index f59a278e4..87715a2f2 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -19,6 +19,9 @@ import { } from '../style/BranchMenu'; import { NewBranchDialog } from './NewBranchDialog'; +const CHANGES_ERR_MSG = + 'The current branch contains files with uncommitted changes. Please commit or discard these changes before switching to or creating another branch.'; + /** * Interface describing component properties. */ @@ -27,6 +30,11 @@ export interface IBranchMenuProps { * Git extension data model. */ model: IGitExtension; + + /** + * Boolean indicating whether branching is disabled. + */ + branching: boolean; } /** @@ -178,6 +186,10 @@ export class BranchMenu extends React.Component< * @param event - event object */ private _onNewBranchClick = () => { + if (!this.props.branching) { + showErrorMessage('Creating a new branch is disabled', CHANGES_ERR_MSG); + return; + } this.setState({ branchDialog: true }); @@ -209,6 +221,10 @@ export class BranchMenu extends React.Component< * @param event - event object */ function onClick() { + if (!self.props.branching) { + showErrorMessage('Switching branches is disabled', CHANGES_ERR_MSG); + return; + } const opts = { branchname: branch }; diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index af44e2575..26ba5bad8 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -247,13 +247,12 @@ export class GitPanel extends React.Component< ); } + const disableBranching = Boolean( + this.props.settings.composite['disableBranchWithChanges'] && + ((this.state.unstagedFiles && this.state.unstagedFiles.length) || + (this.state.stagedFiles && this.state.stagedFiles.length)) + ); if (this.state.inGitRepository) { - const disableBranchOps = Boolean( - this.props.settings.composite['disableBranchWithChanges'] && - ((this.state.unstagedFiles && this.state.unstagedFiles.length) || - (this.state.stagedFiles && this.state.stagedFiles.length)) - ); - main = ( @@ -290,6 +288,7 @@ export class GitPanel extends React.Component<
    { await this.refreshBranch(); if (this.state.isHistoryVisible) { diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 26c234143..6119fabb8 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -75,6 +75,11 @@ export interface IToolbarProps { */ model: IGitExtension; + /** + * Boolean indicating whether branching is disabled. + */ + branching: boolean; + /** * Callback to invoke in order to refresh a repository. * @@ -212,7 +217,10 @@ export class Toolbar extends React.Component { /> {this.state.branchMenu ? ( - + ) : null}
    From 13a53be0d1eca512193c455dbd6349e35084bdde Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 11:02:42 -0800 Subject: [PATCH 053/126] Adjust tab styles and remove unused classes --- src/style/BranchHeaderStyle.ts | 62 +++------------------------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/src/style/BranchHeaderStyle.ts b/src/style/BranchHeaderStyle.ts index 752aa01e4..9c352473f 100644 --- a/src/style/BranchHeaderStyle.ts +++ b/src/style/BranchHeaderStyle.ts @@ -18,12 +18,8 @@ export const unSelectedHeaderStyle = style({ paddingTop: 'var(--jp-border-width)' }); -export const expandedBranchStyle = style({ - height: '500px' -}); - export const openHistorySideBarButtonStyle = style({ - width: '50px', + width: '50%', flex: 'initial', paddingLeft: '10px', paddingRight: '10px', @@ -38,57 +34,9 @@ export const historyLabelStyle = style({ fontWeight: 'normal' }); -export const branchLabelStyle = style({ - fontSize: 'var(--jp-ui-font-size1)', - marginTop: '5px', - marginBottom: '5px', - display: 'inline-block' -}); - -export const branchTrackingLabelStyle = style({ - fontSize: 'var(--jp-ui-font-size1)', - marginTop: '5px', - marginBottom: '5px', - display: 'inline-block', - color: 'var(--jp-ui-font-color2)', - fontWeight: 'normal' -}); - -export const branchDropdownButtonStyle = style({ - backgroundImage: 'var(--jp-icon-arrow-down)', - backgroundSize: '100%', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - height: '18px', - width: '18px', - display: 'inline-block', - verticalAlign: 'middle' -}); - -export const newBranchButtonStyle = style({ - backgroundImage: 'var(--jp-icon-plus)', - backgroundSize: '100%', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - height: '18px', - width: '18px', - display: 'inline-block', - verticalAlign: 'middle' -}); - -export const headerButtonDisabledStyle = style({ - opacity: 0.5 -}); - -export const branchListItemStyle = style({ - listStyle: 'none', - textAlign: 'left', - marginLeft: '1em', - color: 'var(--jp-ui-font-color1)' -}); - export const branchHeaderCenterContent = style({ - paddingLeft: '5px', - paddingRight: '5px', - flex: 'auto' + paddingLeft: '10px', + paddingRight: '10px', + flex: 'auto', + width: '50%' }); From fbc96881895aa1b1af225785ea0306cd55e88162 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 11:03:32 -0800 Subject: [PATCH 054/126] Switch tab order --- src/components/BranchHeader.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/BranchHeader.tsx b/src/components/BranchHeader.tsx index a6c6ff2fc..a1222f359 100644 --- a/src/components/BranchHeader.tsx +++ b/src/components/BranchHeader.tsx @@ -55,25 +55,25 @@ export class BranchHeader extends React.Component<
    this.props.toggleSidebar() + ? () => this.props.toggleSidebar() + : null } - title={'Show commit history'} > -

    History

    +

    Changes

    this.props.toggleSidebar() - : null + ? null + : () => this.props.toggleSidebar() } + title={'Show commit history'} > -

    Changes

    +

    History

    From 51922935d86a50961c6b82774b16e277d1429899 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 14:11:41 -0800 Subject: [PATCH 055/126] Refactor panel tabs --- src/components/BranchHeader.tsx | 82 ---------------------------- src/components/GitPanel.tsx | 97 +++++++++++++++++++++++---------- src/style/BranchHeaderStyle.ts | 42 -------------- src/style/GitPanel.ts | 56 +++++++++++++++++++ src/style/GitPanelStyle.ts | 19 ------- 5 files changed, 123 insertions(+), 173 deletions(-) delete mode 100644 src/components/BranchHeader.tsx delete mode 100644 src/style/BranchHeaderStyle.ts create mode 100644 src/style/GitPanel.ts delete mode 100644 src/style/GitPanelStyle.ts diff --git a/src/components/BranchHeader.tsx b/src/components/BranchHeader.tsx deleted file mode 100644 index a1222f359..000000000 --- a/src/components/BranchHeader.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react'; -import { classes } from 'typestyle'; -import { - branchHeaderCenterContent, - branchStyle, - historyLabelStyle, - openHistorySideBarButtonStyle, - selectedHeaderStyle, - unSelectedHeaderStyle -} from '../style/BranchHeaderStyle'; -import { Git, IGitExtension } from '../tokens'; - -export interface IBranchHeaderState {} - -export interface IBranchHeaderProps { - model: IGitExtension; - currentBranch: string; - upstreamBranch: string; - stagedFiles: Git.IStatusFileResult[]; - data: Git.IBranch[]; - refresh: () => Promise; - toggleSidebar: () => void; - sideBarExpanded: boolean; -} - -export class BranchHeader extends React.Component< - IBranchHeaderProps, - IBranchHeaderState -> { - constructor(props: IBranchHeaderProps) { - super(props); - this.state = {}; - } - - toggleSelect() { - this.props.refresh(); - } - - getHistoryHeaderStyle() { - if (this.props.sideBarExpanded) { - return classes(openHistorySideBarButtonStyle, selectedHeaderStyle); - } - return classes(unSelectedHeaderStyle, openHistorySideBarButtonStyle); - } - - getBranchHeaderStyle() { - if (this.props.sideBarExpanded) { - return classes(branchHeaderCenterContent, unSelectedHeaderStyle); - } - return classes(selectedHeaderStyle, branchHeaderCenterContent); - } - - render() { - return ( -
    -
    -
    this.props.toggleSidebar() - : null - } - > -

    Changes

    -
    -
    this.props.toggleSidebar() - } - title={'Show commit history'} - > -

    History

    -
    -
    -
    - ); - } -} diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 26ba5bad8..26bd782ce 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -1,18 +1,23 @@ import * as React from 'react'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; import { showErrorMessage, showDialog } from '@jupyterlab/apputils'; import { ISettingRegistry } from '@jupyterlab/coreutils'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { JSONObject } from '@phosphor/coreutils'; import { GitExtension } from '../model'; import { - findRepoButtonStyle, - panelContainerStyle, - panelWarningStyle -} from '../style/GitPanelStyle'; + panelWrapperClass, + repoButtonClass, + selectedTabClass, + tabClass, + tabsClass, + tabIndicatorClass, + warningWrapperClass +} from '../style/GitPanel'; import { Git } from '../tokens'; import { decodeStage } from '../utils'; import { GitAuthorForm } from '../widgets/AuthorBox'; -import { BranchHeader } from './BranchHeader'; import { FileList } from './FileList'; import { HistorySideBar } from './HistorySideBar'; import { Toolbar } from './Toolbar'; @@ -24,7 +29,6 @@ export interface IGitSessionNodeState { branches: Git.IBranch[]; currentBranch: string; - upstreamBranch: string; pastCommits: Git.ISingleCommitInfo[]; @@ -33,6 +37,7 @@ export interface IGitSessionNodeState { untrackedFiles: Git.IStatusFileResult[]; isHistoryVisible: boolean; + tab: number; } /** Interface for GitPanel component props */ @@ -53,12 +58,12 @@ export class GitPanel extends React.Component< inGitRepository: false, branches: [], currentBranch: '', - upstreamBranch: '', pastCommits: [], stagedFiles: [], unstagedFiles: [], untrackedFiles: [], - isHistoryVisible: false + isHistoryVisible: false, + tab: 0 }; props.model.repositoryChanged.connect((_, args) => { @@ -88,8 +93,7 @@ export class GitPanel extends React.Component< this.setState({ branches: this.props.model.branches, - currentBranch: currentBranch ? currentBranch.name : 'master', - upstreamBranch: currentBranch ? currentBranch.upstream : '' + currentBranch: currentBranch ? currentBranch.name : 'master' }); }; @@ -157,13 +161,6 @@ export class GitPanel extends React.Component< } }; - toggleSidebar = (): void => { - if (!this.state.isHistoryVisible) { - this.refreshHistory(); - } - this.setState({ isHistoryVisible: !this.state.isHistoryVisible }); - }; - /** * Commits all marked files. * @@ -255,25 +252,45 @@ export class GitPanel extends React.Component< if (this.state.inGitRepository) { main = ( - + + + + {sub} ); } else { main = ( -
    +
    You aren’t in a git repository.
    -
    - ); - } + }; + /** + * Renders a panel for prompting a user to find a Git repository. + * + * @returns fragment + */ + private _renderWarning = () => { return ( -
    - { - await this.refreshBranch(); - if (this.state.isHistoryVisible) { - this.refreshHistory(); - } else { - this.refreshStatus(); - } - }} - /> - {main} +
    +
    Unable to detect a Git repository.
    +
    ); - } + }; /** * Callback invoked upon changing the active panel tab. @@ -340,6 +371,20 @@ export class GitPanel extends React.Component< }); }; + /** + * Callback invoked upon refreshing a repository. + * + * @returns promise which refreshes a repository + */ + private _onRefresh = async () => { + await this.refreshBranch(); + if (this.state.isHistoryVisible) { + this.refreshHistory(); + } else { + this.refreshStatus(); + } + }; + /** * List of modified files (both staged and unstaged). */ @@ -403,7 +448,6 @@ export class GitPanel extends React.Component< throw new Error('Failed to set your identity. ' + error.message); } } - return Promise.resolve(true); } From 018f7634cce9121d9a6160673b5a267a63dec668 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 15:22:54 -0800 Subject: [PATCH 057/126] Remove comments --- tests/test-components/Toolbar.spec.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test-components/Toolbar.spec.tsx b/tests/test-components/Toolbar.spec.tsx index 0debc3e93..70a92b923 100644 --- a/tests/test-components/Toolbar.spec.tsx +++ b/tests/test-components/Toolbar.spec.tsx @@ -57,10 +57,8 @@ describe('Toolbar', function() { }); it('should have all buttons', function() { - // When const node = shallow(); - // Then const buttons = node.find('button'); expect(buttons).toHaveLength(3); expect(buttons.find(`.${pullButtonClass}`)).toHaveLength(1); @@ -75,14 +73,10 @@ describe('Toolbar', function() { }); it('should call API on button click', function() { - // Given const spyPull = jest.spyOn(GitExtension.prototype, 'pull'); const spyPush = jest.spyOn(GitExtension.prototype, 'push'); - // When const node = shallow(); - - // Then const buttons = node.find('button'); buttons.find(`.${pullButtonClass}`).simulate('click'); From 8953cc38a0cd03a9cf5c69db4c8dd462b65351d9 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 17:23:46 -0800 Subject: [PATCH 058/126] Set title attribute --- src/components/BranchMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 87715a2f2..51393011a 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -145,6 +145,7 @@ export class BranchMenu extends React.Component< return ( Date: Mon, 6 Jan 2020 17:31:25 -0800 Subject: [PATCH 059/126] Add title attribute tests --- tests/test-components/CommitBox.spec.tsx | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test-components/CommitBox.spec.tsx b/tests/test-components/CommitBox.spec.tsx index b0016aab8..5bb2b5e59 100644 --- a/tests/test-components/CommitBox.spec.tsx +++ b/tests/test-components/CommitBox.spec.tsx @@ -42,6 +42,16 @@ describe('CommitBox', () => { expect(node.prop('placeholder')).toEqual('Summary (required)'); }); + it('should set a `title` attribute on the input element to provide a commit message summary', () => { + const props = { + onCommit: async () => {}, + hasFiles: false + }; + const component = shallow(); + const node = component.find('input[type="text"]').first(); + expect(node.prop('title').length > 0).toEqual(true); + }); + it('should display placeholder text for the commit message description', () => { const props = { onCommit: async () => {}, @@ -52,6 +62,16 @@ describe('CommitBox', () => { expect(node.prop('placeholder')).toEqual('Description'); }); + it('should set a `title` attribute on the input element to provide a commit message description', () => { + const props = { + onCommit: async () => {}, + hasFiles: false + }; + const component = shallow(); + const node = component.find('TextareaAutosize').first(); + expect(node.prop('title').length > 0).toEqual(true); + }); + it('should display a button to commit changes', () => { const props = { onCommit: async () => {}, @@ -62,6 +82,16 @@ describe('CommitBox', () => { expect(node.prop('value')).toEqual('Commit'); }); + it('should set a `title` attribute on the button to commit changes', () => { + const props = { + onCommit: async () => {}, + hasFiles: false + }; + const component = shallow(); + const node = component.find('input[type="button"]').first(); + expect(node.prop('title').length > 0).toEqual(true); + }); + it('should apply a class to disable the commit button when no files have changes to commit', () => { const props = { onCommit: async () => {}, From 0a0c636bb19a2c23d0bcedd92fb8b1d3af4d9100 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 17:48:36 -0800 Subject: [PATCH 060/126] Add linebreaks From 63f2aa305f6715d423a23f341ac1e072732aff56 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 6 Jan 2020 21:40:18 -0800 Subject: [PATCH 061/126] Rename file and refactor tests --- tests/test-components/BranchHeader.spec.tsx | 130 -------- tests/test-components/BranchMenu.spec.tsx | 340 ++++++++++++++++++++ 2 files changed, 340 insertions(+), 130 deletions(-) delete mode 100644 tests/test-components/BranchHeader.spec.tsx create mode 100644 tests/test-components/BranchMenu.spec.tsx diff --git a/tests/test-components/BranchHeader.spec.tsx b/tests/test-components/BranchHeader.spec.tsx deleted file mode 100644 index 348ef3b65..000000000 --- a/tests/test-components/BranchHeader.spec.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import 'jest'; -import { - BranchHeader, - IBranchHeaderProps -} from '../../src/components/BranchHeader'; -import { branchStyle } from '../../src/style/BranchHeaderStyle'; -import { GitExtension } from '../../src/model'; -import * as git from '../../src/git'; - -jest.mock('../../src/git'); - -describe('BranchHeader', () => { - const props: IBranchHeaderProps = { - model: null, - currentBranch: 'master', - sideBarExpanded: false, - upstreamBranch: 'origin/master', - stagedFiles: [ - { x: '', y: '', to: '', from: 'test-1' }, - { x: '', y: '', to: '', from: 'test-2' } - ], - data: [ - { - is_current_branch: true, - is_remote_branch: false, - name: 'master', - upstream: '', - top_commit: '', - tag: '' - }, - { - is_current_branch: true, - is_remote_branch: false, - name: 'feature-1', - upstream: '', - top_commit: '', - tag: '' - }, - { - is_current_branch: true, - is_remote_branch: false, - name: 'feature-2', - upstream: '', - top_commit: '', - tag: '' - }, - { - is_current_branch: true, - is_remote_branch: false, - name: 'patch-007', - upstream: '', - top_commit: '', - tag: '' - } - ], - refresh: () => Promise.resolve(), - disabled: false, - toggleSidebar: function() { - return true; - } - }; - - describe('#constructor()', () => { - let branchHeader: BranchHeader = null; - - beforeEach(async () => { - const fakeRoot = '/foo'; - const mockGit = git as jest.Mocked; - mockGit.httpGitRequest.mockImplementation((url, method, request) => { - if (url === '/git/server_root') { - return Promise.resolve( - new Response( - JSON.stringify({ - server_root: fakeRoot - }) - ) - ); - } - }); - props.model = new GitExtension(); - - branchHeader = new BranchHeader(props); - }); - - it('should construct a new branch header', () => { - expect(branchHeader).toBeInstanceOf(BranchHeader); - }); - - it('should set default values correctly', () => { - expect(branchHeader.state.dropdownOpen).toEqual(false); - expect(branchHeader.state.showNewBranchBox).toEqual(false); - }); - }); - - describe('#switchBranch()', () => { - const branchHeader = new BranchHeader(props); - it('should switch to specified branch', () => { - const spy = jest.spyOn(GitExtension.prototype, 'checkout'); - branchHeader.switchBranch('new-feature'); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith({ - branchname: 'new-feature' // Branch name - }); - spy.mockRestore(); - }); - }); - - describe('#createNewBranch()', () => { - const branchHeader = new BranchHeader(props); - it('should create and checkout new branch', () => { - const spy = jest.spyOn(GitExtension.prototype, 'checkout'); - branchHeader.createNewBranch('new-feature'); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith({ - newBranch: true, // Create new branch if it doesn't exist - branchname: 'new-feature' // Branch name - }); - spy.mockRestore(); - }); - }); - - describe('#getBranchStyle()', () => { - it('should return correct branch style without drop down state', () => { - const branchHeader = new BranchHeader(props); - let actual = branchHeader.getBranchStyle(); - let expected = branchStyle; - expect(actual).toEqual(expected); - }); - }); -}); diff --git a/tests/test-components/BranchMenu.spec.tsx b/tests/test-components/BranchMenu.spec.tsx new file mode 100644 index 000000000..5008cc97f --- /dev/null +++ b/tests/test-components/BranchMenu.spec.tsx @@ -0,0 +1,340 @@ +import * as React from 'react'; +import 'jest'; +import { shallow } from 'enzyme'; +import { GitExtension } from '../../src/model'; +import * as git from '../../src/git'; +import { listItemClass } from '../../src/style/BranchMenu'; +import { BranchMenu } from '../../src/components/BranchMenu'; + +jest.mock('../../src/git'); + +const BRANCHES = [ + { + is_current_branch: true, + is_remote_branch: false, + name: 'master', + upstream: '', + top_commit: '', + tag: '' + }, + { + is_current_branch: true, + is_remote_branch: false, + name: 'feature-1', + upstream: '', + top_commit: '', + tag: '' + }, + { + is_current_branch: true, + is_remote_branch: false, + name: 'feature-2', + upstream: '', + top_commit: '', + tag: '' + }, + { + is_current_branch: true, + is_remote_branch: false, + name: 'patch-007', + upstream: '', + top_commit: '', + tag: '' + } +]; + +function request(url: string, method: string, request: Object | null) { + if (url === '/git/server_root') { + const res = new Response( + JSON.stringify({ + server_root: '/foo' + }) + ); + return Promise.resolve(res); + } +} + +async function createModel() { + const model = new GitExtension(); + + jest.spyOn(model, 'branches', 'get').mockReturnValue(BRANCHES); + jest.spyOn(model, 'currentBranch', 'get').mockReturnValue(BRANCHES[0]); + + await model.ready; + return model; +} + +describe('BranchMenu', () => { + describe('constructor', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should return a new instance', () => { + const props = { + model: model, + branching: false + }; + const menu = new BranchMenu(props); + expect(menu).toBeInstanceOf(BranchMenu); + }); + + it('should set the default menu filter to an empty string', () => { + const props = { + model: model, + branching: false + }; + const menu = new BranchMenu(props); + expect(menu.state.filter).toEqual(''); + }); + + it('should set the default flag indicating whether to show a dialog to create a new branch to `false`', () => { + const props = { + model: model, + branching: false + }; + const menu = new BranchMenu(props); + expect(menu.state.branchDialog).toEqual(false); + }); + }); + + describe('render', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should display placeholder text for the menu filter', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(); + const node = component.find('input[type="text"]').first(); + expect(node.prop('placeholder')).toEqual('Filter'); + }); + + it('should set a `title` attribute on the input element to filter a branch menu', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(); + const node = component.find('input[type="text"]').first(); + expect(node.prop('title').length > 0).toEqual(true); + }); + + it('should display a button to clear the menu filter once a filter is provided', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(); + component.setState({ + filter: 'foo' + }); + const nodes = component.find('ClearIcon'); + expect(nodes.length).toEqual(1); + }); + + it('should set a `title` on the button to clear the menu filter', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(); + component.setState({ + filter: 'foo' + }); + const html = component + .find('ClearIcon') + .first() + .html(); + expect(html.includes('')).toEqual(true); + }); + + it('should display a button to create a new branch', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const node = component.find('input[type="button"]').first(); + expect(node.prop('value')).toEqual('New Branch'); + }); + + it('should set a `title` attribute on the button to create a new branch', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const node = component.find('input[type="button"]').first(); + expect(node.prop('title').length > 0).toEqual(true); + }); + + it('should display a list of branches', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const nodes = component.find(`.${listItemClass}`); + + const branches = model.branches; + expect(nodes.length).toEqual(branches.length); + + // Should contain the branch names... + for (let i = 0; i < branches.length; i++) { + expect( + nodes + .at(i) + .text() + .includes(branches[i].name) + ).toEqual(true); + } + }); + + it('should set a `title` attribute for each displayed branch', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const nodes = component.find(`.${listItemClass}`); + + const branches = model.branches; + expect(nodes.length).toEqual(branches.length); + + for (let i = 0; i < branches.length; i++) { + expect(nodes.at(i).prop('title').length > 0).toEqual(true); + } + }); + + it('should not, by default, show a dialog to create a new branch', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const node = component.find('NewBranchDialog').first(); + expect(node.prop('open')).toEqual(false); + }); + + it('should show a dialog to create a new branch when the flag indicating whether to show the dialog is `true`', () => { + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + component.setState({ + branchDialog: true + }); + const node = component.find('NewBranchDialog').first(); + expect(node.prop('open')).toEqual(true); + }); + }); + + describe('switch branch', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should not switch to a specified branch upon clicking its corresponding element when branching is disabled', () => { + const spy = jest.spyOn(GitExtension.prototype, 'checkout'); + + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + const nodes = component.find(`.${listItemClass}`); + + const node = nodes.at(1); + node.simulate('click'); + + expect(spy).toHaveBeenCalledTimes(0); + spy.mockRestore(); + }); + + it('should not switch to a specified branch upon clicking its corresponding element when branching is enabled', () => { + const spy = jest.spyOn(GitExtension.prototype, 'checkout'); + + const props = { + model: model, + branching: true + }; + const component = shallow(<BranchMenu {...props} />); + const nodes = component.find(`.${listItemClass}`); + + const node = nodes.at(1); + node.simulate('click'); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith({ + branchname: BRANCHES[1].name + }); + + spy.mockRestore(); + }); + }); + + describe('create branch', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should not allow creating a new branch when branching is disabled', () => { + const spy = jest.spyOn(GitExtension.prototype, 'checkout'); + + const props = { + model: model, + branching: false + }; + const component = shallow(<BranchMenu {...props} />); + + const node = component.find('input[type="button"]').first(); + node.simulate('click'); + + expect(component.state('branchDialog')).toEqual(false); + expect(spy).toHaveBeenCalledTimes(0); + spy.mockRestore(); + }); + + it('should display a dialog to create a new branch when branching is enabled and the new branch button is clicked', () => { + const spy = jest.spyOn(GitExtension.prototype, 'checkout'); + + const props = { + model: model, + branching: true + }; + const component = shallow(<BranchMenu {...props} />); + + const node = component.find('input[type="button"]').first(); + node.simulate('click'); + + expect(component.state('branchDialog')).toEqual(true); + expect(spy).toHaveBeenCalledTimes(0); + spy.mockRestore(); + }); + }); +}); From 8edacf4bcb359a837e5e0df1e397db80ed036640 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Mon, 6 Jan 2020 22:03:51 -0800 Subject: [PATCH 062/126] Refactor and add tests --- tests/test-components/Toolbar.spec.tsx | 323 +++++++++++++++++++------ 1 file changed, 252 insertions(+), 71 deletions(-) diff --git a/tests/test-components/Toolbar.spec.tsx b/tests/test-components/Toolbar.spec.tsx index 70a92b923..5886c61ed 100644 --- a/tests/test-components/Toolbar.spec.tsx +++ b/tests/test-components/Toolbar.spec.tsx @@ -1,90 +1,271 @@ import * as React from 'react'; +import 'jest'; import { shallow } from 'enzyme'; -import { IToolbarProps, Toolbar } from '../../src/components/Toolbar'; +import { GitExtension } from '../../src/model'; +import * as git from '../../src/git'; +import { Toolbar } from '../../src/components/Toolbar'; import { pullButtonClass, pushButtonClass, refreshButtonClass } from '../../src/style/Toolbar'; -import 'jest'; -import { GitExtension } from '../../src/model'; -import * as git from '../../src/git'; jest.mock('../../src/git'); -describe('Toolbar', function() { - let props: IToolbarProps; - - beforeEach(async () => { - const fakePath = '/path/to/repo'; - const fakeRoot = '/foo'; - const mockGit = git as jest.Mocked<typeof git>; - mockGit.httpGitRequest.mockImplementation((url, method, request) => { - let response: Response; - switch (url) { - case '/git/show_top_level': - response = new Response( - JSON.stringify({ - code: 0, - top_repo_path: (request as any)['current_path'] - }) - ); - break; - case '/git/server_root': - response = new Response( - JSON.stringify({ - server_root: fakeRoot - }) - ); - break; - default: - response = new Response( - `{"message": "No mock implementation for ${url}."}`, - { status: 404 } - ); - } - return Promise.resolve(response); - }); - - const model = new GitExtension(); - model.pathRepository = fakePath; - await model.ready; - - props = { - model: model, - refresh: async () => {} - }; +async function createModel() { + const model = new GitExtension(); + + jest.spyOn(model, 'currentBranch', 'get').mockReturnValue({ + is_current_branch: true, + is_remote_branch: false, + name: 'master', + upstream: '', + top_commit: '', + tag: '' + }); + model.pathRepository = '/path/to/repo'; + + await model.ready; + return model; +} + +function request(url: string, method: string, request: Object | null) { + let response: Response; + switch (url) { + case '/git/show_top_level': + response = new Response( + JSON.stringify({ + code: 0, + top_repo_path: (request as any)['current_path'] + }) + ); + break; + case '/git/server_root': + response = new Response( + JSON.stringify({ + server_root: '/foo' + }) + ); + break; + default: + response = new Response( + `{"message": "No mock implementation for ${url}."}`, + { status: 404 } + ); + } + return Promise.resolve(response); +} + +describe('Toolbar', () => { + describe('constructor', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should return a new instance', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const el = new Toolbar(props); + expect(el).toBeInstanceOf(Toolbar); + }); + + it('should set the default flag indicating whether to show a branch menu to `false`', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const el = new Toolbar(props); + expect(el.state.branchMenu).toEqual(false); + }); + + it('should set the default flag indicating whether to show a repository menu to `false`', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const el = new Toolbar(props); + expect(el.state.repoMenu).toEqual(false); + }); + }); + + describe('render', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should display a button to pull the latest changes', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const nodes = node.find(`.${pullButtonClass}`); + + expect(nodes.length).toEqual(1); + }); + + it('should set the `title` attribute on the button to pull the latest changes', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${pullButtonClass}`).first(); + + expect(button.prop('title')).toEqual('Pull latest changes'); + }); + + it('should display a button to push the latest changes', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const nodes = node.find(`.${pushButtonClass}`); + + expect(nodes.length).toEqual(1); + }); + + it('should set the `title` attribute on the button to push the latest changes', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${pushButtonClass}`).first(); + + expect(button.prop('title')).toEqual('Push committed changes'); + }); + + it('should display a button to refresh the current repository', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const nodes = node.find(`.${refreshButtonClass}`); + + expect(nodes.length).toEqual(1); + }); + + it('should set the `title` attribute on the button to refresh the current repository', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${refreshButtonClass}`).first(); + + expect(button.prop('title')).toEqual( + 'Refresh the repository to detect local and remote changes' + ); + }); }); - it('should have all buttons', function() { - const node = shallow(<Toolbar {...props} />); - - const buttons = node.find('button'); - expect(buttons).toHaveLength(3); - expect(buttons.find(`.${pullButtonClass}`)).toHaveLength(1); - expect(buttons.find(`.${pullButtonClass}`).prop('title')).toEqual( - 'Pull latest changes' - ); - expect(buttons.find(`.${pushButtonClass}`)).toHaveLength(1); - expect(buttons.find(`.${pushButtonClass}`).prop('title')).toEqual( - 'Push committed changes' - ); - expect(buttons.find(`.${refreshButtonClass}`)).toHaveLength(1); + describe('pull changes', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should pull changes when the button to pull the latest changes is clicked', () => { + const spy = jest.spyOn(GitExtension.prototype, 'pull'); + + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${pullButtonClass}`); + + button.simulate('click'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(undefined); + + spy.mockRestore(); + }); + }); + + describe('push changes', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should push changes when the button to push the latest changes is clicked', () => { + const spy = jest.spyOn(GitExtension.prototype, 'push'); + + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${pushButtonClass}`); + + button.simulate('click'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(undefined); + + spy.mockRestore(); + }); }); - it('should call API on button click', function() { - const spyPull = jest.spyOn(GitExtension.prototype, 'pull'); - const spyPush = jest.spyOn(GitExtension.prototype, 'push'); + describe('refresh repository', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); - const node = shallow(<Toolbar {...props} />); - const buttons = node.find('button'); + model = await createModel(); + }); + + it('should refresh the repository when the button to refresh the repository is clicked', () => { + const spy = jest.fn(async () => {}); - buttons.find(`.${pullButtonClass}`).simulate('click'); - expect(spyPull).toHaveBeenCalledTimes(1); - expect(spyPull).toHaveBeenCalledWith(undefined); + const props = { + model: model, + branching: false, + refresh: spy + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${refreshButtonClass}`); - buttons.find(`.${pushButtonClass}`).simulate('click'); - expect(spyPush).toHaveBeenCalledTimes(1); - expect(spyPush).toHaveBeenCalledWith(undefined); + button.simulate('click'); + expect(spy).toHaveBeenCalledTimes(1); + + spy.mockRestore(); + }); }); }); From ad66d44ed43953e1dd4257baf3343d6cee4a5920 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Mon, 6 Jan 2020 22:12:43 -0800 Subject: [PATCH 063/126] Split rendering logic into separate private methods --- src/components/Toolbar.tsx | 190 +++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 81 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 6119fabb8..cf2454c9d 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -129,103 +129,131 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * @returns fragment */ render() { - const repo = this.props.model.pathRepository || ''; - const branch = repo ? this.props.model.currentBranch.name : ''; return ( <div className={toolbarClass}> - <div className={toolbarNavClass}> - <button + {this._renderTopNav()} + {this._renderRepoMenu()} + {this._renderBranchMenu()} + </div> + ); + } + + /** + * Renders the top navigation. + * + * @returns fragment + */ + private _renderTopNav = () => { + return ( + <div className={toolbarNavClass}> + <button + className={classes(toolbarButtonClass, pullButtonClass, 'jp-Icon-16')} + title={'Pull latest changes'} + onClick={this._onPullClick} + /> + <button + className={classes(toolbarButtonClass, pushButtonClass, 'jp-Icon-16')} + title={'Push committed changes'} + onClick={this._onPushClick} + /> + <button + className={classes( + toolbarButtonClass, + refreshButtonClass, + 'jp-Icon-16' + )} + title={'Refresh the repository to detect local and remote changes'} + onClick={this._onRefreshClick} + /> + </div> + ); + }; + + /** + * Renders a repository menu. + * + * @returns fragment + */ + private _renderRepoMenu = () => { + const repo = this.props.model.pathRepository || ''; + return ( + <div className={toolbarMenuWrapperClass}> + <button + className={toolbarMenuButtonClass} + title={`Current repository: ${repo}`} + onClick={this._onRepositoryClick} + > + <span + className={classes( + toolbarMenuButtonIconClass, + repoIconClass, + 'jp-Icon-16' + )} + /> + <div className={toolbarMenuButtonTitleWrapperClass}> + <p className={toolbarMenuButtonTitleClass}>Current Repository</p> + <p className={toolbarMenuButtonSubtitleClass}> + {PathExt.basename(repo)} + </p> + </div> + <span className={classes( - toolbarButtonClass, - pullButtonClass, + toolbarMenuButtonIconClass, + this.state.repoMenu ? closeMenuIconClass : openMenuIconClass, 'jp-Icon-16' )} - title={'Pull latest changes'} - onClick={this._onPullClick} /> - <button + </button> + {this.state.repoMenu ? null : null} + </div> + ); + }; + + /** + * Renders a branch menu. + * + * @returns fragment + */ + private _renderBranchMenu = () => { + if (!this.props.model.pathRepository) { + return null; + } + const branch = this.props.model.currentBranch.name || ''; + return ( + <div className={toolbarMenuWrapperClass}> + <button + className={toolbarMenuButtonClass} + title={`Change the current branch: ${branch}`} + onClick={this._onBranchClick} + > + <span className={classes( - toolbarButtonClass, - pushButtonClass, + toolbarMenuButtonIconClass, + branchIconClass, 'jp-Icon-16' )} - title={'Push committed changes'} - onClick={this._onPushClick} /> - <button + <div className={toolbarMenuButtonTitleWrapperClass}> + <p className={toolbarMenuButtonTitleClass}>Current Branch</p> + <p className={toolbarMenuButtonSubtitleClass}>{branch}</p> + </div> + <span className={classes( - toolbarButtonClass, - refreshButtonClass, + toolbarMenuButtonIconClass, + this.state.branchMenu ? closeMenuIconClass : openMenuIconClass, 'jp-Icon-16' )} - title={'Refresh the repository to detect local and remote changes'} - onClick={this._onRefreshClick} /> - </div> - <div className={toolbarMenuWrapperClass}> - <button - className={toolbarMenuButtonClass} - title={`Current repository: ${repo}`} - onClick={this._onRepositoryClick} - > - <span - className={classes( - toolbarMenuButtonIconClass, - repoIconClass, - 'jp-Icon-16' - )} - /> - <div className={toolbarMenuButtonTitleWrapperClass}> - <p className={toolbarMenuButtonTitleClass}>Current Repository</p> - <p className={toolbarMenuButtonSubtitleClass}> - {PathExt.basename(repo)} - </p> - </div> - <span - className={classes( - toolbarMenuButtonIconClass, - this.state.repoMenu ? closeMenuIconClass : openMenuIconClass, - 'jp-Icon-16' - )} - /> - </button> - {this.state.repoMenu ? null : null} - </div> - <div className={toolbarMenuWrapperClass}> - <button - className={toolbarMenuButtonClass} - title={`Change the current branch: ${branch}`} - onClick={this._onBranchClick} - > - <span - className={classes( - toolbarMenuButtonIconClass, - branchIconClass, - 'jp-Icon-16' - )} - /> - <div className={toolbarMenuButtonTitleWrapperClass}> - <p className={toolbarMenuButtonTitleClass}>Current Branch</p> - <p className={toolbarMenuButtonSubtitleClass}>{branch}</p> - </div> - <span - className={classes( - toolbarMenuButtonIconClass, - this.state.branchMenu ? closeMenuIconClass : openMenuIconClass, - 'jp-Icon-16' - )} - /> - </button> - {this.state.branchMenu ? ( - <BranchMenu - model={this.props.model} - branching={this.props.branching} - /> - ) : null} - </div> + </button> + {this.state.branchMenu ? ( + <BranchMenu + model={this.props.model} + branching={this.props.branching} + /> + ) : null} </div> ); - } + }; /** * Callback invoked upon a change to the repository path. From a484cf9d41e02d4c38867e76764e5e1d420ffedf Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Mon, 6 Jan 2020 22:40:20 -0800 Subject: [PATCH 064/126] Add tests --- tests/test-components/Toolbar.spec.tsx | 56 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/test-components/Toolbar.spec.tsx b/tests/test-components/Toolbar.spec.tsx index 5886c61ed..2bca5a7e0 100644 --- a/tests/test-components/Toolbar.spec.tsx +++ b/tests/test-components/Toolbar.spec.tsx @@ -7,7 +7,8 @@ import { Toolbar } from '../../src/components/Toolbar'; import { pullButtonClass, pushButtonClass, - refreshButtonClass + refreshButtonClass, + toolbarMenuButtonClass } from '../../src/style/Toolbar'; jest.mock('../../src/git'); @@ -181,6 +182,59 @@ describe('Toolbar', () => { 'Refresh the repository to detect local and remote changes' ); }); + + it('should display a button to toggle a repository menu', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${toolbarMenuButtonClass}`).first(); + + const text = button.text(); + expect(text.includes('Current Repository')).toEqual(true); + }); + + it('should set the `title` attribute on the button to toggle a repository menu', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${toolbarMenuButtonClass}`).first(); + + const bool = button.prop('title').includes('Current repository: '); + expect(bool).toEqual(true); + }); + + it('should display a button to toggle a branch menu', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${toolbarMenuButtonClass}`).at(1); + + const text = button.text(); + expect(text.includes('Current Branch')).toEqual(true); + }); + + it('should set the `title` attribute on the button to toggle a branch menu', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${toolbarMenuButtonClass}`).at(1); + + expect(button.prop('title')).toEqual( + `Change the current branch: ${model.currentBranch.name}` + ); + }); }); describe('pull changes', () => { From b1ec4728c215b5af1cadf51b68634e278cb2d328 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Mon, 6 Jan 2020 22:45:31 -0800 Subject: [PATCH 065/126] Add tests --- tests/test-components/Toolbar.spec.tsx | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test-components/Toolbar.spec.tsx b/tests/test-components/Toolbar.spec.tsx index 2bca5a7e0..8e35b7910 100644 --- a/tests/test-components/Toolbar.spec.tsx +++ b/tests/test-components/Toolbar.spec.tsx @@ -237,6 +237,42 @@ describe('Toolbar', () => { }); }); + describe('branch menu', () => { + let model: GitExtension; + + beforeEach(async () => { + const mock = git as jest.Mocked<typeof git>; + mock.httpGitRequest.mockImplementation(request); + + model = await createModel(); + }); + + it('should not, by default, display a branch menu', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const nodes = node.find('BranchMenu'); + + expect(nodes.length).toEqual(0); + }); + + it('should display a branch menu when the button to display a branch menu is clicked', () => { + const props = { + model: model, + branching: false, + refresh: async () => {} + }; + const node = shallow(<Toolbar {...props} />); + const button = node.find(`.${toolbarMenuButtonClass}`).at(1); + + button.simulate('click'); + expect(node.find('BranchMenu').length).toEqual(1); + }); + }); + describe('pull changes', () => { let model: GitExtension; From 6d2ccf7177ddd1d6782ae114cb709a5d55dc1b88 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:08:20 -0800 Subject: [PATCH 066/126] Set state rather than force update --- src/components/Toolbar.tsx | 46 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index cf2454c9d..2c1e5c3f8 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -101,6 +101,16 @@ export interface IToolbarState { * Boolean indicating whether a repository menu is shown. */ repoMenu: boolean; + + /** + * Current repository. + */ + repository: string; + + /** + * Current branch name. + */ + branch: string; } /** @@ -119,7 +129,11 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { this.props.model.headChanged.connect(this._onHeadChange, this); this.state = { branchMenu: false, - repoMenu: false + repoMenu: false, + repository: this.props.model.pathRepository || '', + branch: this.props.model.pathRepository + ? this.props.model.currentBranch.name + : '' }; } @@ -175,12 +189,11 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * @returns fragment */ private _renderRepoMenu = () => { - const repo = this.props.model.pathRepository || ''; return ( <div className={toolbarMenuWrapperClass}> <button className={toolbarMenuButtonClass} - title={`Current repository: ${repo}`} + title={`Current repository: ${this.state.repository}`} onClick={this._onRepositoryClick} > <span @@ -193,7 +206,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { <div className={toolbarMenuButtonTitleWrapperClass}> <p className={toolbarMenuButtonTitleClass}>Current Repository</p> <p className={toolbarMenuButtonSubtitleClass}> - {PathExt.basename(repo)} + {PathExt.basename(this.state.repository)} </p> </div> <span @@ -215,15 +228,14 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * @returns fragment */ private _renderBranchMenu = () => { - if (!this.props.model.pathRepository) { + if (!this.state.repository) { return null; } - const branch = this.props.model.currentBranch.name || ''; return ( <div className={toolbarMenuWrapperClass}> <button className={toolbarMenuButtonClass} - title={`Change the current branch: ${branch}`} + title={`Change the current branch: ${this.state.branch}`} onClick={this._onBranchClick} > <span @@ -235,7 +247,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /> <div className={toolbarMenuButtonTitleWrapperClass}> <p className={toolbarMenuButtonTitleClass}>Current Branch</p> - <p className={toolbarMenuButtonSubtitleClass}>{branch}</p> + <p className={toolbarMenuButtonSubtitleClass}> + {this.state.branch} + </p> </div> <span className={classes( @@ -259,14 +273,26 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * Callback invoked upon a change to the repository path. */ private _onRepositoryChange = () => { - this.forceUpdate(); + this._syncState(); }; /** * Callback invoked upon a change to the current HEAD. */ private _onHeadChange = () => { - this.forceUpdate(); + this._syncState(); + }; + + /** + * Syncs the repository state with the underlying model. + */ + private _syncState = () => { + this.setState({ + repository: this.props.model.pathRepository || '', + branch: this.props.model.pathRepository + ? this.props.model.currentBranch.name + : '' + }); }; /** From 1924375e286fab80501027028d77ea66283c3b62 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:14:48 -0800 Subject: [PATCH 067/126] Listen to model changes --- src/components/BranchMenu.tsx | 41 +++++++++++++++++++++++++++++++---- src/components/Toolbar.tsx | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 51393011a..f859f010a 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -50,6 +50,11 @@ export interface IBranchMenuState { * Boolean indicating whether to show a dialog to create a new branch. */ branchDialog: boolean; + + /** + * Current branch name. + */ + branch: string; } /** @@ -67,9 +72,14 @@ export class BranchMenu extends React.Component< */ constructor(props: IBranchMenuProps) { super(props); + this.props.model.repositoryChanged.connect(this._onRepositoryChange, this); + this.props.model.headChanged.connect(this._onHeadChange, this); this.state = { filter: '', - branchDialog: false + branchDialog: false, + branch: this.props.model.pathRepository + ? this.props.model.currentBranch.name + : '' }; } @@ -148,9 +158,7 @@ export class BranchMenu extends React.Component< title={`Switch to branch: ${branch.name}`} className={classes( listItemClass, - branch.name === this.props.model.currentBranch.name - ? activeListItemClass - : null + branch.name === this.state.branch ? activeListItemClass : null )} key={idx} onClick={this._onBranchClickFactory(branch.name)} @@ -161,6 +169,31 @@ export class BranchMenu extends React.Component< ); }; + /** + * Callback invoked upon a change to the repository path. + */ + private _onRepositoryChange = () => { + this._syncState(); + }; + + /** + * Callback invoked upon a change to the current HEAD. + */ + private _onHeadChange = () => { + this._syncState(); + }; + + /** + * Syncs the component state with the underlying model. + */ + private _syncState = () => { + this.setState({ + branch: this.props.model.pathRepository + ? this.props.model.currentBranch.name + : '' + }); + }; + /** * Callback invoked upon a change to the menu filter. * diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 2c1e5c3f8..b619e8eb3 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -284,7 +284,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { }; /** - * Syncs the repository state with the underlying model. + * Syncs the component state with the underlying model. */ private _syncState = () => { this.setState({ From a8a8b69d040c450f6299234a084d673085c4e3dd Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:19:20 -0800 Subject: [PATCH 068/126] Listen to model changes --- src/components/NewBranchDialog.tsx | 39 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index fa4f2dd75..0bad78d92 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -60,6 +60,11 @@ export interface INewBranchDialogState { * Base branch. */ base: string; + + /** + * Current branch name. + */ + current: string; } /** @@ -77,9 +82,12 @@ export class NewBranchDialog extends React.Component< */ constructor(props: INewBranchDialogProps) { super(props); + this.props.model.repositoryChanged.connect(this._onRepositoryChange, this); + this.props.model.headChanged.connect(this._onHeadChange, this); this.state = { name: '', - base: this.props.model.currentBranch.name + base: this.props.model.currentBranch.name, + current: this.props.model.currentBranch.name }; } @@ -161,7 +169,7 @@ export class NewBranchDialog extends React.Component< */ private _renderItem = (branch: Git.IBranch, idx: number) => { // TODO: consider allowing users to branch from any branch, rather than just the current branch... - if (branch.name !== this.props.model.currentBranch.name) { + if (branch.name !== this.state.current) { return null; } return ( @@ -177,7 +185,7 @@ export class NewBranchDialog extends React.Component< <span className={classes(listItemIconClass, 'jp-Icon-16')} /> <div className={listItemContentClass}> <p className={listItemTitleClass}>{branch.name}</p> - {branch.name === this.props.model.currentBranch.name ? ( + {branch.name === this.state.current ? ( <p className={listItemDescClass}> The current branch. Pick this if you want to build on work done in this branch. @@ -188,6 +196,31 @@ export class NewBranchDialog extends React.Component< ); }; + /** + * Callback invoked upon a change to the repository path. + */ + private _onRepositoryChange = () => { + this._syncState(); + }; + + /** + * Callback invoked upon a change to the current HEAD. + */ + private _onHeadChange = () => { + this._syncState(); + }; + + /** + * Syncs the component state with the underlying model. + */ + private _syncState = () => { + this.setState({ + current: this.props.model.pathRepository + ? this.props.model.currentBranch.name + : '' + }); + }; + /** * Returns a callback which is invoked upon clicking a branch name. * From d9aab8941ae9ac68ab0678271bf2bd07512f00ed Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:35:45 -0800 Subject: [PATCH 069/126] Listen for status changes --- src/components/BranchMenu.tsx | 26 ++++++++++---------------- src/components/NewBranchDialog.tsx | 26 ++++++++++---------------- src/components/Toolbar.tsx | 26 ++++++++++---------------- 3 files changed, 30 insertions(+), 48 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index f859f010a..af89b3167 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -72,8 +72,16 @@ export class BranchMenu extends React.Component< */ constructor(props: IBranchMenuProps) { super(props); - this.props.model.repositoryChanged.connect(this._onRepositoryChange, this); - this.props.model.headChanged.connect(this._onHeadChange, this); + + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + this.state = { filter: '', branchDialog: false, @@ -169,20 +177,6 @@ export class BranchMenu extends React.Component< ); }; - /** - * Callback invoked upon a change to the repository path. - */ - private _onRepositoryChange = () => { - this._syncState(); - }; - - /** - * Callback invoked upon a change to the current HEAD. - */ - private _onHeadChange = () => { - this._syncState(); - }; - /** * Syncs the component state with the underlying model. */ diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 0bad78d92..7335ff614 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -82,8 +82,16 @@ export class NewBranchDialog extends React.Component< */ constructor(props: INewBranchDialogProps) { super(props); - this.props.model.repositoryChanged.connect(this._onRepositoryChange, this); - this.props.model.headChanged.connect(this._onHeadChange, this); + + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + this.state = { name: '', base: this.props.model.currentBranch.name, @@ -196,20 +204,6 @@ export class NewBranchDialog extends React.Component< ); }; - /** - * Callback invoked upon a change to the repository path. - */ - private _onRepositoryChange = () => { - this._syncState(); - }; - - /** - * Callback invoked upon a change to the current HEAD. - */ - private _onHeadChange = () => { - this._syncState(); - }; - /** * Syncs the component state with the underlying model. */ diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index b619e8eb3..e82b98eb4 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -125,8 +125,16 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { */ constructor(props: IToolbarProps) { super(props); - this.props.model.repositoryChanged.connect(this._onRepositoryChange, this); - this.props.model.headChanged.connect(this._onHeadChange, this); + + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + this.state = { branchMenu: false, repoMenu: false, @@ -269,20 +277,6 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { ); }; - /** - * Callback invoked upon a change to the repository path. - */ - private _onRepositoryChange = () => { - this._syncState(); - }; - - /** - * Callback invoked upon a change to the current HEAD. - */ - private _onHeadChange = () => { - this._syncState(); - }; - /** * Syncs the component state with the underlying model. */ From 12998469319af259541d7a46d911642f192ed363 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:42:34 -0800 Subject: [PATCH 070/126] Add log statements --- src/components/BranchMenu.tsx | 1 + src/model.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index af89b3167..f45bd592d 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -181,6 +181,7 @@ export class BranchMenu extends React.Component< * Syncs the component state with the underlying model. */ private _syncState = () => { + console.log('SYNCING STATE'); this.setState({ branch: this.props.model.pathRepository ? this.props.model.currentBranch.name diff --git a/src/model.ts b/src/model.ts index e07725ea5..73c1aa271 100644 --- a/src/model.ts +++ b/src/model.ts @@ -973,6 +973,7 @@ export class GitExtension implements IGitExtension { protected _setStatus(v: Git.IStatusFileResult[]) { this._status = v; this._statusChanged.emit(this._status); + console.log('HERE'); } private async _getServerRoot(): Promise<string> { From 68b63214c0740a30847dee77a12081970467c20a Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 16:56:39 -0800 Subject: [PATCH 071/126] Set state to render upon model changes --- src/components/BranchMenu.tsx | 25 +++++++++++++++---------- src/components/NewBranchDialog.tsx | 21 +++++++++++++++------ src/components/Toolbar.tsx | 15 +++++++-------- src/model.ts | 1 - 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index f45bd592d..d27fc66ec 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -54,7 +54,12 @@ export interface IBranchMenuState { /** * Current branch name. */ - branch: string; + current: string; + + /** + * Current list of branches. + */ + branches: Git.IBranch[]; } /** @@ -73,6 +78,8 @@ export class BranchMenu extends React.Component< constructor(props: IBranchMenuProps) { super(props); + const repo = this.props.model.pathRepository; + // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -85,9 +92,8 @@ export class BranchMenu extends React.Component< this.state = { filter: '', branchDialog: false, - branch: this.props.model.pathRepository - ? this.props.model.currentBranch.name - : '' + current: repo ? this.props.model.currentBranch.name : '', + branches: repo ? this.props.model.branches : [] }; } @@ -145,7 +151,7 @@ export class BranchMenu extends React.Component< * @returns fragment array */ private _renderItems = () => { - return this.props.model.branches.map(this._renderItem); + return this.state.branches.map(this._renderItem); }; /** @@ -166,7 +172,7 @@ export class BranchMenu extends React.Component< title={`Switch to branch: ${branch.name}`} className={classes( listItemClass, - branch.name === this.state.branch ? activeListItemClass : null + branch.name === this.state.current ? activeListItemClass : null )} key={idx} onClick={this._onBranchClickFactory(branch.name)} @@ -181,11 +187,10 @@ export class BranchMenu extends React.Component< * Syncs the component state with the underlying model. */ private _syncState = () => { - console.log('SYNCING STATE'); + const repo = this.props.model.pathRepository; this.setState({ - branch: this.props.model.pathRepository - ? this.props.model.currentBranch.name - : '' + current: repo ? this.props.model.currentBranch.name : '', + branches: repo ? this.props.model.branches : [] }); }; diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 7335ff614..cd519b5f3 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -65,6 +65,11 @@ export interface INewBranchDialogState { * Current branch name. */ current: string; + + /** + * Current list of branches. + */ + branches: Git.IBranch[]; } /** @@ -83,6 +88,8 @@ export class NewBranchDialog extends React.Component< constructor(props: INewBranchDialogProps) { super(props); + const repo = this.props.model.pathRepository; + // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -94,8 +101,9 @@ export class NewBranchDialog extends React.Component< this.state = { name: '', - base: this.props.model.currentBranch.name, - current: this.props.model.currentBranch.name + base: repo ? this.props.model.currentBranch.name : '', + current: repo ? this.props.model.currentBranch.name : '', + branches: repo ? this.props.model.branches : [] }; } @@ -165,7 +173,7 @@ export class NewBranchDialog extends React.Component< * @returns fragment array */ private _renderItems = () => { - return this.props.model.branches.map(this._renderItem); + return this.state.branches.map(this._renderItem); }; /** @@ -208,10 +216,11 @@ export class NewBranchDialog extends React.Component< * Syncs the component state with the underlying model. */ private _syncState = () => { + const repo = this.props.model.pathRepository; this.setState({ - current: this.props.model.pathRepository - ? this.props.model.currentBranch.name - : '' + base: repo ? this.props.model.currentBranch.name : '', + current: repo ? this.props.model.currentBranch.name : '', + branches: repo ? this.props.model.branches : [] }); }; diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index e82b98eb4..e3f96a46d 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -126,6 +126,8 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { constructor(props: IToolbarProps) { super(props); + const repo = this.props.model.pathRepository; + // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -138,10 +140,8 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { this.state = { branchMenu: false, repoMenu: false, - repository: this.props.model.pathRepository || '', - branch: this.props.model.pathRepository - ? this.props.model.currentBranch.name - : '' + repository: repo || '', + branch: repo ? this.props.model.currentBranch.name : '' }; } @@ -281,11 +281,10 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * Syncs the component state with the underlying model. */ private _syncState = () => { + const repo = this.props.model.pathRepository; this.setState({ - repository: this.props.model.pathRepository || '', - branch: this.props.model.pathRepository - ? this.props.model.currentBranch.name - : '' + repository: repo || '', + branch: repo ? this.props.model.currentBranch.name : '' }); }; diff --git a/src/model.ts b/src/model.ts index 73c1aa271..e07725ea5 100644 --- a/src/model.ts +++ b/src/model.ts @@ -973,7 +973,6 @@ export class GitExtension implements IGitExtension { protected _setStatus(v: Git.IStatusFileResult[]) { this._status = v; this._statusChanged.emit(this._status); - console.log('HERE'); } private async _getServerRoot(): Promise<string> { From 4afed4a29f9efd8a1df683d471482955e967bccc Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:03:52 -0800 Subject: [PATCH 072/126] Update tests --- tests/test-components/BranchMenu.spec.tsx | 31 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/test-components/BranchMenu.spec.tsx b/tests/test-components/BranchMenu.spec.tsx index 5008cc97f..7145b2129 100644 --- a/tests/test-components/BranchMenu.spec.tsx +++ b/tests/test-components/BranchMenu.spec.tsx @@ -44,14 +44,30 @@ const BRANCHES = [ ]; function request(url: string, method: string, request: Object | null) { - if (url === '/git/server_root') { - const res = new Response( - JSON.stringify({ - server_root: '/foo' - }) - ); - return Promise.resolve(res); + let response: Response; + switch (url) { + case '/git/show_top_level': + response = new Response( + JSON.stringify({ + code: 0, + top_repo_path: (request as any)['current_path'] + }) + ); + break; + case '/git/server_root': + response = new Response( + JSON.stringify({ + server_root: '/foo' + }) + ); + break; + default: + response = new Response( + `{"message": "No mock implementation for ${url}."}`, + { status: 404 } + ); } + return Promise.resolve(response); } async function createModel() { @@ -59,6 +75,7 @@ async function createModel() { jest.spyOn(model, 'branches', 'get').mockReturnValue(BRANCHES); jest.spyOn(model, 'currentBranch', 'get').mockReturnValue(BRANCHES[0]); + model.pathRepository = '/path/to/repo'; await model.ready; return model; From 885cc1eef9b714aa159a23651eb6f2bfbc4a053f Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:13:00 -0800 Subject: [PATCH 073/126] Add debug statements --- src/components/BranchMenu.tsx | 2 ++ src/model.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index d27fc66ec..b25ceb80a 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -187,7 +187,9 @@ export class BranchMenu extends React.Component< * Syncs the component state with the underlying model. */ private _syncState = () => { + console.log('SYNCING STATE'); const repo = this.props.model.pathRepository; + console.log(this.props.model.branches); this.setState({ current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] diff --git a/src/model.ts b/src/model.ts index e07725ea5..57f5de4c4 100644 --- a/src/model.ts +++ b/src/model.ts @@ -973,6 +973,7 @@ export class GitExtension implements IGitExtension { protected _setStatus(v: Git.IStatusFileResult[]) { this._status = v; this._statusChanged.emit(this._status); + console.log('SETTING STATUS'); } private async _getServerRoot(): Promise<string> { From 28462dcd22f9b0d8bdb3f75ee93aae42c48954d7 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:16:31 -0800 Subject: [PATCH 074/126] Remove debug statements --- src/components/BranchMenu.tsx | 2 -- src/model.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index b25ceb80a..d27fc66ec 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -187,9 +187,7 @@ export class BranchMenu extends React.Component< * Syncs the component state with the underlying model. */ private _syncState = () => { - console.log('SYNCING STATE'); const repo = this.props.model.pathRepository; - console.log(this.props.model.branches); this.setState({ current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] diff --git a/src/model.ts b/src/model.ts index 57f5de4c4..e07725ea5 100644 --- a/src/model.ts +++ b/src/model.ts @@ -973,7 +973,6 @@ export class GitExtension implements IGitExtension { protected _setStatus(v: Git.IStatusFileResult[]) { this._status = v; this._statusChanged.emit(this._status); - console.log('SETTING STATUS'); } private async _getServerRoot(): Promise<string> { From 1fafb0a42d8ac394014526e836960fa149adcc6a Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:37:47 -0800 Subject: [PATCH 075/126] Prevent memory leaks by removing event listeners --- src/components/BranchMenu.tsx | 40 +++++++++++++++++++++++------- src/components/NewBranchDialog.tsx | 40 +++++++++++++++++++++++------- src/components/Toolbar.tsx | 40 +++++++++++++++++++++++------- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index d27fc66ec..606e119be 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -79,15 +79,7 @@ export class BranchMenu extends React.Component< super(props); const repo = this.props.model.pathRepository; - - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - - // When the HEAD changes, decent probability that we've switched branches: - this.props.model.headChanged.connect(this._syncState, this); - - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): - this.props.model.statusChanged.connect(this._syncState, this); + this._addListeners(); this.state = { filter: '', @@ -97,6 +89,13 @@ export class BranchMenu extends React.Component< }; } + /** + * Callback invoked when a component will no longer be mounted. + */ + componentWillUnmount() { + this._removeListeners(); + } + /** * Renders the component. * @@ -183,6 +182,29 @@ export class BranchMenu extends React.Component< ); }; + /** + * Adds model listeners. + */ + private _addListeners = () => { + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + }; + + /** + * Removes model listeners. + */ + private _removeListeners = () => { + this.props.model.repositoryChanged.disconnect(this._syncState); + this.props.model.headChanged.disconnect(this._syncState); + this.props.model.statusChanged.disconnect(this._syncState); + }; + /** * Syncs the component state with the underlying model. */ diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index cd519b5f3..6076b013d 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -89,15 +89,7 @@ export class NewBranchDialog extends React.Component< super(props); const repo = this.props.model.pathRepository; - - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - - // When the HEAD changes, decent probability that we've switched branches: - this.props.model.headChanged.connect(this._syncState, this); - - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): - this.props.model.statusChanged.connect(this._syncState, this); + this._addListeners(); this.state = { name: '', @@ -107,6 +99,13 @@ export class NewBranchDialog extends React.Component< }; } + /** + * Callback invoked when a component will no longer be mounted. + */ + componentWillUnmount() { + this._removeListeners(); + } + /** * Renders the component. * @@ -212,6 +211,29 @@ export class NewBranchDialog extends React.Component< ); }; + /** + * Adds model listeners. + */ + private _addListeners = () => { + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + }; + + /** + * Removes model listeners. + */ + private _removeListeners = () => { + this.props.model.repositoryChanged.disconnect(this._syncState); + this.props.model.headChanged.disconnect(this._syncState); + this.props.model.statusChanged.disconnect(this._syncState); + }; + /** * Syncs the component state with the underlying model. */ diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index e3f96a46d..92682b590 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -127,15 +127,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { super(props); const repo = this.props.model.pathRepository; - - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - - // When the HEAD changes, decent probability that we've switched branches: - this.props.model.headChanged.connect(this._syncState, this); - - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): - this.props.model.statusChanged.connect(this._syncState, this); + this._addListeners(); this.state = { branchMenu: false, @@ -145,6 +137,13 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { }; } + /** + * Callback invoked when a component will no longer be mounted. + */ + componentWillUnmount() { + this._removeListeners(); + } + /** * Renders the component. * @@ -277,6 +276,29 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { ); }; + /** + * Adds model listeners. + */ + private _addListeners = () => { + // When the repository changes, we're likely to have a new set of branches: + this.props.model.repositoryChanged.connect(this._syncState, this); + + // When the HEAD changes, decent probability that we've switched branches: + this.props.model.headChanged.connect(this._syncState, this); + + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + this.props.model.statusChanged.connect(this._syncState, this); + }; + + /** + * Removes model listeners. + */ + private _removeListeners = () => { + this.props.model.repositoryChanged.disconnect(this._syncState); + this.props.model.headChanged.disconnect(this._syncState); + this.props.model.statusChanged.disconnect(this._syncState); + }; + /** * Syncs the component state with the underlying model. */ From b2c0430f2a660228bee8f8d599df96de2594469f Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:42:13 -0800 Subject: [PATCH 076/126] Provide the `this` context when removing listeners --- src/components/BranchMenu.tsx | 6 +++--- src/components/NewBranchDialog.tsx | 6 +++--- src/components/Toolbar.tsx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 606e119be..d0565e877 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -200,9 +200,9 @@ export class BranchMenu extends React.Component< * Removes model listeners. */ private _removeListeners = () => { - this.props.model.repositoryChanged.disconnect(this._syncState); - this.props.model.headChanged.disconnect(this._syncState); - this.props.model.statusChanged.disconnect(this._syncState); + this.props.model.repositoryChanged.disconnect(this._syncState, this); + this.props.model.headChanged.disconnect(this._syncState, this); + this.props.model.statusChanged.disconnect(this._syncState, this); }; /** diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 6076b013d..d6dc32bac 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -229,9 +229,9 @@ export class NewBranchDialog extends React.Component< * Removes model listeners. */ private _removeListeners = () => { - this.props.model.repositoryChanged.disconnect(this._syncState); - this.props.model.headChanged.disconnect(this._syncState); - this.props.model.statusChanged.disconnect(this._syncState); + this.props.model.repositoryChanged.disconnect(this._syncState, this); + this.props.model.headChanged.disconnect(this._syncState, this); + this.props.model.statusChanged.disconnect(this._syncState, this); }; /** diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 92682b590..da471a7ed 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -294,9 +294,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * Removes model listeners. */ private _removeListeners = () => { - this.props.model.repositoryChanged.disconnect(this._syncState); - this.props.model.headChanged.disconnect(this._syncState); - this.props.model.statusChanged.disconnect(this._syncState); + this.props.model.repositoryChanged.disconnect(this._syncState, this); + this.props.model.headChanged.disconnect(this._syncState, this); + this.props.model.statusChanged.disconnect(this._syncState, this); }; /** From 1c8efe43c5c77f36fd506bd79d060cdbce9e0ecc Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 7 Jan 2020 17:58:34 -0800 Subject: [PATCH 077/126] Refresh both status and branches on poll --- src/model.ts | 58 +++++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/model.ts b/src/model.ts index e07725ea5..7f1dcda8f 100644 --- a/src/model.ts +++ b/src/model.ts @@ -41,7 +41,7 @@ export class GitExtension implements IGitExtension { interval = DEFAULT_REFRESH_INTERVAL; } const poll = new Poll({ - factory: () => model._refreshStatus(), + factory: () => model.refresh(), frequency: { interval: interval, backoff: true, @@ -787,8 +787,31 @@ export class GitExtension implements IGitExtension { * Request git status refresh */ async refreshStatus(): Promise<void> { - await this._poll.refresh(); - await this._poll.tick; + await this.ready; + const path = this.pathRepository; + + if (path === null) { + this._setStatus([]); + return Promise.resolve(); + } + + try { + let response = await httpGitRequest('/git/status', 'POST', { + current_path: path + }); + const data = await response.json(); + if (response.status !== 200) { + console.error(data.message); + // TODO should we notify the user + this._setStatus([]); + } + + this._setStatus((data as Git.IStatusResult).files); + } catch (err) { + console.error(err); + // TODO should we notify the user + this._setStatus([]); + } } /** @@ -936,35 +959,6 @@ export class GitExtension implements IGitExtension { } } - /** Refresh the git repository status */ - protected async _refreshStatus(): Promise<void> { - await this.ready; - const path = this.pathRepository; - - if (path === null) { - this._setStatus([]); - return Promise.resolve(); - } - - try { - let response = await httpGitRequest('/git/status', 'POST', { - current_path: path - }); - const data = await response.json(); - if (response.status !== 200) { - console.error(data.message); - // TODO should we notify the user - this._setStatus([]); - } - - this._setStatus((data as Git.IStatusResult).files); - } catch (err) { - console.error(err); - // TODO should we notify the user - this._setStatus([]); - } - } - /** * Set repository status * From 205496a5a9dd1b1a52cb1b4fcc0bca374eda9631 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:29:14 -0800 Subject: [PATCH 078/126] Replace function expressions --- src/components/BranchMenu.tsx | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index d0565e877..01feb62ec 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -149,9 +149,9 @@ export class BranchMenu extends React.Component< * * @returns fragment array */ - private _renderItems = () => { + private _renderItems() { return this.state.branches.map(this._renderItem); - }; + } /** * Renders a menu item. @@ -160,7 +160,7 @@ export class BranchMenu extends React.Component< * @param idx - item index * @returns fragment */ - private _renderItem = (branch: Git.IBranch, idx: number) => { + private _renderItem(branch: Git.IBranch, idx: number) { // Perform a "simple" filter... (TODO: consider implementing fuzzy filtering) if (this.state.filter && !branch.name.includes(this.state.filter)) { return null; @@ -180,12 +180,12 @@ export class BranchMenu extends React.Component< {branch.name} </ListItem> ); - }; + } /** * Adds model listeners. */ - private _addListeners = () => { + private _addListeners() { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -194,54 +194,54 @@ export class BranchMenu extends React.Component< // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): this.props.model.statusChanged.connect(this._syncState, this); - }; + } /** * Removes model listeners. */ - private _removeListeners = () => { + private _removeListeners() { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); - }; + } /** * Syncs the component state with the underlying model. */ - private _syncState = () => { + private _syncState() { const repo = this.props.model.pathRepository; this.setState({ current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] }); - }; + } /** * Callback invoked upon a change to the menu filter. * * @param event - event object */ - private _onFilterChange = (event: any) => { + private _onFilterChange(event: any) { this.setState({ filter: event.target.value }); - }; + } /** * Callback invoked to reset the menu filter. */ - private _resetFilter = () => { + private _resetFilter() { this.setState({ filter: '' }); - }; + } /** * Callback invoked upon clicking a button to create a new branch. * * @param event - event object */ - private _onNewBranchClick = () => { + private _onNewBranchClick() { if (!this.props.branching) { showErrorMessage('Creating a new branch is disabled', CHANGES_ERR_MSG); return; @@ -249,16 +249,16 @@ export class BranchMenu extends React.Component< this.setState({ branchDialog: true }); - }; + } /** * Callback invoked upon closing a dialog to create a new branch. */ - private _onNewBranchDialogClose = () => { + private _onNewBranchDialogClose() { this.setState({ branchDialog: false }); - }; + } /** * Returns a callback which is invoked upon clicking a branch name. @@ -266,7 +266,7 @@ export class BranchMenu extends React.Component< * @param branch - branch name * @returns callback */ - private _onBranchClickFactory = (branch: string) => { + private _onBranchClickFactory(branch: string) { const self = this; return onClick; @@ -311,5 +311,5 @@ export class BranchMenu extends React.Component< function onError(err: any) { showErrorMessage('Error switching branch', err.message); } - }; + } } From 404c3c4f54a710528cc99ffef82bdbf88dc4f948 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:30:30 -0800 Subject: [PATCH 079/126] Replace function expressions --- src/components/CommitBox.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 79921c2c6..5d15c2390 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -109,35 +109,35 @@ export class CommitBox extends React.Component< * * @param event - event object */ - private _onCommitClick = () => { + private _onCommitClick() { const msg = this.state.summary + '\n' + this.state.description + '\n'; this.props.onCommit(msg); // NOTE: we assume here that committing changes always works and we can safely clear component state this._reset(); - }; + } /** * Callback invoked upon updating a commit message description. * * @param event - event object */ - private _onDescriptionChange = (event: any): void => { + private _onDescriptionChange(event: any): void { this.setState({ description: event.target.value }); - }; + } /** * Callback invoked upon updating a commit message summary. * * @param event - event object */ - private _onSummaryChange = (event: any): void => { + private _onSummaryChange(event: any): void { this.setState({ summary: event.target.value }); - }; + } /** * Callback invoked upon a `'keypress'` event when entering a commit message summary. @@ -157,10 +157,10 @@ export class CommitBox extends React.Component< /** * Resets component state (e.g., in order to re-initialize the commit message input box). */ - private _reset = (): void => { + private _reset(): void { this.setState({ summary: '', description: '' }); - }; + } } From b82efa92fab8f948b50a18a15e2084d7be9ebd66 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:32:45 -0800 Subject: [PATCH 080/126] Replace function expressions --- src/components/GitPanel.tsx | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index d4f2dba0a..d71229c73 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -208,7 +208,7 @@ export class GitPanel extends React.Component< * * @returns fragment */ - private _renderToolbar = () => { + private _renderToolbar() { const disableBranching = Boolean( this.props.settings.composite['disableBranchWithChanges'] && ((this.state.unstagedFiles && this.state.unstagedFiles.length) || @@ -221,14 +221,14 @@ export class GitPanel extends React.Component< refresh={this._onRefresh} /> ); - }; + } /** * Renders the main panel. * * @returns fragment */ - private _renderMain = () => { + private _renderMain() { if (this.state.inGitRepository) { return ( <React.Fragment> @@ -240,14 +240,14 @@ export class GitPanel extends React.Component< ); } return this._renderWarning(); - }; + } /** * Renders panel tabs. * * @returns fragment */ - private _renderTabs = () => { + private _renderTabs() { return ( <Tabs classes={{ @@ -280,14 +280,14 @@ export class GitPanel extends React.Component< /> </Tabs> ); - }; + } /** * Renders a panel for viewing and committing file changes. * * @returns fragment */ - private _renderChanges = () => { + private _renderChanges() { return ( <React.Fragment> <FileList @@ -311,14 +311,14 @@ export class GitPanel extends React.Component< )} </React.Fragment> ); - }; + } /** * Renders a panel for viewing commit history. * * @returns fragment */ - private _renderHistory = () => { + private _renderHistory() { return ( <HistorySideBar isExpanded={this.state.isHistoryVisible} @@ -328,14 +328,14 @@ export class GitPanel extends React.Component< renderMime={this.props.renderMime} /> ); - }; + } /** * Renders a panel for prompting a user to find a Git repository. * * @returns fragment */ - private _renderWarning = () => { + private _renderWarning() { return ( <div className={warningWrapperClass}> <div>Unable to detect a Git repository.</div> @@ -349,7 +349,7 @@ export class GitPanel extends React.Component< </button> </div> ); - }; + } /** * Callback invoked upon changing the active panel tab. @@ -357,7 +357,7 @@ export class GitPanel extends React.Component< * @param event - event object * @param tab - tab number */ - private _onTabChange = (event: any, tab: number): void => { + private _onTabChange(event: any, tab: number): void { let isHistoryVisible; if (tab === 1) { this.refreshHistory(); @@ -369,21 +369,21 @@ export class GitPanel extends React.Component< tab: tab, isHistoryVisible: isHistoryVisible }); - }; + } /** * Callback invoked upon refreshing a repository. * * @returns promise which refreshes a repository */ - private _onRefresh = async () => { + private async _onRefresh() { await this.refreshBranch(); if (this.state.isHistoryVisible) { this.refreshHistory(); } else { this.refreshStatus(); } - }; + } /** * List of modified files (both staged and unstaged). From 2d3db2636b07d901cdf5bfd285395a29579fe87a Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:34:29 -0800 Subject: [PATCH 081/126] Replace function expressions --- src/components/NewBranchDialog.tsx | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index d6dc32bac..6895a8d64 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -171,9 +171,9 @@ export class NewBranchDialog extends React.Component< * * @returns fragment array */ - private _renderItems = () => { + private _renderItems() { return this.state.branches.map(this._renderItem); - }; + } /** * Renders a branch menu item. @@ -182,7 +182,7 @@ export class NewBranchDialog extends React.Component< * @param idx - item index * @returns fragment */ - private _renderItem = (branch: Git.IBranch, idx: number) => { + private _renderItem(branch: Git.IBranch, idx: number) { // TODO: consider allowing users to branch from any branch, rather than just the current branch... if (branch.name !== this.state.current) { return null; @@ -209,12 +209,12 @@ export class NewBranchDialog extends React.Component< </div> </ListItem> ); - }; + } /** * Adds model listeners. */ - private _addListeners = () => { + private _addListeners() { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -223,28 +223,28 @@ export class NewBranchDialog extends React.Component< // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): this.props.model.statusChanged.connect(this._syncState, this); - }; + } /** * Removes model listeners. */ - private _removeListeners = () => { + private _removeListeners() { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); - }; + } /** * Syncs the component state with the underlying model. */ - private _syncState = () => { + private _syncState() { const repo = this.props.model.pathRepository; this.setState({ base: repo ? this.props.model.currentBranch.name : '', current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] }); - }; + } /** * Returns a callback which is invoked upon clicking a branch name. @@ -252,7 +252,7 @@ export class NewBranchDialog extends React.Component< * @param branch - branch name * @returns callback */ - private _onBranchClickFactory = (branch: string) => { + private _onBranchClickFactory(branch: string) { const self = this; return onClick; @@ -267,25 +267,25 @@ export class NewBranchDialog extends React.Component< base: branch }); } - }; + } /** * Callback invoked upon a change to the branch name input element. * * @param event - event object */ - private _onNameChange = (event: any) => { + private _onNameChange(event: any) { this.setState({ name: event.target.value }); - }; + } /** * Callback invoked upon clicking a button to create a new branch. * * @param event - event object */ - private _onCreate = () => { + private _onCreate() { const branch = this.state.name; // Close the branch dialog: @@ -298,14 +298,14 @@ export class NewBranchDialog extends React.Component< // Create the branch: this._createBranch(branch); - }; + } /** * Creates a new branch. * * @param branch - branch name */ - private _createBranch = (branch: string) => { + private _createBranch(branch: string) { const opts = { newBranch: true, branchname: branch @@ -336,5 +336,5 @@ export class NewBranchDialog extends React.Component< function onError(err: any) { showErrorMessage('Error creating branch', err.message); } - }; + } } From ea2d0e4a0a7dfaf6fba513a1e3c5ff3248d540b7 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:36:24 -0800 Subject: [PATCH 082/126] Replace function expressions --- src/components/Toolbar.tsx | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index da471a7ed..20702d5b9 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -164,7 +164,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * * @returns fragment */ - private _renderTopNav = () => { + private _renderTopNav() { return ( <div className={toolbarNavClass}> <button @@ -188,14 +188,14 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /> </div> ); - }; + } /** * Renders a repository menu. * * @returns fragment */ - private _renderRepoMenu = () => { + private _renderRepoMenu() { return ( <div className={toolbarMenuWrapperClass}> <button @@ -227,14 +227,14 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { {this.state.repoMenu ? null : null} </div> ); - }; + } /** * Renders a branch menu. * * @returns fragment */ - private _renderBranchMenu = () => { + private _renderBranchMenu() { if (!this.state.repository) { return null; } @@ -274,12 +274,12 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { ) : null} </div> ); - }; + } /** * Adds model listeners. */ - private _addListeners = () => { + private _addListeners() { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -288,84 +288,84 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): this.props.model.statusChanged.connect(this._syncState, this); - }; + } /** * Removes model listeners. */ - private _removeListeners = () => { + private _removeListeners() { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); - }; + } /** * Syncs the component state with the underlying model. */ - private _syncState = () => { + private _syncState() { const repo = this.props.model.pathRepository; this.setState({ repository: repo || '', branch: repo ? this.props.model.currentBranch.name : '' }); - }; + } /** * Callback invoked upon clicking a button to pull the latest changes. * * @param event - event object */ - private _onPullClick = () => { + private _onPullClick() { showGitOperationDialog(this.props.model, Operation.Pull).catch(reason => { console.error( `Encountered an error when pulling changes. Error: ${reason}` ); }); - }; + } /** * Callback invoked upon clicking a button to push the latest changes. * * @param event - event object */ - private _onPushClick = () => { + private _onPushClick() { showGitOperationDialog(this.props.model, Operation.Push).catch(reason => { console.error( `Encountered an error when pushing changes. Error: ${reason}` ); }); - }; + } /** * Callback invoked upon clicking a button to change the current repository. * * @param event - event object */ - private _onRepositoryClick = () => { + private _onRepositoryClick() { // Toggle the repository menu: this.setState({ repoMenu: !this.state.repoMenu }); - }; + } /** * Callback invoked upon clicking a button to change the current branch. * * @param event - event object */ - private _onBranchClick = () => { + private _onBranchClick() { // Toggle the branch menu: this.setState({ branchMenu: !this.state.branchMenu }); - }; + } /** * Callback invoked upon clicking a button to refresh a repository. * * @param event - event object */ - private _onRefreshClick = () => { + private _onRefreshClick() { this.props.refresh(); - }; + } } From 7fdaa683905a10883b54cc8cdfb52a3ea492e6e3 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:42:19 -0800 Subject: [PATCH 083/126] Revert to function expressions for event handlers --- src/components/BranchMenu.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 01feb62ec..94a35bc7f 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -150,7 +150,7 @@ export class BranchMenu extends React.Component< * @returns fragment array */ private _renderItems() { - return this.state.branches.map(this._renderItem); + return this.state.branches.map(this._renderItem, this); } /** @@ -221,27 +221,27 @@ export class BranchMenu extends React.Component< * * @param event - event object */ - private _onFilterChange(event: any) { + private _onFilterChange = (event: any): void => { this.setState({ filter: event.target.value }); - } + }; /** * Callback invoked to reset the menu filter. */ - private _resetFilter() { + private _resetFilter = (): void => { this.setState({ filter: '' }); - } + }; /** * Callback invoked upon clicking a button to create a new branch. * * @param event - event object */ - private _onNewBranchClick() { + private _onNewBranchClick = (): void => { if (!this.props.branching) { showErrorMessage('Creating a new branch is disabled', CHANGES_ERR_MSG); return; @@ -249,16 +249,16 @@ export class BranchMenu extends React.Component< this.setState({ branchDialog: true }); - } + }; /** * Callback invoked upon closing a dialog to create a new branch. */ - private _onNewBranchDialogClose() { + private _onNewBranchDialogClose = (): void => { this.setState({ branchDialog: false }); - } + }; /** * Returns a callback which is invoked upon clicking a branch name. From d380337cee326786a56e0495308d3c0ac4350889 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:44:16 -0800 Subject: [PATCH 084/126] Revert to function expressions for event handlers using `this` context --- src/components/CommitBox.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 5d15c2390..1d6ea975d 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -109,35 +109,35 @@ export class CommitBox extends React.Component< * * @param event - event object */ - private _onCommitClick() { + private _onCommitClick = (): void => { const msg = this.state.summary + '\n' + this.state.description + '\n'; this.props.onCommit(msg); // NOTE: we assume here that committing changes always works and we can safely clear component state this._reset(); - } + }; /** * Callback invoked upon updating a commit message description. * * @param event - event object */ - private _onDescriptionChange(event: any): void { + private _onDescriptionChange = (event: any): void => { this.setState({ description: event.target.value }); - } + }; /** * Callback invoked upon updating a commit message summary. * * @param event - event object */ - private _onSummaryChange(event: any): void { + private _onSummaryChange = (event: any): void => { this.setState({ summary: event.target.value }); - } + }; /** * Callback invoked upon a `'keypress'` event when entering a commit message summary. From 6f6bfffd675de4bfe7b55dc6510a299f60c545f7 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:46:30 -0800 Subject: [PATCH 085/126] Revert to function expressions for event handlers --- src/components/GitPanel.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index d71229c73..2cf527311 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -357,7 +357,7 @@ export class GitPanel extends React.Component< * @param event - event object * @param tab - tab number */ - private _onTabChange(event: any, tab: number): void { + private _onTabChange = (event: any, tab: number): void => { let isHistoryVisible; if (tab === 1) { this.refreshHistory(); @@ -369,21 +369,21 @@ export class GitPanel extends React.Component< tab: tab, isHistoryVisible: isHistoryVisible }); - } + }; /** * Callback invoked upon refreshing a repository. * * @returns promise which refreshes a repository */ - private async _onRefresh() { + private _onRefresh = async () => { await this.refreshBranch(); if (this.state.isHistoryVisible) { this.refreshHistory(); } else { this.refreshStatus(); } - } + }; /** * List of modified files (both staged and unstaged). From 5735b13ab84e20dd3b11b09d9552d25c892f5d0c Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:49:45 -0800 Subject: [PATCH 086/126] Revert to function expressions for event handlers --- src/components/BranchMenu.tsx | 6 +++--- src/components/NewBranchDialog.tsx | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 94a35bc7f..c3bee0bd2 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -185,7 +185,7 @@ export class BranchMenu extends React.Component< /** * Adds model listeners. */ - private _addListeners() { + private _addListeners(): void { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -199,7 +199,7 @@ export class BranchMenu extends React.Component< /** * Removes model listeners. */ - private _removeListeners() { + private _removeListeners(): void { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); @@ -208,7 +208,7 @@ export class BranchMenu extends React.Component< /** * Syncs the component state with the underlying model. */ - private _syncState() { + private _syncState(): void { const repo = this.props.model.pathRepository; this.setState({ current: repo ? this.props.model.currentBranch.name : '', diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 6895a8d64..61fe458a0 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -172,7 +172,7 @@ export class NewBranchDialog extends React.Component< * @returns fragment array */ private _renderItems() { - return this.state.branches.map(this._renderItem); + return this.state.branches.map(this._renderItem, this); } /** @@ -214,7 +214,7 @@ export class NewBranchDialog extends React.Component< /** * Adds model listeners. */ - private _addListeners() { + private _addListeners(): void { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -228,7 +228,7 @@ export class NewBranchDialog extends React.Component< /** * Removes model listeners. */ - private _removeListeners() { + private _removeListeners(): void { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); @@ -237,7 +237,7 @@ export class NewBranchDialog extends React.Component< /** * Syncs the component state with the underlying model. */ - private _syncState() { + private _syncState(): void { const repo = this.props.model.pathRepository; this.setState({ base: repo ? this.props.model.currentBranch.name : '', @@ -274,18 +274,18 @@ export class NewBranchDialog extends React.Component< * * @param event - event object */ - private _onNameChange(event: any) { + private _onNameChange = (event: any): void => { this.setState({ name: event.target.value }); - } + }; /** * Callback invoked upon clicking a button to create a new branch. * * @param event - event object */ - private _onCreate() { + private _onCreate = (): void => { const branch = this.state.name; // Close the branch dialog: @@ -298,14 +298,14 @@ export class NewBranchDialog extends React.Component< // Create the branch: this._createBranch(branch); - } + }; /** * Creates a new branch. * * @param branch - branch name */ - private _createBranch(branch: string) { + private _createBranch(branch: string): void { const opts = { newBranch: true, branchname: branch From f9e20f7354ab77acb2d2f2e556e63dabcb0956da Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 11:51:33 -0800 Subject: [PATCH 087/126] Revert to function expressions for event handlers --- src/components/Toolbar.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 20702d5b9..027a2c51f 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -279,7 +279,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Adds model listeners. */ - private _addListeners() { + private _addListeners(): void { // When the repository changes, we're likely to have a new set of branches: this.props.model.repositoryChanged.connect(this._syncState, this); @@ -293,7 +293,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Removes model listeners. */ - private _removeListeners() { + private _removeListeners(): void { this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); @@ -315,57 +315,57 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * * @param event - event object */ - private _onPullClick() { + private _onPullClick = (): void => { showGitOperationDialog(this.props.model, Operation.Pull).catch(reason => { console.error( `Encountered an error when pulling changes. Error: ${reason}` ); }); - } + }; /** * Callback invoked upon clicking a button to push the latest changes. * * @param event - event object */ - private _onPushClick() { + private _onPushClick = (): void => { showGitOperationDialog(this.props.model, Operation.Push).catch(reason => { console.error( `Encountered an error when pushing changes. Error: ${reason}` ); }); - } + }; /** * Callback invoked upon clicking a button to change the current repository. * * @param event - event object */ - private _onRepositoryClick() { + private _onRepositoryClick = (): void => { // Toggle the repository menu: this.setState({ repoMenu: !this.state.repoMenu }); - } + }; /** * Callback invoked upon clicking a button to change the current branch. * * @param event - event object */ - private _onBranchClick() { + private _onBranchClick = (): void => { // Toggle the branch menu: this.setState({ branchMenu: !this.state.branchMenu }); - } + }; /** * Callback invoked upon clicking a button to refresh a repository. * * @param event - event object */ - private _onRefreshClick() { + private _onRefreshClick = (): void => { this.props.refresh(); - } + }; } From c9bdc287b3c74979e57ca52ead8faf320480ab69 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 12:34:10 -0800 Subject: [PATCH 088/126] Adjust tab height --- src/style/GitPanel.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/style/GitPanel.ts b/src/style/GitPanel.ts index f90098fa0..685cd836f 100644 --- a/src/style/GitPanel.ts +++ b/src/style/GitPanel.ts @@ -19,6 +19,8 @@ export const repoButtonClass = style({ }); export const tabsClass = style({ + minHeight: '36px!important', + $nest: { 'button:last-of-type': { borderRight: 'none' @@ -30,6 +32,7 @@ export const tabClass = style({ width: '50%', minWidth: '0!important', maxWidth: '50%!important', + minHeight: '36px!important', backgroundColor: 'var(--jp-layout-color2)!important', From 7b897e0a5214e1a7f6938f8005ce285eeab79073 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 13:31:32 -0800 Subject: [PATCH 089/126] Ensure minimum container height for displaying changed files --- src/components/FileList.tsx | 25 ++++++++++++++----------- src/style/FileListStyle.ts | 5 +++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx index 9ad832951..3be4bec20 100644 --- a/src/components/FileList.tsx +++ b/src/components/FileList.tsx @@ -5,6 +5,7 @@ import { Menu } from '@phosphor/widgets'; import * as React from 'react'; import { GitExtension } from '../model'; import { + fileListWrapperClass, moveFileDownButtonSelectedStyle, moveFileDownButtonStyle, moveFileUpButtonSelectedStyle, @@ -482,7 +483,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> { if (this.props.settings.composite['simpleStaging']) { return ( - <div> + <div className={fileListWrapperClass}> <div> <GitStageSimple heading={'Changed'} @@ -495,16 +496,18 @@ export class FileList extends React.Component<IFileListProps, IFileListState> { </div> </div> ); - } else { - return ( - <div onContextMenu={event => event.preventDefault()}> - <div> - <Staged /> - <Changed /> - <Untracked /> - </div> - </div> - ); } + return ( + <div + className={fileListWrapperClass} + onContextMenu={event => event.preventDefault()} + > + <div> + <Staged /> + <Changed /> + <Untracked /> + </div> + </div> + ); } } diff --git a/src/style/FileListStyle.ts b/src/style/FileListStyle.ts index b8cb4b837..63d70bb9e 100644 --- a/src/style/FileListStyle.ts +++ b/src/style/FileListStyle.ts @@ -1,5 +1,10 @@ import { style } from 'typestyle'; +export const fileListWrapperClass = style({ + height: 'auto', + paddingBottom: '40px' +}); + export const moveFileUpButtonStyle = style({ backgroundImage: 'var(--jp-icon-move-file-up)' }); From 887dcacb5787cc1fea7a27132864536988b16230 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 13:36:33 -0800 Subject: [PATCH 090/126] Remove top margin and ensure minimum container height for history --- src/style/HistorySideBarStyle.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/style/HistorySideBarStyle.ts b/src/style/HistorySideBarStyle.ts index f0423ad68..9f6c5f146 100644 --- a/src/style/HistorySideBarStyle.ts +++ b/src/style/HistorySideBarStyle.ts @@ -3,7 +3,12 @@ import { style } from 'typestyle'; export const historySideBarStyle = style({ display: 'flex', flexDirection: 'column', + + minHeight: '400px', + + marginBlockStart: 0, + marginBlockEnd: 0, paddingLeft: 0, - overflowY: 'scroll', - marginBlockEnd: 0 + + overflowY: 'scroll' }); From 92153180439cd1e75457d3022f7ea66151f6c623 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 13:40:59 -0800 Subject: [PATCH 091/126] Remove bottom margin for branch menu --- src/style/BranchMenu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 1ae26d510..3b75105bf 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -2,7 +2,7 @@ import { style } from 'typestyle'; export const wrapperClass = style({ marginTop: '6px', - marginBottom: '10px' + marginBottom: '0' }); export const filterWrapperClass = style({ From cf20b93f7678353188fd2950292bee1ac4353674 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 14:00:00 -0800 Subject: [PATCH 092/126] Reduce min button height --- src/style/Toolbar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 77606e5ea..840c1b6da 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -37,7 +37,7 @@ export const toolbarMenuButtonClass = style({ flexWrap: 'wrap', width: '100%', - minHeight: '55px', + minHeight: '50px', /* top | right | bottom | left */ padding: '4px 11px 4px 11px', From 7794eca7e5ded062c54331d16f44fcf01c7cd586 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 14:52:16 -0800 Subject: [PATCH 093/126] Add return type --- src/components/Toolbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 027a2c51f..4a0abc4f0 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -302,7 +302,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Syncs the component state with the underlying model. */ - private _syncState() { + private _syncState(): void { const repo = this.props.model.pathRepository; this.setState({ repository: repo || '', From f1f2cf2b9bb3ca8afc70fb09d17f1fa6de3043d0 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 17:22:52 -0800 Subject: [PATCH 094/126] Add a border to the branch list --- src/style/NewBranchDialog.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index ad6cced10..1ac377d23 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -84,6 +84,9 @@ export const listWrapperClass = style({ width: '100%', maxHeight: '400px', + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', + overflow: 'hidden', overflowY: 'scroll' }); From 6009baee2af6b9f0503e939c98786c453a32db5c Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 17:30:59 -0800 Subject: [PATCH 095/126] Remove unused styles and remove min height requirement --- src/style/NewBranchDialog.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index 1ac377d23..83bb5cfd1 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -92,23 +92,16 @@ export const listWrapperClass = style({ }); export const listItemClass = style({ - boxSizing: 'border-box', - display: 'flex', flexDirection: 'row', flexWrap: 'wrap', width: '100%', - minHeight: '55px', /* top | right | bottom | left */ - padding: '4px 11px 4px 11px', + padding: '4px 11px 4px 11px!important', fontSize: 'var(--jp-ui-font-size1)', - lineHeight: '1.5em', - textAlign: 'left', - - border: 'none', - borderRadius: 0 + lineHeight: '1.5em' }); export const activeListItemClass = style({ From 55b7da24dd122e2689f1b53c6969cd56751d24cc Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 17:49:17 -0800 Subject: [PATCH 096/126] Sort branches to ensure current branch listed first --- src/components/NewBranchDialog.tsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 61fe458a0..18ffb2b5e 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -172,7 +172,26 @@ export class NewBranchDialog extends React.Component< * @returns fragment array */ private _renderItems() { - return this.state.branches.map(this._renderItem, this); + const current = this.props.model.currentBranch.name; + return this.state.branches + .slice() + .sort(comparator) + .map(this._renderItem, this); + + /** + * Comparator function for sorting branches. + * + * @private + * @param a - first branch + * @param b - second branch + * @returns integer indicating sort order + */ + function comparator(a: Git.IBranch, b: Git.IBranch): number { + if (a.name === current) { + return -1; + } + return 0; + } } /** @@ -183,10 +202,6 @@ export class NewBranchDialog extends React.Component< * @returns fragment */ private _renderItem(branch: Git.IBranch, idx: number) { - // TODO: consider allowing users to branch from any branch, rather than just the current branch... - if (branch.name !== this.state.current) { - return null; - } return ( <ListItem button From a756c3fdb3d7d5a981bd347084a6daabc7687197 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 18:09:56 -0800 Subject: [PATCH 097/126] Add frontend support for branching from an arbitrary branch --- src/components/NewBranchDialog.tsx | 38 +++++++++++++++++++++--------- src/model.ts | 4 ++++ src/style/NewBranchDialog.ts | 4 +++- src/tokens.ts | 4 ++++ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 18ffb2b5e..87c8d0518 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -16,6 +16,7 @@ import { closeButtonClass, contentWrapperClass, createButtonClass, + listItemBoldTitleClass, listItemClass, listItemContentClass, listItemDescClass, @@ -27,6 +28,13 @@ import { titleWrapperClass } from '../style/NewBranchDialog'; +const BRANCH_DESC = { + current: + 'The current branch. Pick this if you want to build on work done in this branch.', + default: + 'The default branch. Pick this if you want to start fresh from the default branch.' +}; + /** * Interface describing component properties. */ @@ -202,25 +210,33 @@ export class NewBranchDialog extends React.Component< * @returns fragment */ private _renderItem(branch: Git.IBranch, idx: number) { + const isBase = branch.name === this.state.base; + const isCurr = branch.name === this.state.current; + + let isBold; + let desc; + if (isCurr) { + isBold = true; + desc = BRANCH_DESC['current']; + } return ( <ListItem button - className={classes( - listItemClass, - branch.name === this.state.base ? activeListItemClass : null - )} + className={classes(listItemClass, isBase ? activeListItemClass : null)} key={idx} onClick={this._onBranchClickFactory(branch.name)} > <span className={classes(listItemIconClass, 'jp-Icon-16')} /> <div className={listItemContentClass}> - <p className={listItemTitleClass}>{branch.name}</p> - {branch.name === this.state.current ? ( - <p className={listItemDescClass}> - The current branch. Pick this if you want to build on work done in - this branch. - </p> - ) : null} + <p + className={classes( + listItemTitleClass, + isBold ? listItemBoldTitleClass : null + )} + > + {branch.name} + </p> + {desc ? <p className={listItemDescClass}>{desc}</p> : null} </div> </ListItem> ); diff --git a/src/model.ts b/src/model.ts index 7f1dcda8f..0f0bcae7e 100644 --- a/src/model.ts +++ b/src/model.ts @@ -387,6 +387,7 @@ export class GitExtension implements IGitExtension { checkout_branch: false, new_check: false, branchname: '', + startpoint: '', checkout_all: true, filename: '', top_repo_path: path @@ -397,6 +398,9 @@ export class GitExtension implements IGitExtension { body.branchname = options.branchname; body.checkout_branch = true; body.new_check = options.newBranch === true; + if (options.newBranch) { + body.startpoint = options.startpoint || this._currentBranch.name; + } } else if (options.filename) { body.filename = options.filename; body.checkout_all = false; diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index 83bb5cfd1..9a0037ea3 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -136,7 +136,9 @@ export const listItemIconClass = style({ backgroundPosition: 'center' }); -export const listItemTitleClass = style({ +export const listItemTitleClass = style({}); + +export const listItemBoldTitleClass = style({ fontWeight: 700 }); diff --git a/src/tokens.ts b/src/tokens.ts index a318b4a06..608867f6f 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -319,6 +319,10 @@ export namespace Git { * Is it a new branch? */ newBranch?: boolean; + /** + * The commit (branch name, tag, or commit id) to which a new branch HEAD will point. + */ + startpoint?: string; /** * Filename */ From 55427974212da70941b5b763de2d366bdf2cb57c Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 18:11:06 -0800 Subject: [PATCH 098/126] Add backend support for branching from an arbitrary branch --- jupyterlab_git/git.py | 5 +++-- jupyterlab_git/handlers.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jupyterlab_git/git.py b/jupyterlab_git/git.py index b615c0c1c..99ea14aa9 100644 --- a/jupyterlab_git/git.py +++ b/jupyterlab_git/git.py @@ -606,12 +606,13 @@ def reset_to_commit(self, commit_id, top_repo_path): ) return my_output - def checkout_new_branch(self, branchname, current_path): + def checkout_new_branch(self, branchname, startpoint, current_path): """ Execute git checkout <make-branch> command & return the result. """ + cmd = ["git", "checkout", "-b", branchname, startpoint] p = Popen( - ["git", "checkout", "-b", branchname], + cmd, stdout=PIPE, stderr=PIPE, cwd=os.path.join(self.root_dir, current_path), diff --git a/jupyterlab_git/handlers.py b/jupyterlab_git/handlers.py index 3dc07944a..f021c0cac 100644 --- a/jupyterlab_git/handlers.py +++ b/jupyterlab_git/handlers.py @@ -320,7 +320,7 @@ def post(self): if data["checkout_branch"]: if data["new_check"]: my_output = self.git.checkout_new_branch( - data["branchname"], top_repo_path + data["branchname"], data["startpoint"], top_repo_path ) else: my_output = self.git.checkout_branch(data["branchname"], top_repo_path) From 3ba0bf586ae3997f331f3fd5d2384272ce4cd743 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Wed, 8 Jan 2020 18:16:37 -0800 Subject: [PATCH 099/126] Avoid resetting the "base" branch each time the status updates --- src/components/NewBranchDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 87c8d0518..2341f99f7 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -271,7 +271,7 @@ export class NewBranchDialog extends React.Component< private _syncState(): void { const repo = this.props.model.pathRepository; this.setState({ - base: repo ? this.props.model.currentBranch.name : '', + base: repo ? this.state.base : '', current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] }); From b67ed789082442dbdfd69163c26e10e69a4feb52 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 15:33:26 -0800 Subject: [PATCH 100/126] Add support for filtering branch menu --- src/components/NewBranchDialog.tsx | 55 ++++++++++++++++++++ src/style/NewBranchDialog.ts | 83 +++++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 2341f99f7..8bd35df8c 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -16,6 +16,10 @@ import { closeButtonClass, contentWrapperClass, createButtonClass, + filterClass, + filterClearClass, + filterInputClass, + filterWrapperClass, listItemBoldTitleClass, listItemClass, listItemContentClass, @@ -69,6 +73,11 @@ export interface INewBranchDialogState { */ base: string; + /** + * Menu filter. + */ + filter: string; + /** * Current branch name. */ @@ -102,6 +111,7 @@ export class NewBranchDialog extends React.Component< this.state = { name: '', base: repo ? this.props.model.currentBranch.name : '', + filter: '', current: repo ? this.props.model.currentBranch.name : '', branches: repo ? this.props.model.branches : [] }; @@ -150,6 +160,27 @@ export class NewBranchDialog extends React.Component< title="Enter a branch name" /> <p>Create branch based on...</p> + <div className={filterWrapperClass}> + <div className={filterClass}> + <input + className={filterInputClass} + type="text" + onChange={this._onFilterChange} + value={this.state.filter} + placeholder="Filter" + title="Filter branch menu" + /> + {this.state.filter ? ( + <button className={filterClearClass}> + <ClearIcon + titleAccess="Clear the current filter" + fontSize="small" + onClick={this._resetFilter} + /> + </button> + ) : null} + </div> + </div> <div className={listWrapperClass}> <List disablePadding>{this._renderItems()}</List> </div> @@ -210,6 +241,10 @@ export class NewBranchDialog extends React.Component< * @returns fragment */ private _renderItem(branch: Git.IBranch, idx: number) { + // Perform a "simple" filter... (TODO: consider implementing fuzzy filtering) + if (this.state.filter && !branch.name.includes(this.state.filter)) { + return null; + } const isBase = branch.name === this.state.base; const isCurr = branch.name === this.state.current; @@ -277,6 +312,26 @@ export class NewBranchDialog extends React.Component< }); } + /** + * Callback invoked upon a change to the menu filter. + * + * @param event - event object + */ + private _onFilterChange = (event: any): void => { + this.setState({ + filter: event.target.value + }); + }; + + /** + * Callback invoked to reset the menu filter. + */ + private _resetFilter = (): void => { + this.setState({ + filter: '' + }); + }; + /** * Returns a callback which is invoked upon clicking a branch name. * diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index 9a0037ea3..2d4cf15db 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -2,6 +2,7 @@ import { style } from 'typestyle'; export const branchDialogClass = style({ width: '400px', + height: '460px', borderRadius: '3px!important' }); @@ -47,7 +48,7 @@ export const contentWrapperClass = style({ $nest: { '> p': { - marginBottom: '10px' + marginBottom: '7px' } } }); @@ -79,10 +80,88 @@ export const nameInputClass = style({ } }); +export const filterWrapperClass = style({ + padding: 0, + paddingBottom: '4px' +}); + +export const filterClass = style({ + boxSizing: 'border-box', + display: 'inline-block', + position: 'relative', + + width: '100%', + + marginRight: '11px', + + fontSize: 'var(--jp-ui-font-size1)' +}); + +export const filterInputClass = style({ + boxSizing: 'border-box', + + width: '100%', + height: '2em', + + /* top | right | bottom | left */ + padding: '1px 18px 2px 7px', + + color: 'var(--jp-ui-font-color0)', + fontSize: 'var(--jp-ui-font-size1)', + fontWeight: 300, + + backgroundColor: 'var(--jp-layout-color1)', + + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', + + $nest: { + '&:active': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + }, + '&:focus': { + border: 'var(--jp-border-width) solid var(--jp-brand-color1)' + } + } +}); + +export const filterClearClass = style({ + position: 'absolute', + right: '5px', + top: '0.6em', + + height: '1.1em', + width: '1.1em', + + padding: 0, + + backgroundColor: 'var(--jp-inverse-layout-color4)', + + border: 'none', + borderRadius: '50%', + + $nest: { + svg: { + width: '0.5em!important', + height: '0.5em!important', + + fill: 'var(--jp-ui-inverse-font-color0)' + }, + '&:hover': { + backgroundColor: 'var(--jp-inverse-layout-color3)' + }, + '&:active': { + backgroundColor: 'var(--jp-inverse-layout-color2)' + } + } +}); + export const listWrapperClass = style({ + boxSizing: 'border-box', display: 'block', + width: '100%', - maxHeight: '400px', + height: '200px', border: 'var(--jp-border-width) solid var(--jp-border-color2)', borderRadius: '3px', From 1f9e6cad34a39411a820a26845eac3504e5d281c Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 15:40:14 -0800 Subject: [PATCH 101/126] Fix styling for dark mode --- src/style/NewBranchDialog.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index 2d4cf15db..ae526b700 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -106,12 +106,9 @@ export const filterInputClass = style({ /* top | right | bottom | left */ padding: '1px 18px 2px 7px', - color: 'var(--jp-ui-font-color0)', fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - backgroundColor: 'var(--jp-layout-color1)', - border: 'var(--jp-border-width) solid var(--jp-border-color2)', borderRadius: '3px', From b9ab3210a711bc79b63c79c51602c865e2ebc22e Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 16:02:47 -0800 Subject: [PATCH 102/126] Enforce light theming for the new branch dialog --- src/style/NewBranchDialog.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index ae526b700..b6ee74d4d 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -22,10 +22,10 @@ export const closeButtonClass = style({ $nest: { '&:hover': { - backgroundColor: 'var(--jp-toolbar-active-background)' + backgroundColor: '#e0e0e0' }, '&:active': { - backgroundColor: 'var(--jp-toolbar-active-background)' + backgroundColor: '#e0e0e0' } } }); @@ -67,7 +67,7 @@ export const nameInputClass = style({ fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - border: 'var(--jp-border-width) solid var(--jp-border-color2)', + border: 'var(--jp-border-width) solid #e0e0e0', borderRadius: '3px', $nest: { @@ -109,7 +109,7 @@ export const filterInputClass = style({ fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - border: 'var(--jp-border-width) solid var(--jp-border-color2)', + border: 'var(--jp-border-width) solid #e0e0e0', borderRadius: '3px', $nest: { @@ -132,7 +132,7 @@ export const filterClearClass = style({ padding: 0, - backgroundColor: 'var(--jp-inverse-layout-color4)', + backgroundColor: '#757575', border: 'none', borderRadius: '50%', @@ -142,13 +142,13 @@ export const filterClearClass = style({ width: '0.5em!important', height: '0.5em!important', - fill: 'var(--jp-ui-inverse-font-color0)' + fill: 'white' }, '&:hover': { - backgroundColor: 'var(--jp-inverse-layout-color3)' + backgroundColor: '#616161' }, '&:active': { - backgroundColor: 'var(--jp-inverse-layout-color2)' + backgroundColor: '#424242' } } }); @@ -160,7 +160,7 @@ export const listWrapperClass = style({ width: '100%', height: '200px', - border: 'var(--jp-border-width) solid var(--jp-border-color2)', + border: 'var(--jp-border-width) solid #e0e0e0', borderRadius: '3px', overflow: 'hidden', @@ -206,7 +206,7 @@ export const listItemIconClass = style({ /* top | right | bottom | left */ margin: 'auto 8px auto 0', - backgroundImage: 'var(--jp-icon-git-branch)', + backgroundImage: 'var(--jp-icon-git-branch-light-theme)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' @@ -238,7 +238,7 @@ export const buttonClass = style({ }); export const cancelButtonClass = style({ - backgroundColor: 'var(--jp-inverse-layout-color4)' + backgroundColor: '#757575' }); export const createButtonClass = style({ From a0099b10ab02115621d1744861258f109731988a Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 16:04:29 -0800 Subject: [PATCH 103/126] Reset the filter upon creating a new branch --- src/components/NewBranchDialog.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 8bd35df8c..1eaa54e35 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -377,9 +377,10 @@ export class NewBranchDialog extends React.Component< // Close the branch dialog: this.props.onClose(); - // Reset the branch name: + // Reset the branch name and filter: this.setState({ - name: '' + name: '', + filter: '' }); // Create the branch: From 0bf96b7ebf9ba9ad92ecc917290f6ae10a66aaf8 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 16:08:03 -0800 Subject: [PATCH 104/126] Reset state upon closing dialog to create branch --- src/components/NewBranchDialog.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 1eaa54e35..d4ae46bb3 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -136,7 +136,7 @@ export class NewBranchDialog extends React.Component< paper: branchDialogClass }} open={this.props.open} - onClose={this.props.onClose} + onClose={this._onClose} aria-labelledby="new-branch-dialog" > <div className={titleWrapperClass}> @@ -145,7 +145,7 @@ export class NewBranchDialog extends React.Component< <ClearIcon titleAccess="Close this dialog" fontSize="small" - onClick={this.props.onClose} + onClick={this._onClose} /> </button> </div> @@ -191,7 +191,7 @@ export class NewBranchDialog extends React.Component< type="button" title="Close this dialog without creating a new branch" value="Cancel" - onClick={this.props.onClose} + onClick={this._onClose} /> <input className={classes(buttonClass, createButtonClass)} @@ -312,6 +312,19 @@ export class NewBranchDialog extends React.Component< }); } + /** + * Callback invoked upon closing the dialog. + * + * @param event - event object + */ + private _onClose = (): void => { + this.props.onClose(); + this.setState({ + name: '', + filter: '' + }); + }; + /** * Callback invoked upon a change to the menu filter. * From 5c96dd7ec32a3c75aec56d97fee72a6f8e044119 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Thu, 9 Jan 2020 16:09:12 -0800 Subject: [PATCH 105/126] Add variable for explicitly targeting light theme --- style/variables.css | 1 + 1 file changed, 1 insertion(+) diff --git a/style/variables.css b/style/variables.css index 460eb97d7..7b088c465 100644 --- a/style/variables.css +++ b/style/variables.css @@ -11,6 +11,7 @@ --jp-git-diff-output-color: rgba(0, 141, 255, 0.3); --jp-icon-clear-white: url('./images/clear-white.svg'); --jp-icon-discard-file-selected: url('./images/discard-selected.svg'); + --jp-icon-git-branch-light-theme: url('./images/git-branch.svg'); --jp-icon-git-clone: url('./images/git-clone-icon.svg'); --jp-icon-move-file-down-hover: url('./images/move-file-down-hover.svg'); --jp-icon-move-file-up-hover: url('./images/move-file-up-hover.svg'); From 426996596ab742e6c3bbd55128b6b662ffe2241b Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 16:26:04 -0800 Subject: [PATCH 106/126] Use branch name for element key --- src/components/BranchMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index c3bee0bd2..99bbbfa8b 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -173,7 +173,7 @@ export class BranchMenu extends React.Component< listItemClass, branch.name === this.state.current ? activeListItemClass : null )} - key={idx} + key={branch.name} onClick={this._onBranchClickFactory(branch.name)} > <span className={listItemIconClass} /> From dabd7393dc7b015fba2604f6d1534efe450cc1ab Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 16:47:21 -0800 Subject: [PATCH 107/126] Remove duplicate state --- src/components/GitPanel.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 2cf527311..1262fb552 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -36,7 +36,6 @@ export interface IGitSessionNodeState { unstagedFiles: Git.IStatusFileResult[]; untrackedFiles: Git.IStatusFileResult[]; - isHistoryVisible: boolean; tab: number; } @@ -62,7 +61,6 @@ export class GitPanel extends React.Component< stagedFiles: [], unstagedFiles: [], untrackedFiles: [], - isHistoryVisible: false, tab: 0 }; @@ -77,7 +75,7 @@ export class GitPanel extends React.Component< }, this); props.model.headChanged.connect(async () => { await this.refreshBranch(); - if (this.state.isHistoryVisible) { + if (this.state.tab === 1) { this.refreshHistory(); } else { this.refreshStatus(); @@ -233,9 +231,7 @@ export class GitPanel extends React.Component< return ( <React.Fragment> {this._renderTabs()} - {this.state.isHistoryVisible - ? this._renderHistory() - : this._renderChanges()} + {this.state.tab === 1 ? this._renderHistory() : this._renderChanges()} </React.Fragment> ); } @@ -321,7 +317,7 @@ export class GitPanel extends React.Component< private _renderHistory() { return ( <HistorySideBar - isExpanded={this.state.isHistoryVisible} + isExpanded={this.state.tab === 1} branches={this.state.branches} pastCommits={this.state.pastCommits} model={this.props.model} @@ -358,16 +354,11 @@ export class GitPanel extends React.Component< * @param tab - tab number */ private _onTabChange = (event: any, tab: number): void => { - let isHistoryVisible; if (tab === 1) { this.refreshHistory(); - isHistoryVisible = true; - } else { - isHistoryVisible = false; } this.setState({ - tab: tab, - isHistoryVisible: isHistoryVisible + tab: tab }); }; @@ -378,7 +369,7 @@ export class GitPanel extends React.Component< */ private _onRefresh = async () => { await this.refreshBranch(); - if (this.state.isHistoryVisible) { + if (this.state.tab === 1) { this.refreshHistory(); } else { this.refreshStatus(); From ac751219aa2250759b460ddef5f630d09fc9085f Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 18:01:38 -0800 Subject: [PATCH 108/126] Remove listener for repository changes --- src/components/BranchMenu.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 99bbbfa8b..e591b7dae 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -186,9 +186,6 @@ export class BranchMenu extends React.Component< * Adds model listeners. */ private _addListeners(): void { - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); @@ -200,7 +197,6 @@ export class BranchMenu extends React.Component< * Removes model listeners. */ private _removeListeners(): void { - this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); } From 87f144d71aa2709c4161630a56d3539c23f39f98 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 18:09:51 -0800 Subject: [PATCH 109/126] Move listener binding to lifecycle method --- src/components/BranchMenu.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index e591b7dae..63eea277e 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -79,7 +79,6 @@ export class BranchMenu extends React.Component< super(props); const repo = this.props.model.pathRepository; - this._addListeners(); this.state = { filter: '', @@ -89,6 +88,13 @@ export class BranchMenu extends React.Component< }; } + /** + * Callback invoked immediately after mounting a component (i.e., inserting into a tree). + */ + componentDidMount(): void { + this._addListeners(); + } + /** * Callback invoked when a component will no longer be mounted. */ From 9bdb8f3b37eb16971c163b98d06f6b1d934c1182 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 18:12:16 -0800 Subject: [PATCH 110/126] Remove listener and move listener binding to lifecyle method --- src/components/NewBranchDialog.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index d4ae46bb3..1ffa48d43 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -106,7 +106,6 @@ export class NewBranchDialog extends React.Component< super(props); const repo = this.props.model.pathRepository; - this._addListeners(); this.state = { name: '', @@ -117,6 +116,13 @@ export class NewBranchDialog extends React.Component< }; } + /** + * Callback invoked immediately after mounting a component (i.e., inserting into a tree). + */ + componentDidMount(): void { + this._addListeners(); + } + /** * Callback invoked when a component will no longer be mounted. */ @@ -281,9 +287,6 @@ export class NewBranchDialog extends React.Component< * Adds model listeners. */ private _addListeners(): void { - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); @@ -295,7 +298,6 @@ export class NewBranchDialog extends React.Component< * Removes model listeners. */ private _removeListeners(): void { - this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); } From 1f940fcffe8f42d479a90ab0bce3cc3d9c0ed58f Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 18:14:38 -0800 Subject: [PATCH 111/126] Remove listener and move listener binding to lifecyle method --- src/components/BranchMenu.tsx | 2 +- src/components/NewBranchDialog.tsx | 2 +- src/components/Toolbar.tsx | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index 63eea277e..cd58d91fc 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -98,7 +98,7 @@ export class BranchMenu extends React.Component< /** * Callback invoked when a component will no longer be mounted. */ - componentWillUnmount() { + componentWillUnmount(): void { this._removeListeners(); } diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 1ffa48d43..100aea99d 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -126,7 +126,7 @@ export class NewBranchDialog extends React.Component< /** * Callback invoked when a component will no longer be mounted. */ - componentWillUnmount() { + componentWillUnmount(): void { this._removeListeners(); } diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 4a0abc4f0..419898450 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -127,7 +127,6 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { super(props); const repo = this.props.model.pathRepository; - this._addListeners(); this.state = { branchMenu: false, @@ -137,10 +136,17 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { }; } + /** + * Callback invoked immediately after mounting a component (i.e., inserting into a tree). + */ + componentDidMount(): void { + this._addListeners(); + } + /** * Callback invoked when a component will no longer be mounted. */ - componentWillUnmount() { + componentWillUnmount(): void { this._removeListeners(); } @@ -280,9 +286,6 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * Adds model listeners. */ private _addListeners(): void { - // When the repository changes, we're likely to have a new set of branches: - this.props.model.repositoryChanged.connect(this._syncState, this); - // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); @@ -294,7 +297,6 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { * Removes model listeners. */ private _removeListeners(): void { - this.props.model.repositoryChanged.disconnect(this._syncState, this); this.props.model.headChanged.disconnect(this._syncState, this); this.props.model.statusChanged.disconnect(this._syncState, this); } From cb77764563c7f02fea4fea5cd6341988793730f4 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 18:48:45 -0800 Subject: [PATCH 112/126] Update comments --- src/components/BranchMenu.tsx | 2 +- src/components/NewBranchDialog.tsx | 2 +- src/components/Toolbar.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index cd58d91fc..a874bbace 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -195,7 +195,7 @@ export class BranchMenu extends React.Component< // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension) or changed repositories: this.props.model.statusChanged.connect(this._syncState, this); } diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 100aea99d..5d21ddad1 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -290,7 +290,7 @@ export class NewBranchDialog extends React.Component< // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension) or changed repositories: this.props.model.statusChanged.connect(this._syncState, this); } diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 419898450..c9b4a781e 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -289,7 +289,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { // When the HEAD changes, decent probability that we've switched branches: this.props.model.headChanged.connect(this._syncState, this); - // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension): + // When the status changes, we may have checked out a new branch (e.g., via the command-line and not via the extension) or changed repositories: this.props.model.statusChanged.connect(this._syncState, this); } From 0aeaa16aa53cfebf743aeee1f31c650a4e8bb5b2 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 20:09:24 -0800 Subject: [PATCH 113/126] Use branch name as element key --- src/components/NewBranchDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 5d21ddad1..7c84d14cf 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -264,7 +264,7 @@ export class NewBranchDialog extends React.Component< <ListItem button className={classes(listItemClass, isBase ? activeListItemClass : null)} - key={idx} + key={branch.name} onClick={this._onBranchClickFactory(branch.name)} > <span className={classes(listItemIconClass, 'jp-Icon-16')} /> From db804cee6e0349d18ff120897cf3695801028f31 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 20:19:13 -0800 Subject: [PATCH 114/126] Add title attribute --- src/components/NewBranchDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 7c84d14cf..0c784555d 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -263,6 +263,7 @@ export class NewBranchDialog extends React.Component< return ( <ListItem button + title={`Create a new branch based on: ${branch.name}`} className={classes(listItemClass, isBase ? activeListItemClass : null)} key={branch.name} onClick={this._onBranchClickFactory(branch.name)} From 1bd564b0fe6b5806b9d8bd93384ab5d5bc5014a8 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Fri, 24 Jan 2020 21:56:42 -0800 Subject: [PATCH 115/126] Ensure consistent container height for branch menu and file list --- src/style/BranchMenu.ts | 1 + src/style/FileListStyle.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/style/BranchMenu.ts b/src/style/BranchMenu.ts index 3b75105bf..73105fcfc 100644 --- a/src/style/BranchMenu.ts +++ b/src/style/BranchMenu.ts @@ -97,6 +97,7 @@ export const newBranchButtonClass = style({ export const listWrapperClass = style({ display: 'block', width: '100%', + minHeight: '150px', maxHeight: '400px', overflow: 'hidden', diff --git a/src/style/FileListStyle.ts b/src/style/FileListStyle.ts index 63d70bb9e..cb4e4427e 100644 --- a/src/style/FileListStyle.ts +++ b/src/style/FileListStyle.ts @@ -2,7 +2,12 @@ import { style } from 'typestyle'; export const fileListWrapperClass = style({ height: 'auto', - paddingBottom: '40px' + minHeight: '150px', + maxHeight: '400px', + paddingBottom: '40px', + + overflow: 'hidden', + overflowY: 'scroll' }); export const moveFileUpButtonStyle = style({ From fcb6b42cfd49705acc801b35378bb995bce31d75 Mon Sep 17 00:00:00 2001 From: telamonian <mklein@jhu.edu> Date: Fri, 7 Feb 2020 01:27:07 -0500 Subject: [PATCH 116/126] broad stroke fixes for dark mode new branch dialog; still needs tweaks --- src/style/NewBranchDialog.ts | 44 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index b6ee74d4d..d4a30d2d3 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -1,10 +1,11 @@ import { style } from 'typestyle'; export const branchDialogClass = style({ - width: '400px', + backgroundColor: 'var(--jp-layout-color1)!important', + borderRadius: '3px!important', + color: 'var(--jp-ui-font-color1)!important', height: '460px', - - borderRadius: '3px!important' + width: '400px' }); export const closeButtonClass = style({ @@ -54,21 +55,17 @@ export const contentWrapperClass = style({ }); export const nameInputClass = style({ + backgroundColor: 'var(--jp-layout-color0)', + border: 'var(--jp-border-width) solid #e0e0e0', + borderRadius: '3px', boxSizing: 'border-box', - - width: '100%', - height: '2em', - - marginBottom: '16px', - - /* top | right | bottom | left */ - padding: '1px 18px 2px 7px', - + color: 'var(--jp-ui-font-color1)', fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - - border: 'var(--jp-border-width) solid #e0e0e0', - borderRadius: '3px', + height: '2em', + marginBottom: '16px', + padding: '1px 18px 2px 7px' /* top | right | bottom | left */, + width: '100%', $nest: { '&:active': { @@ -98,19 +95,16 @@ export const filterClass = style({ }); export const filterInputClass = style({ + backgroundColor: 'var(--jp-layout-color0)', + border: 'var(--jp-border-width) solid #e0e0e0', + borderRadius: '3px', boxSizing: 'border-box', - - width: '100%', - height: '2em', - - /* top | right | bottom | left */ - padding: '1px 18px 2px 7px', - + color: 'var(--jp-ui-font-color1)', fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - - border: 'var(--jp-border-width) solid #e0e0e0', - borderRadius: '3px', + height: '2em', + padding: '1px 18px 2px 7px' /* top | right | bottom | left */, + width: '100%', $nest: { '&:active': { From c61f2474260f585a8370edbf1f644c883ffba2ab Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:01:18 -0800 Subject: [PATCH 117/126] Update return types --- src/components/BranchMenu.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index a874bbace..e8cfc17aa 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -105,9 +105,9 @@ export class BranchMenu extends React.Component< /** * Renders the component. * - * @returns fragment + * @returns React element */ - render() { + render(): React.ReactElement { return ( <div className={wrapperClass}> <div className={filterWrapperClass}> @@ -155,7 +155,7 @@ export class BranchMenu extends React.Component< * * @returns fragment array */ - private _renderItems() { + private _renderItems(): React.ReactElement[] { return this.state.branches.map(this._renderItem, this); } @@ -166,7 +166,10 @@ export class BranchMenu extends React.Component< * @param idx - item index * @returns fragment */ - private _renderItem(branch: Git.IBranch, idx: number) { + private _renderItem( + branch: Git.IBranch, + idx: number + ): React.ReactElement | null { // Perform a "simple" filter... (TODO: consider implementing fuzzy filtering) if (this.state.filter && !branch.name.includes(this.state.filter)) { return null; @@ -278,7 +281,7 @@ export class BranchMenu extends React.Component< * @private * @param event - event object */ - function onClick() { + function onClick(): void { if (!self.props.branching) { showErrorMessage('Switching branches is disabled', CHANGES_ERR_MSG); return; @@ -298,7 +301,7 @@ export class BranchMenu extends React.Component< * @private * @param result - result */ - function onResolve(result: any) { + function onResolve(result: any): void { if (result.code !== 0) { showErrorMessage('Error switching branch', result.message); } @@ -310,7 +313,7 @@ export class BranchMenu extends React.Component< * @private * @param err - error */ - function onError(err: any) { + function onError(err: any): void { showErrorMessage('Error switching branch', err.message); } } From 1f4078a4f01d28b59c8f6e329b893e2c9f207066 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:02:06 -0800 Subject: [PATCH 118/126] Update descriptions --- src/components/BranchMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx index e8cfc17aa..fb0d35893 100644 --- a/src/components/BranchMenu.tsx +++ b/src/components/BranchMenu.tsx @@ -153,7 +153,7 @@ export class BranchMenu extends React.Component< /** * Renders menu items. * - * @returns fragment array + * @returns array of React elements */ private _renderItems(): React.ReactElement[] { return this.state.branches.map(this._renderItem, this); @@ -164,7 +164,7 @@ export class BranchMenu extends React.Component< * * @param branch - branch * @param idx - item index - * @returns fragment + * @returns React element */ private _renderItem( branch: Git.IBranch, From 50470ce560a178877496c810a19f17bf4f37c357 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:02:43 -0800 Subject: [PATCH 119/126] Add return type and update description --- src/components/CommitBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 1d6ea975d..ffdc6b131 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -66,9 +66,9 @@ export class CommitBox extends React.Component< /** * Renders the component. * - * @returns fragment + * @returns React element */ - render() { + render(): React.ReactElement { const disabled = !(this.props.hasFiles && this.state.summary); return ( <form className={commitFormClass}> From f2a410ef4cf4824cf9ea86101a89e4f3f4ee2a03 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:04:48 -0800 Subject: [PATCH 120/126] Add return types and update descriptions --- src/components/GitPanel.tsx | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 1262fb552..d59c05806 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -192,7 +192,12 @@ export class GitPanel extends React.Component< } }; - render() { + /** + * Renders the component. + * + * @returns React element + */ + render(): React.ReactElement { return ( <div className={panelWrapperClass}> {this._renderToolbar()} @@ -204,9 +209,9 @@ export class GitPanel extends React.Component< /** * Renders a toolbar. * - * @returns fragment + * @returns React element */ - private _renderToolbar() { + private _renderToolbar(): React.ReactElement { const disableBranching = Boolean( this.props.settings.composite['disableBranchWithChanges'] && ((this.state.unstagedFiles && this.state.unstagedFiles.length) || @@ -224,9 +229,9 @@ export class GitPanel extends React.Component< /** * Renders the main panel. * - * @returns fragment + * @returns React element */ - private _renderMain() { + private _renderMain(): React.ReactElement { if (this.state.inGitRepository) { return ( <React.Fragment> @@ -241,9 +246,9 @@ export class GitPanel extends React.Component< /** * Renders panel tabs. * - * @returns fragment + * @returns React element */ - private _renderTabs() { + private _renderTabs(): React.ReactElement { return ( <Tabs classes={{ @@ -281,9 +286,9 @@ export class GitPanel extends React.Component< /** * Renders a panel for viewing and committing file changes. * - * @returns fragment + * @returns React element */ - private _renderChanges() { + private _renderChanges(): React.ReactElement { return ( <React.Fragment> <FileList @@ -312,9 +317,9 @@ export class GitPanel extends React.Component< /** * Renders a panel for viewing commit history. * - * @returns fragment + * @returns React element */ - private _renderHistory() { + private _renderHistory(): React.ReactElement { return ( <HistorySideBar isExpanded={this.state.tab === 1} @@ -329,9 +334,9 @@ export class GitPanel extends React.Component< /** * Renders a panel for prompting a user to find a Git repository. * - * @returns fragment + * @returns React element */ - private _renderWarning() { + private _renderWarning(): React.ReactElement { return ( <div className={warningWrapperClass}> <div>Unable to detect a Git repository.</div> From bb906e2ecba4dbc11471b9a8476d8e48ffa914d6 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:06:30 -0800 Subject: [PATCH 121/126] Add return value types and update descriptions --- src/components/NewBranchDialog.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index 0c784555d..a0f1b377a 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -133,9 +133,9 @@ export class NewBranchDialog extends React.Component< /** * Renders the component. * - * @returns fragment + * @returns React element */ - render() { + render(): React.ReactElement { return ( <Dialog classes={{ @@ -214,9 +214,9 @@ export class NewBranchDialog extends React.Component< /** * Renders branch menu items. * - * @returns fragment array + * @returns array of React elements */ - private _renderItems() { + private _renderItems(): React.ReactElement[] { const current = this.props.model.currentBranch.name; return this.state.branches .slice() @@ -244,9 +244,12 @@ export class NewBranchDialog extends React.Component< * * @param branch - branch * @param idx - item index - * @returns fragment + * @returns React element */ - private _renderItem(branch: Git.IBranch, idx: number) { + private _renderItem( + branch: Git.IBranch, + idx: number + ): React.ReactElement | null { // Perform a "simple" filter... (TODO: consider implementing fuzzy filtering) if (this.state.filter && !branch.name.includes(this.state.filter)) { return null; @@ -364,7 +367,7 @@ export class NewBranchDialog extends React.Component< * @private * @param event - event object */ - function onClick() { + function onClick(): void { self.setState({ base: branch }); @@ -424,7 +427,7 @@ export class NewBranchDialog extends React.Component< * @private * @param result - result */ - function onResolve(result: any) { + function onResolve(result: any): void { if (result.code !== 0) { showErrorMessage('Error creating branch', result.message); } @@ -436,7 +439,7 @@ export class NewBranchDialog extends React.Component< * @private * @param err - error */ - function onError(err: any) { + function onError(err: any): void { showErrorMessage('Error creating branch', err.message); } } From f1a692a16e80f00a08613fd782739f81ae1f8305 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:07:57 -0800 Subject: [PATCH 122/126] Add return value types and update descriptions --- src/components/Toolbar.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index c9b4a781e..fe818a940 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -153,9 +153,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Renders the component. * - * @returns fragment + * @returns React element */ - render() { + render(): React.ReactElement { return ( <div className={toolbarClass}> {this._renderTopNav()} @@ -168,9 +168,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Renders the top navigation. * - * @returns fragment + * @returns React element */ - private _renderTopNav() { + private _renderTopNav(): React.ReactElement { return ( <div className={toolbarNavClass}> <button @@ -199,9 +199,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Renders a repository menu. * - * @returns fragment + * @returns React element */ - private _renderRepoMenu() { + private _renderRepoMenu(): React.ReactElement { return ( <div className={toolbarMenuWrapperClass}> <button @@ -238,9 +238,9 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { /** * Renders a branch menu. * - * @returns fragment + * @returns React element */ - private _renderBranchMenu() { + private _renderBranchMenu(): React.ReactElement | null { if (!this.state.repository) { return null; } From 063c9f83f10853ad912841e21f50c9064624e209 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:12:38 -0800 Subject: [PATCH 123/126] Remove aria attributes --- src/components/GitPanel.tsx | 1 - src/components/NewBranchDialog.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index d59c05806..9a053f4c0 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -256,7 +256,6 @@ export class GitPanel extends React.Component< indicator: tabIndicatorClass }} value={this.state.tab} - aria-label="Panel tabs" onChange={this._onTabChange} > <Tab diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx index a0f1b377a..c6580937b 100644 --- a/src/components/NewBranchDialog.tsx +++ b/src/components/NewBranchDialog.tsx @@ -143,7 +143,6 @@ export class NewBranchDialog extends React.Component< }} open={this.props.open} onClose={this._onClose} - aria-labelledby="new-branch-dialog" > <div className={titleWrapperClass}> <p className={titleClass}>Create a Branch</p> From 95ea19039eb6d7426edf3b7915b9123a6baeab81 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 19:57:56 -0800 Subject: [PATCH 124/126] Disable button and remove button hint icon --- src/components/Toolbar.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index fe818a940..2a9d1a90d 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -205,6 +205,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { return ( <div className={toolbarMenuWrapperClass}> <button + disabled className={toolbarMenuButtonClass} title={`Current repository: ${this.state.repository}`} onClick={this._onRepositoryClick} @@ -222,13 +223,13 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { {PathExt.basename(this.state.repository)} </p> </div> - <span + {/*<span className={classes( toolbarMenuButtonIconClass, this.state.repoMenu ? closeMenuIconClass : openMenuIconClass, 'jp-Icon-16' )} - /> + />*/} </button> {this.state.repoMenu ? null : null} </div> From 45b4a30a64fc039248b19f699eed9b9b42ce9015 Mon Sep 17 00:00:00 2001 From: Athan Reines <kgryte@gmail.com> Date: Tue, 11 Feb 2020 20:21:14 -0800 Subject: [PATCH 125/126] Update dark theme dialog styling --- src/style/NewBranchDialog.ts | 79 +++++++++++++++++++++++------------- style/variables.css | 1 - 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/style/NewBranchDialog.ts b/src/style/NewBranchDialog.ts index d4a30d2d3..6d9cd4e26 100644 --- a/src/style/NewBranchDialog.ts +++ b/src/style/NewBranchDialog.ts @@ -1,11 +1,14 @@ import { style } from 'typestyle'; export const branchDialogClass = style({ - backgroundColor: 'var(--jp-layout-color1)!important', - borderRadius: '3px!important', - color: 'var(--jp-ui-font-color1)!important', height: '460px', - width: '400px' + width: '400px', + + color: 'var(--jp-ui-font-color1)!important', + + borderRadius: '3px!important', + + backgroundColor: 'var(--jp-layout-color1)!important' }); export const closeButtonClass = style({ @@ -21,12 +24,17 @@ export const closeButtonClass = style({ border: 'none', borderRadius: '50%', + backgroundColor: 'var(--jp-layout-color1)', + $nest: { + svg: { + fill: 'var(--jp-ui-font-color1)' + }, '&:hover': { - backgroundColor: '#e0e0e0' + backgroundColor: 'var(--jp-border-color2)' }, '&:active': { - backgroundColor: '#e0e0e0' + backgroundColor: 'var(--jp-border-color2)' } } }); @@ -37,7 +45,7 @@ export const titleWrapperClass = style({ padding: '15px', - borderBottom: 'var(--jp-border-width) solid #e0e0e0' + borderBottom: 'var(--jp-border-width) solid var(--jp-border-color2)' }); export const titleClass = style({ @@ -55,17 +63,24 @@ export const contentWrapperClass = style({ }); export const nameInputClass = style({ - backgroundColor: 'var(--jp-layout-color0)', - border: 'var(--jp-border-width) solid #e0e0e0', - borderRadius: '3px', boxSizing: 'border-box', - color: 'var(--jp-ui-font-color1)', - fontSize: 'var(--jp-ui-font-size1)', - fontWeight: 300, + + width: '100%', height: '2em', + marginBottom: '16px', - padding: '1px 18px 2px 7px' /* top | right | bottom | left */, - width: '100%', + + /* top | right | bottom | left */ + padding: '1px 18px 2px 7px', + + color: 'var(--jp-ui-font-color0)', + fontSize: 'var(--jp-ui-font-size1)', + fontWeight: 300, + + backgroundColor: 'var(--jp-layout-color1)', + + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', $nest: { '&:active': { @@ -95,16 +110,22 @@ export const filterClass = style({ }); export const filterInputClass = style({ - backgroundColor: 'var(--jp-layout-color0)', - border: 'var(--jp-border-width) solid #e0e0e0', - borderRadius: '3px', boxSizing: 'border-box', - color: 'var(--jp-ui-font-color1)', + + width: '100%', + height: '2em', + + /* top | right | bottom | left */ + padding: '1px 18px 2px 7px', + + color: 'var(--jp-ui-font-color0)', fontSize: 'var(--jp-ui-font-size1)', fontWeight: 300, - height: '2em', - padding: '1px 18px 2px 7px' /* top | right | bottom | left */, - width: '100%', + + backgroundColor: 'var(--jp-layout-color1)', + + border: 'var(--jp-border-width) solid var(--jp-border-color2)', + borderRadius: '3px', $nest: { '&:active': { @@ -126,7 +147,7 @@ export const filterClearClass = style({ padding: 0, - backgroundColor: '#757575', + backgroundColor: 'var(--jp-inverse-layout-color4)', border: 'none', borderRadius: '50%', @@ -136,13 +157,13 @@ export const filterClearClass = style({ width: '0.5em!important', height: '0.5em!important', - fill: 'white' + fill: 'var(--jp-ui-inverse-font-color0)' }, '&:hover': { - backgroundColor: '#616161' + backgroundColor: 'var(--jp-inverse-layout-color3)' }, '&:active': { - backgroundColor: '#424242' + backgroundColor: 'var(--jp-inverse-layout-color2)' } } }); @@ -154,7 +175,7 @@ export const listWrapperClass = style({ width: '100%', height: '200px', - border: 'var(--jp-border-width) solid #e0e0e0', + border: 'var(--jp-border-width) solid var(--jp-border-color2)', borderRadius: '3px', overflow: 'hidden', @@ -200,7 +221,7 @@ export const listItemIconClass = style({ /* top | right | bottom | left */ margin: 'auto 8px auto 0', - backgroundImage: 'var(--jp-icon-git-branch-light-theme)', + backgroundImage: 'var(--jp-icon-git-branch)', backgroundSize: '16px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' @@ -215,7 +236,7 @@ export const listItemBoldTitleClass = style({ export const actionsWrapperClass = style({ padding: '15px!important', - borderTop: 'var(--jp-border-width) solid #e0e0e0' + borderTop: 'var(--jp-border-width) solid var(--jp-border-color2)' }); export const buttonClass = style({ diff --git a/style/variables.css b/style/variables.css index 7b088c465..460eb97d7 100644 --- a/style/variables.css +++ b/style/variables.css @@ -11,7 +11,6 @@ --jp-git-diff-output-color: rgba(0, 141, 255, 0.3); --jp-icon-clear-white: url('./images/clear-white.svg'); --jp-icon-discard-file-selected: url('./images/discard-selected.svg'); - --jp-icon-git-branch-light-theme: url('./images/git-branch.svg'); --jp-icon-git-clone: url('./images/git-clone-icon.svg'); --jp-icon-move-file-down-hover: url('./images/move-file-down-hover.svg'); --jp-icon-move-file-up-hover: url('./images/move-file-up-hover.svg'); From f75c2a6a41c2a8f263a006c0c9af1023e96a159d Mon Sep 17 00:00:00 2001 From: telamonian <mklein@jhu.edu> Date: Fri, 14 Feb 2020 17:20:28 -0500 Subject: [PATCH 126/126] split out enabled style for branch select button --- src/components/Toolbar.tsx | 7 ++++++- src/style/Toolbar.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 2a9d1a90d..cf761d892 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { classes } from 'typestyle'; import { Dialog, showDialog } from '@jupyterlab/apputils'; import { PathExt } from '@jupyterlab/coreutils'; + import { // NOTE: keep in alphabetical order branchIconClass, @@ -14,6 +15,7 @@ import { toolbarButtonClass, toolbarClass, toolbarMenuButtonClass, + toolbarMenuButtonEnabledClass, toolbarMenuButtonIconClass, toolbarMenuButtonSubtitleClass, toolbarMenuButtonTitleClass, @@ -248,7 +250,10 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> { return ( <div className={toolbarMenuWrapperClass}> <button - className={toolbarMenuButtonClass} + className={classes( + toolbarMenuButtonClass, + toolbarMenuButtonEnabledClass + )} title={`Change the current branch: ${this.state.branch}`} onClick={this._onBranchClick} > diff --git a/src/style/Toolbar.ts b/src/style/Toolbar.ts index 840c1b6da..60bc46357 100644 --- a/src/style/Toolbar.ts +++ b/src/style/Toolbar.ts @@ -50,8 +50,10 @@ export const toolbarMenuButtonClass = style({ border: 'none', borderRadius: 0, - background: 'var(--jp-layout-color1)', + background: 'var(--jp-layout-color1)' +}); +export const toolbarMenuButtonEnabledClass = style({ $nest: { '&:hover': { backgroundColor: 'var(--jp-layout-color2)'