summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2019-02-15 10:25:33 +0000
committerPhil Hughes <me@iamphill.com>2019-02-15 10:33:33 +0000
commitd17de657fe782eb838545aabeee00285e181dce1 (patch)
tree20c5a7c0035cb7152be97065020899cbb5bdd865
parent3f55633a2bd95c50168ecbae43230af90d88dbe3 (diff)
downloadgitlab-ce-d17de657fe782eb838545aabeee00285e181dce1.tar.gz
Make the file tree in merge requests resizable
Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51857
-rw-r--r--app/assets/javascripts/diffs/components/app.vue35
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue12
-rw-r--r--app/assets/javascripts/diffs/constants.js6
-rw-r--r--app/assets/javascripts/diffs/store/actions.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue7
-rw-r--r--app/assets/stylesheets/framework/common.scss12
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss19
-rw-r--r--app/assets/stylesheets/pages/diff.scss33
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--changelogs/unreleased/diff-tree-resizable.yml5
-rw-r--r--spec/javascripts/diffs/components/app_spec.js26
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js12
-rw-r--r--spec/javascripts/vue_shared/components/panel_resizer_spec.js9
13 files changed, 143 insertions, 42 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index f0ce2579ee7..8f47931d14a 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -4,6 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import eventHub from '../../notes/event_hub';
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
@@ -11,6 +12,13 @@ import NoChanges from './no_changes.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import CommitWidget from './commit_widget.vue';
import TreeList from './tree_list.vue';
+import {
+ TREE_LIST_WIDTH_STORAGE_KEY,
+ INITIAL_TREE_WIDTH,
+ MIN_TREE_WIDTH,
+ MAX_TREE_WIDTH,
+ TREE_HIDE_STATS_WIDTH,
+} from '../constants';
export default {
name: 'DiffsApp',
@@ -23,6 +31,7 @@ export default {
CommitWidget,
TreeList,
GlLoadingIcon,
+ PanelResizer,
},
props: {
endpoint: {
@@ -54,8 +63,12 @@ export default {
},
},
data() {
+ const treeWidth =
+ parseInt(localStorage.getItem(TREE_LIST_WIDTH_STORAGE_KEY), 10) || INITIAL_TREE_WIDTH;
+
return {
assignedDiscussions: false,
+ treeWidth,
};
},
computed: {
@@ -96,6 +109,9 @@ export default {
this.startVersion.version_index === this.mergeRequestDiff.version_index)
);
},
+ hideFileStats() {
+ return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
+ },
},
watch: {
diffViewType() {
@@ -142,6 +158,7 @@ export default {
'startRenderDiffsQueue',
'assignDiscussionsToDiff',
'setHighlightedRow',
+ 'cacheTreeListWidth',
]),
fetchData() {
this.fetchDiffFiles()
@@ -184,6 +201,8 @@ export default {
}
},
},
+ minTreeWidth: MIN_TREE_WIDTH,
+ maxTreeWidth: MAX_TREE_WIDTH,
};
</script>
@@ -209,7 +228,21 @@ export default {
:data-can-create-note="getNoteableData.current_user.can_create_note"
class="files d-flex prepend-top-default"
>
- <div v-show="showTreeList" class="diff-tree-list"><tree-list /></div>
+ <div
+ v-show="showTreeList"
+ :style="{ width: `${treeWidth}px` }"
+ class="diff-tree-list js-diff-tree-list"
+ >
+ <panel-resizer
+ :size.sync="treeWidth"
+ :start-size="treeWidth"
+ :min-size="$options.minTreeWidth"
+ :max-size="$options.maxTreeWidth"
+ side="right"
+ @resize-end="cacheTreeListWidth"
+ />
+ <tree-list :hide-file-stats="hideFileStats" />
+ </div>
<div class="diff-files-holder">
<commit-widget v-if="commit" :commit="commit" />
<template v-if="renderDiffFiles">
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 7e00b994541..8fc3af15bea 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -13,6 +13,12 @@ export default {
Icon,
FileRow,
},
+ props: {
+ hideFileStats: {
+ type: Boolean,
+ required: true,
+ },
+ },
data() {
return {
search: '',
@@ -40,6 +46,9 @@ export default {
return acc;
}, []);
},
+ fileRowExtraComponent() {
+ return this.hideFileStats ? null : FileRowStats;
+ },
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile', 'toggleFileFinder']),
@@ -48,7 +57,6 @@ export default {
},
},
shortcutKeyCharacter: `${/Mac/i.test(navigator.userAgent) ? '&#8984;' : 'Ctrl'}+P`,
- FileRowStats,
diffTreeFiltering: gon.features && gon.features.diffTreeFiltering,
};
</script>
@@ -98,7 +106,7 @@ export default {
:file="file"
:level="0"
:hide-extra-on-tree="true"
- :extra-component="$options.FileRowStats"
+ :extra-component="fileRowExtraComponent"
:show-changed-icon="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index bd188d9de9e..7002655ea49 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -36,3 +36,9 @@ export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
+export const TREE_LIST_WIDTH_STORAGE_KEY = 'mr_tree_list_width';
+
+export const INITIAL_TREE_WIDTH = 320;
+export const MIN_TREE_WIDTH = 240;
+export const MAX_TREE_WIDTH = 400;
+export const TREE_HIDE_STATS_WIDTH = 260;
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 7fb66ce433b..c6a691d52b0 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -16,6 +16,7 @@ import {
MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
WHITESPACE_STORAGE_KEY,
+ TREE_LIST_WIDTH_STORAGE_KEY,
} from '../constants';
export const setBaseConfig = ({ commit }, options) => {
@@ -300,5 +301,9 @@ export const toggleFileFinder = ({ commit }, visible) => {
commit(types.TOGGLE_FILE_FINDER_VISIBLE, visible);
};
+export const cacheTreeListWidth = (_, size) => {
+ localStorage.setItem(TREE_LIST_WIDTH_STORAGE_KEY, size);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index bf736a378dd..8d81940eb91 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -28,11 +28,12 @@ export default {
data() {
return {
size: this.startSize,
+ isDragging: false,
};
},
computed: {
className() {
- return `drag-${this.side}`;
+ return [`position-${this.side}-0`, { 'is-dragging': this.isDragging }];
},
cursorStyle() {
if (this.enabled) {
@@ -57,6 +58,7 @@ export default {
startDrag(e) {
if (this.enabled) {
e.preventDefault();
+ this.isDragging = true;
this.startPos = e.clientX;
this.currentStartSize = this.size;
document.addEventListener('mousemove', this.drag);
@@ -80,6 +82,7 @@ export default {
},
endDrag(e) {
e.preventDefault();
+ this.isDragging = false;
document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size);
},
@@ -91,7 +94,7 @@ export default {
<div
:class="className"
:style="cursorStyle"
- class="drag-handle"
+ class="position-absolute position-top-0 position-bottom-0 drag-handle"
@mousedown="startDrag"
@dblclick="resetSize"
></div>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c5c3b66438c..2179743feb0 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -442,3 +442,15 @@ img.emoji {
.position-left-0 { left: 0; }
.position-right-0 { right: 0; }
.position-top-0 { top: 0; }
+
+.drag-handle {
+ width: 4px;
+
+ &:hover {
+ background-color: $white-normal;
+ }
+
+ &.is-dragging {
+ background-color: $gray-600;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 2ac98b5d18f..a80158943c6 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -682,25 +682,6 @@ $ide-commit-header-height: 48px;
flex: 1;
}
-.drag-handle {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 4px;
-
- &:hover {
- background-color: $white-normal;
- }
-
- &.drag-right {
- right: 0;
- }
-
- &.drag-left {
- left: 0;
- }
-}
-
.ide-commit-list-container {
display: flex;
flex: 1;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index a708149b36c..ae0768592e0 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1038,12 +1038,30 @@
}
.diff-tree-list {
- width: 320px;
+ position: -webkit-sticky;
+ position: sticky;
+ $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ max-height: calc(100vh - #{$top-pos});
+ padding-right: $gl-padding;
+ z-index: 202;
+
+ .with-performance-bar & {
+ $performance-bar-top-pos: $performance-bar-height + $top-pos;
+ top: $performance-bar-top-pos;
+ max-height: calc(100vh - #{$performance-bar-top-pos});
+ }
+
+ .drag-handle {
+ bottom: 16px;
+ transform: translateX(-6px);
+ }
}
.diff-files-holder {
flex: 1;
min-width: 0;
+ z-index: 201;
}
.compare-versions-container {
@@ -1051,23 +1069,12 @@
}
.tree-list-holder {
- position: -webkit-sticky;
- position: sticky;
- $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
- top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
- max-height: calc(100vh - #{$top-pos});
- padding-right: $gl-padding;
+ height: 100%;
.file-row {
margin-left: 0;
margin-right: 0;
}
-
- .with-performance-bar & {
- $performance-bar-top-pos: $performance-bar-height + $top-pos;
- top: $performance-bar-top-pos;
- max-height: calc(100vh - #{$performance-bar-top-pos});
- }
}
.tree-list-scroll {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 790d438c7e2..883c856870f 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -735,7 +735,7 @@
.mr-version-controls {
position: relative;
- z-index: 103;
+ z-index: 203;
background: $gray-light;
color: $gl-text-color;
margin-top: -1px;
@@ -809,7 +809,7 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 200;
+ z-index: 300;
background-color: $white-light;
border-bottom: 1px solid $border-color;
diff --git a/changelogs/unreleased/diff-tree-resizable.yml b/changelogs/unreleased/diff-tree-resizable.yml
new file mode 100644
index 00000000000..7411640aea5
--- /dev/null
+++ b/changelogs/unreleased/diff-tree-resizable.yml
@@ -0,0 +1,5 @@
+---
+title: Make file tree in merge requests resizable
+merge_request:
+author:
+type: added
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index a2cbc0f3c72..5abdfe695d0 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -68,6 +68,32 @@ describe('diffs/components/app', () => {
});
});
+ describe('resizable', () => {
+ afterEach(() => {
+ localStorage.removeItem('mr_tree_list_width');
+ });
+
+ it('sets initial width when no localStorage has been set', () => {
+ createComponent();
+
+ expect(vm.vm.treeWidth).toEqual(320);
+ });
+
+ it('sets initial width to localStorage size', () => {
+ localStorage.setItem('mr_tree_list_width', '200');
+
+ createComponent();
+
+ expect(vm.vm.treeWidth).toEqual(200);
+ });
+
+ it('sets width of tree list', () => {
+ createComponent();
+
+ expect(vm.find('.js-diff-tree-list').element.style.width).toEqual('320px');
+ });
+ });
+
describe('empty state', () => {
it('renders empty state when no diff files exist', () => {
createComponent();
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index 9e556698f34..cd7bf6405e5 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -28,7 +28,7 @@ describe('Diffs tree list component', () => {
localStorage.removeItem('mr_diff_tree_list');
- vm = mountComponentWithStore(Component, { store });
+ vm = mountComponentWithStore(Component, { store, props: { hideFileStats: false } });
});
afterEach(() => {
@@ -77,6 +77,16 @@ describe('Diffs tree list component', () => {
expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app');
});
+ it('hides file stats', done => {
+ vm.hideFileStats = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.file-row-stats')).toBe(null);
+
+ done();
+ });
+ });
+
it('calls toggleTreeOpen when clicking folder', () => {
spyOn(vm.$store, 'dispatch').and.stub();
diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js
index 49a580be06b..caabe3aa260 100644
--- a/spec/javascripts/vue_shared/components/panel_resizer_spec.js
+++ b/spec/javascripts/vue_shared/components/panel_resizer_spec.js
@@ -44,7 +44,10 @@ describe('Panel Resizer component', () => {
});
expect(vm.$el.tagName).toEqual('DIV');
- expect(vm.$el.getAttribute('class')).toBe('drag-handle drag-left');
+ expect(vm.$el.getAttribute('class')).toBe(
+ 'position-absolute position-top-0 position-bottom-0 drag-handle position-left-0',
+ );
+
expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;');
});
@@ -55,7 +58,9 @@ describe('Panel Resizer component', () => {
});
expect(vm.$el.tagName).toEqual('DIV');
- expect(vm.$el.getAttribute('class')).toBe('drag-handle drag-right');
+ expect(vm.$el.getAttribute('class')).toBe(
+ 'position-absolute position-top-0 position-bottom-0 drag-handle position-right-0',
+ );
});
it('drag the resizer', () => {