Commit d17de657 authored by Phil Hughes's avatar Phil Hughes

Make the file tree in merge requests resizable

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51857
parent 3f55633a
......@@ -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">
......
......@@ -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"
......
......@@ -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;
......@@ -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 () => {};
......@@ -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>
......
......@@ -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;
}
}
......@@ -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;
......
......@@ -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 {
......
......@@ -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;
......
---
title: Make file tree in merge requests resizable
merge_request:
author:
type: added
......@@ -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();
......
......@@ -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();
......
......@@ -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', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment