diff --git a/package.json b/package.json index 24abc04b..96309152 100644 --- a/package.json +++ b/package.json @@ -493,6 +493,11 @@ }, "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" }, + "git-graph.stickyHeader": { + "type": "boolean", + "default": true, + "description": "Keeps the header visible when the view is scrolled" + }, "git-graph.dialog.addTag.pushToRemote": { "type": "boolean", "default": false, diff --git a/src/config.ts b/src/config.ts index d5dc870b..2a91f3fe 100644 --- a/src/config.ts +++ b/src/config.ts @@ -545,6 +545,13 @@ class Config { return !!this.config.get('showStatusBarItem', true); } + /** + * Get the value of the `git-graph.stickyHeader` Extension Setting. + */ + get stickyHeader() { + return !!this.config.get('stickyHeader', true); + } + /** * Get the value of the `git-graph.tabIconColourTheme` Extension Setting. */ diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 3fa92f21..6c08748d 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -639,6 +639,7 @@ export class GitGraphView extends Disposable { customPullRequestProviders: config.customPullRequestProviders, dateFormat: config.dateFormat, defaultColumnVisibility: config.defaultColumnVisibility, + stickyHeader: config.stickyHeader, dialogDefaults: config.dialogDefaults, enhancedAccessibility: config.enhancedAccessibility, fetchAndPrune: config.fetchAndPrune, @@ -681,9 +682,10 @@ export class GitGraphView extends Disposable {

${UNABLE_TO_FIND_GIT_MSG}

`; } else if (numRepos > 0) { + const stickyClassAttr = initialState.config.stickyHeader ? ' class="sticky"' : ''; body = `
-
+
Repo: Branches: @@ -698,8 +700,8 @@ export class GitGraphView extends Disposable {
+
-
`; diff --git a/src/types.ts b/src/types.ts index 70004b0d..9b74422f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -260,6 +260,7 @@ export interface GitGraphViewConfig { readonly showRemoteBranches: boolean; readonly showStashes: boolean; readonly showTags: boolean; + readonly stickyHeader: boolean; } export interface GitGraphViewGlobalState { diff --git a/tests/config.test.ts b/tests/config.test.ts index 18cb70cd..3563f904 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -2845,6 +2845,8 @@ describe('Config', () => { describe('showStatusBarItem', testBooleanExtensionSetting('showStatusBarItem', 'showStatusBarItem', true)); + describe('stickyHeader', testBooleanExtensionSetting('stickyHeader', 'stickyHeader', true)); + describe('tabIconColourTheme', () => { it('Should return TabIconColourTheme.Colour when the configuration value is "colour"', () => { // Setup diff --git a/web/contextMenu.ts b/web/contextMenu.ts index 927d771c..2605174f 100644 --- a/web/contextMenu.ts +++ b/web/contextMenu.ts @@ -83,7 +83,7 @@ class ContextMenu { ? 2 - menuBounds.height // context menu fits above : -2 - (menuBounds.height - (frameBounds.height - (event.pageY - frameBounds.top))); // Overlap the context menu vertically with the cursor menu.style.left = (frameElem.scrollLeft + Math.max(event.pageX - frameBounds.left + relativeX, 2)) + 'px'; - menu.style.top = (frameElem.scrollTop + Math.max(event.pageY - frameBounds.top + relativeY, 2)) + 'px'; + menu.style.top = Math.max(event.clientY - frameBounds.top + relativeY, 2) + 'px'; menu.style.opacity = '1'; this.elem = menu; this.onClose = onClose; diff --git a/web/main.ts b/web/main.ts index 645ec57a..84326e08 100644 --- a/web/main.ts +++ b/web/main.ts @@ -49,6 +49,7 @@ class GitGraphView { private readonly viewElem: HTMLElement; private readonly controlsElem: HTMLElement; private readonly tableElem: HTMLElement; + private tableColHeadersElem: HTMLElement | null; private readonly footerElem: HTMLElement; private readonly showRemoteBranchesElem: HTMLInputElement; private readonly refreshBtnElem: HTMLElement; @@ -72,6 +73,7 @@ class GitGraphView { this.controlsElem = document.getElementById('controls')!; this.tableElem = document.getElementById('commitTable')!; + this.tableColHeadersElem = document.getElementById('tableColHeaders')!; this.footerElem = document.getElementById('footer')!; this.scrollShadowElem = document.getElementById('scrollShadow')!; @@ -818,7 +820,8 @@ class GitGraphView { markdown: this.config.markdown }); - let html = 'GraphDescription' + + const stickyClassAttr = this.config.stickyHeader ? ' class="sticky"' : ''; + let html = 'GraphDescription' + (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + @@ -920,6 +923,11 @@ class GitGraphView { } } } + + if (this.config.stickyHeader) { + this.tableColHeadersElem = document.getElementById('tableColHeaders'); + this.alignTableHeaderToControls(); + } } private renderUncommittedChanges() { @@ -1881,6 +1889,22 @@ class GitGraphView { this.requestLoadRepoInfoAndCommits(false, true); } + private alignTableHeaderToControls() { + if (!this.tableColHeadersElem) { + return; + } + + const controlsHeight = this.controlsElem.offsetHeight; + const controlsWidth = this.controlsElem.offsetWidth; + const tableColHeadersHeight = this.tableColHeadersElem.offsetHeight; + const bottomBorderWidth = 1; + const shadowYPos = controlsHeight + tableColHeadersHeight + bottomBorderWidth; + + this.tableColHeadersElem.style.top = `${controlsHeight}px`; + this.scrollShadowElem.style.top = `${shadowYPos}px`; + this.scrollShadowElem.style.width = `${controlsWidth}px`; + } + /* Observers */ @@ -1893,6 +1917,10 @@ class GitGraphView { windowWidth = window.outerWidth; windowHeight = window.outerHeight; } + + if (this.config.stickyHeader) { + this.alignTableHeaderToControls(); + } }); } diff --git a/web/styles/contextMenu.css b/web/styles/contextMenu.css index d0e42b2f..76886360 100644 --- a/web/styles/contextMenu.css +++ b/web/styles/contextMenu.css @@ -1,13 +1,13 @@ .contextMenu{ display:block; - position:absolute; + position:fixed; background-color:var(--vscode-menu-background); box-shadow:0 1px 4px 1px var(--vscode-widget-shadow); color:var(--vscode-menu-foreground); list-style-type:none; margin:0; padding:4px 0; - z-index:10; + z-index:13; -webkit-user-select:none; user-select:none; } diff --git a/web/styles/main.css b/web/styles/main.css index 9e9a532f..4f672dc1 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -74,9 +74,10 @@ code{ top:0; left:0; right:0; - height:0; - box-shadow:0 -6px 6px 6px var(--vscode-scrollbar-shadow); - z-index:20; + height:6px; + box-shadow: inset 0 6px 6px -6px var(--vscode-scrollbar-shadow); + z-index:11; + pointer-events:none; } @@ -214,7 +215,8 @@ code{ padding:0 4px; } #commitTable th{ - border-bottom:1px solid rgba(128,128,128,0.5); + border-bottom:1px solid transparent; + box-shadow: 0 1px 0 rgba(128,128,128,0.5); line-height:18px; padding:6px 12px; } @@ -277,6 +279,12 @@ code{ .tableColHeader{ position:relative; } +#tableColHeaders.sticky .tableColHeader { + position: sticky; + top: inherit; + z-index: 11; + background-color: var(--vscode-editor-background); +} .resizeCol{ position:absolute; top:0; @@ -776,11 +784,12 @@ body.tagLabelsRightAligned .gitRef.tag{ #controls{ display:block; - position:relative; + position: relative; left:0; right:0; top:0; padding:4px 132px 4px 0; + background-color: var(--vscode-editor-background); border-bottom:1px solid rgba(128,128,128,0.5); line-height:32px; text-align:center; @@ -790,6 +799,11 @@ body.tagLabelsRightAligned .gitRef.tag{ user-select:none; } +#controls.sticky { + position:sticky; + z-index:12; +} + #repoControl, #branchControl, #showRemoteBranchesControl{ display:inline-block; white-space:nowrap;