From 507595b26284fc48d42b868115017f0e0c59ad68 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Mon, 7 Oct 2024 13:24:38 +0200 Subject: [PATCH 1/2] Scource viewer: Add sticky scrollbar Adds a virtual, sticky scrollbar for easier navigation on long files. Changelog: added --- .../components/blob_content_viewer.vue | 3 +- .../source_viewer/source_viewer.vue | 38 +++++++++++-- app/assets/stylesheets/framework/files.scss | 53 ++++++++++++++++++- .../stylesheets/highlight/themes/dark.scss | 9 ++++ .../stylesheets/highlight/themes/monokai.scss | 11 ++++ .../highlight/themes/solarized-dark.scss | 9 ++++ .../highlight/themes/solarized-light.scss | 9 ++++ 7 files changed, 127 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index fffad3db577cdc..5d97faf5e1fe65 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -389,7 +389,8 @@ export default { :show-blame="showBlame" :project-path="projectPath" :current-ref="currentRef" - class="blob-viewer" + class="blob-viewer gl-grid" + :class="{ 'blob-viewer-blame': showBlame }" @error="onError" /> { + this.$refs.syncScroll.querySelectorAll('.syncscroll')[index].scrollLeft = Math.round( + xRate * (elem.scrollWidth - elem.clientWidth), + ); + }); + }, + async initSyncScrollBar() { + await this.$nextTick(); + + const lineMarginLeft = + this.$refs.syncScroll.querySelectorAll('code')[0]?.style.marginLeft || 0; + const lineWidth = + `${this.$refs.syncScroll.querySelectorAll('.line')[0]?.offsetWidth}px` || '100%'; + + // Virtual scrollbar styles + this.$refs.syncScrollBar.style.marginLeft = lineMarginLeft; + this.$refs.syncScrollWidth.style.width = lineWidth; + }, async handleChunkAppear(chunkIndex, handleOverlappingChunk = true) { if (!this.renderedChunks.includes(chunkIndex)) { this.renderedChunks.push(chunkIndex); @@ -109,6 +133,8 @@ export default { // request the blame information for overlapping chunk incase it is visible in the DOM this.handleChunkAppear(chunkIndex - 1, false); } + + await this.initSyncScrollBar(); } }, async requestBlameInfo(chunkIndex) { @@ -141,11 +167,11 @@ export default { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 6a7c32080adec4..bad407f2cf377c 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -2,6 +2,8 @@ * File content holder * */ +$blame-width: 400px; + .file-holder { @apply gl-border gl-rounded-base; @@ -457,7 +459,7 @@ span.idiff { } .blame-commit { - width: 400px; + width: $blame-width; flex: none; background: $gray-10; border-left: 3px solid; @@ -543,3 +545,52 @@ span.idiff { background: linear-gradient(to top, $white, transparent); } } + +.blob-viewer { + grid-template-columns: 1fr; +} + +.blob-viewer-blame { + grid-template-columns: $blame-width 1fr; + + .scrollbar { + grid-column-start: 2; + } + + .syncscroll-wrapper { + left: #{$blame-width + 1px}; + } +} + +.syncscroll.scrollbar { + z-index: 2; + position: sticky; + bottom: 0; + width: auto; + height: 1rem; + margin-top: -1rem; + pointer-events: auto; + overflow: auto hidden; + // Use fixed values as fallback so it + // doesn't change in darkmode + // as it's related to the + // color theme of the + // Syntax highlighting theme + // stylelint-disable-next-line scale-unlimited/declaration-strict-value + scrollbar-color: #606060 #fff; + scrollbar-width: thin; + + .scrollbar-indicator { + height: 1px; + } +} + +.syncscroll-wrapper { + display: block; + position: absolute; + bottom: 1px; + left: 1px; + right: 1px; + height: calc(1rem - 1px); + @apply gl-bg-default; +} diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index 11a07df9a9649a..f4b85832b7588e 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -390,3 +390,12 @@ $dark-il: #de935f; .vi { color: $dark-vi; } /* Name.Variable.Instance */ .il { color: $dark-il; } /* Literal.Number.Integer.Long */ } + +.syncscroll.scrollbar { + color-scheme: dark; + scrollbar-color: $gray-200 $dark-main-bg; +} + +.syncscroll-wrapper { + background-color: $dark-main-bg; +} diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index 0fda4073b200c9..15bbaefe51e8f7 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -375,3 +375,14 @@ $monokai-gh: #75715e; .gi { color: $monokai-gi; } /* Generic.Inserted & Diff Inserted */ .gh { color: $monokai-gh; } /* Generic.Heading */ } + +.syncscroll.scrollbar { + color-scheme: dark; + scrollbar-color: $gray-200 $monokai-bg; +} + +.syncscroll-wrapper { + background-color: $monokai-bg; +} + + diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index 8fb561fba31b9d..094eea246f7103 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -401,3 +401,12 @@ $solarized-dark-il: #2aa198; .vi { color: $solarized-dark-vi; } /* Name.Variable.Instance */ .il { color: $solarized-dark-il; } /* Literal.Number.Integer.Long */ } + +.syncscroll.scrollbar { + color-scheme: dark; + scrollbar-color: $gray-200 $solarized-dark-line-bg; +} + +.syncscroll-wrapper { + background-color: $solarized-dark-line-bg; +} diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index 609d7efd6f0482..27a0b753253946 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -389,3 +389,12 @@ $solarized-light-il: #2aa198; .vi { color: $solarized-light-vi; } /* Name.Variable.Instance */ .il { color: $solarized-light-il; } /* Literal.Number.Integer.Long */ } + +.syncscroll.scrollbar { + scrollbar-color: $gray-600 $solarized-light-line-bg; +} + +.syncscroll-wrapper { + background-color: $solarized-light-line-bg; +} + -- GitLab From dbd9361fb3cefcfea650da6d921f8765e28b76d7 Mon Sep 17 00:00:00 2001 From: Sascha Eggenberger Date: Mon, 14 Oct 2024 15:49:12 +0200 Subject: [PATCH 2/2] Simplify code, performance improvements and smooth scroll on mobile --- .../source_viewer/source_viewer.vue | 32 +++++++++---------- app/assets/stylesheets/framework/files.scss | 23 ++++--------- .../stylesheets/highlight/themes/dark.scss | 6 +--- .../stylesheets/highlight/themes/monokai.scss | 8 +---- .../highlight/themes/solarized-dark.scss | 6 +--- .../highlight/themes/solarized-light.scss | 7 +--- 6 files changed, 26 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue index e9c413bc3b0415..acf4a872179c3e 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -101,27 +101,21 @@ export default { addBlobLinksTracking(); }, methods: { - syncScroll(event) { - const el = event.target; - const scrollX = el.scrollLeft; - const xRate = scrollX / (el.scrollWidth - el.clientWidth); - - this.$refs.syncScroll.querySelectorAll('.syncscroll').forEach((elem, index) => { - this.$refs.syncScroll.querySelectorAll('.syncscroll')[index].scrollLeft = Math.round( - xRate * (elem.scrollWidth - elem.clientWidth), - ); + setScrollbarPosition() { + requestAnimationFrame(() => { + this.$refs.syncScrollBar.scrollLeft = this.$refs.syncScrollContainer.scrollLeft; + }); + }, + setContainerPosition() { + requestAnimationFrame(() => { + this.$refs.syncScrollContainer.scrollLeft = this.$refs.syncScrollBar.scrollLeft; }); }, async initSyncScrollBar() { await this.$nextTick(); - const lineMarginLeft = - this.$refs.syncScroll.querySelectorAll('code')[0]?.style.marginLeft || 0; const lineWidth = `${this.$refs.syncScroll.querySelectorAll('.line')[0]?.offsetWidth}px` || '100%'; - - // Virtual scrollbar styles - this.$refs.syncScrollBar.style.marginLeft = lineMarginLeft; this.$refs.syncScrollWidth.style.width = lineWidth; }, async handleChunkAppear(chunkIndex, handleOverlappingChunk = true) { @@ -171,11 +165,13 @@ export default {
- -
-
+
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index bad407f2cf377c..2ea09a5c0f4df0 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -556,15 +556,17 @@ span.idiff { .scrollbar { grid-column-start: 2; } +} - .syncscroll-wrapper { - left: #{$blame-width + 1px}; - } +.syncscroll { + will-change: scroll-position; + -webkit-overflow-scrolling: touch; } .syncscroll.scrollbar { - z-index: 2; + z-index: 3; position: sticky; + left: 0; bottom: 0; width: auto; height: 1rem; @@ -577,20 +579,9 @@ span.idiff { // color theme of the // Syntax highlighting theme // stylelint-disable-next-line scale-unlimited/declaration-strict-value - scrollbar-color: #606060 #fff; - scrollbar-width: thin; + scrollbar-color: #606060 rgba(0, 0, 0, 0.15); .scrollbar-indicator { height: 1px; } } - -.syncscroll-wrapper { - display: block; - position: absolute; - bottom: 1px; - left: 1px; - right: 1px; - height: calc(1rem - 1px); - @apply gl-bg-default; -} diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index f4b85832b7588e..dbe9abab2cc7aa 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -393,9 +393,5 @@ $dark-il: #de935f; .syncscroll.scrollbar { color-scheme: dark; - scrollbar-color: $gray-200 $dark-main-bg; -} - -.syncscroll-wrapper { - background-color: $dark-main-bg; + scrollbar-color: $gray-200 rgba(255, 255, 255, 0.15); } diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index 15bbaefe51e8f7..eb4779eed1fdc3 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -378,11 +378,5 @@ $monokai-gh: #75715e; .syncscroll.scrollbar { color-scheme: dark; - scrollbar-color: $gray-200 $monokai-bg; + scrollbar-color: $gray-200 rgba(255, 255, 255, 0.15); } - -.syncscroll-wrapper { - background-color: $monokai-bg; -} - - diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index 094eea246f7103..36a89d347f101d 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -404,9 +404,5 @@ $solarized-dark-il: #2aa198; .syncscroll.scrollbar { color-scheme: dark; - scrollbar-color: $gray-200 $solarized-dark-line-bg; -} - -.syncscroll-wrapper { - background-color: $solarized-dark-line-bg; + scrollbar-color: $gray-200 rgba(255, 255, 255, 0.15); } diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index 27a0b753253946..497e05a5c52869 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -391,10 +391,5 @@ $solarized-light-il: #2aa198; } .syncscroll.scrollbar { - scrollbar-color: $gray-600 $solarized-light-line-bg; + scrollbar-color: $gray-600 rgba(0, 0, 0, 0.15); } - -.syncscroll-wrapper { - background-color: $solarized-light-line-bg; -} - -- GitLab