Resolve "Resizable file list and commit panel"
This commit is contained in:
parent
0e7e9e32b3
commit
7fbb5addaf
10 changed files with 262 additions and 5 deletions
|
@ -2,11 +2,18 @@
|
|||
import { mapGetters, mapState, mapActions } from 'vuex';
|
||||
import repoCommitSection from './repo_commit_section.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
repoCommitSection,
|
||||
icon,
|
||||
panelResizer,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
@ -18,10 +25,20 @@ export default {
|
|||
currentIcon() {
|
||||
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.rightPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
|
@ -29,6 +46,12 @@ export default {
|
|||
collapsed: !this.rightPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -39,6 +62,7 @@ export default {
|
|||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div
|
||||
class="multi-file-commit-panel-section">
|
||||
|
@ -71,5 +95,14 @@ export default {
|
|||
<repo-commit-section
|
||||
class=""/>
|
||||
</div>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!rightPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="left"/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,11 +2,18 @@
|
|||
import { mapState, mapActions } from 'vuex';
|
||||
import projectTree from './ide_project_tree.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
projectTree,
|
||||
icon,
|
||||
panelResizer,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
@ -16,10 +23,20 @@ export default {
|
|||
currentIcon() {
|
||||
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.leftPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
|
@ -27,6 +44,12 @@ export default {
|
|||
collapsed: !this.leftPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -37,6 +60,7 @@ export default {
|
|||
:class="{
|
||||
'is-collapsed': leftPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div class="multi-file-commit-panel-inner">
|
||||
<project-tree
|
||||
|
@ -58,5 +82,14 @@ export default {
|
|||
class="collapse-text"
|
||||
>Collapse sidebar</span>
|
||||
</button>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!leftPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="right"/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -90,6 +90,11 @@ export default {
|
|||
rightPanelCollapsed() {
|
||||
this.editor.updateDimensions();
|
||||
},
|
||||
panelResizing(isResizing) {
|
||||
if (isResizing === false) {
|
||||
this.editor.updateDimensions();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
|
@ -99,6 +104,7 @@ export default {
|
|||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
'rightPanelCollapsed',
|
||||
'panelResizing',
|
||||
]),
|
||||
shouldHideEditor() {
|
||||
return this.activeFile.binary && !this.activeFile.raw;
|
||||
|
|
|
@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setResizingStatus = ({ commit }, resizing) => {
|
||||
commit(types.SET_RESIZING_STATUS, resizing);
|
||||
};
|
||||
|
||||
export const checkCommitStatus = ({ state }) =>
|
||||
service
|
||||
.getBranchData(state.currentProjectId, state.currentBranchId)
|
||||
|
|
|
@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT';
|
|||
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
|
||||
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
|
||||
export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
|
||||
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
|
||||
|
||||
// Project Mutation Types
|
||||
export const SET_PROJECT = 'SET_PROJECT';
|
||||
|
|
|
@ -49,6 +49,11 @@ export default {
|
|||
rightPanelCollapsed: collapsed,
|
||||
});
|
||||
},
|
||||
[types.SET_RESIZING_STATUS](state, resizing) {
|
||||
Object.assign(state, {
|
||||
panelResizing: resizing,
|
||||
});
|
||||
},
|
||||
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
|
||||
Object.assign(entry.lastCommit, {
|
||||
id: lastCommit.commit.id,
|
||||
|
|
|
@ -19,4 +19,5 @@ export default () => ({
|
|||
projects: {},
|
||||
leftPanelCollapsed: false,
|
||||
rightPanelCollapsed: true,
|
||||
panelResizing: false,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
startSize: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
side: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
minSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
maxSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: Number.MAX_VALUE,
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
size: this.startSize,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
return `drag${this.side}`;
|
||||
},
|
||||
cursorStyle() {
|
||||
if (this.enabled) {
|
||||
return { cursor: 'ew-resize' };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetSize(e) {
|
||||
e.preventDefault();
|
||||
this.size = this.startSize;
|
||||
this.$emit('update:size', this.size);
|
||||
},
|
||||
startDrag(e) {
|
||||
if (this.enabled) {
|
||||
e.preventDefault();
|
||||
this.startPos = e.clientX;
|
||||
this.currentStartSize = this.size;
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
document.addEventListener('mouseup', this.endDrag, { once: true });
|
||||
this.$emit('resize-start', this.size);
|
||||
}
|
||||
},
|
||||
drag(e) {
|
||||
e.preventDefault();
|
||||
let moved = e.clientX - this.startPos;
|
||||
if (this.side === 'left') moved = -moved;
|
||||
let newSize = this.currentStartSize + moved;
|
||||
if (newSize < this.minSize) {
|
||||
newSize = this.minSize;
|
||||
} else if (newSize > this.maxSize) {
|
||||
newSize = this.maxSize;
|
||||
}
|
||||
this.size = newSize;
|
||||
|
||||
this.$emit('update:size', newSize);
|
||||
},
|
||||
endDrag(e) {
|
||||
e.preventDefault();
|
||||
document.removeEventListener('mousemove', this.drag);
|
||||
this.$emit('resize-end', this.size);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dragHandle"
|
||||
:class="className"
|
||||
:style="cursorStyle"
|
||||
@mousedown="startDrag"
|
||||
@dblclick="resetSize"
|
||||
></div>
|
||||
</template>
|
|
@ -36,10 +36,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .ide-view {
|
||||
height: calc(100vh - #{$header-height});
|
||||
}
|
||||
|
||||
.ide-file-list {
|
||||
flex: 1;
|
||||
|
||||
|
@ -242,12 +238,13 @@ table.table tr td.multi-file-table-name {
|
|||
|
||||
.multi-file-commit-panel {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 290px;
|
||||
padding: 0;
|
||||
background-color: $gray-light;
|
||||
border-left: 1px solid $white-dark;
|
||||
padding-right: 3px;
|
||||
|
||||
.projects-sidebar {
|
||||
display: flex;
|
||||
|
@ -496,3 +493,30 @@ table.table tr td.multi-file-table-name {
|
|||
margin-top: $header-height;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.with-performance-bar {
|
||||
.ide-flash-container.flash-container {
|
||||
margin-top: $header-height + $performance-bar-height;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
height: calc(100vh - #{$header-height + $performance-bar-height});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dragHandle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background-color: $white-dark;
|
||||
|
||||
&.dragright {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.dragleft {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
|
59
spec/javascripts/vue_shared/components/panel_resizer_spec.js
Normal file
59
spec/javascripts/vue_shared/components/panel_resizer_spec.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import Vue from 'vue';
|
||||
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Panel Resizer component', () => {
|
||||
let vm;
|
||||
let PanelResizer;
|
||||
|
||||
const triggerEvent = (eventName, el = vm.$el, clientX = 0) => {
|
||||
const event = document.createEvent('MouseEvents');
|
||||
event.initMouseEvent(eventName, true, true, window, 1, clientX, 0, clientX, 0, false, false,
|
||||
false, false, 0, null);
|
||||
|
||||
el.dispatchEvent(event);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
PanelResizer = Vue.extend(panelResizer);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should render a div element with the correct classes and styles', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
});
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
expect(vm.$el.getAttribute('class')).toBe('dragHandle dragleft');
|
||||
expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;');
|
||||
});
|
||||
|
||||
it('should render a div element with the correct classes for a right side panel', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'right',
|
||||
});
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
expect(vm.$el.getAttribute('class')).toBe('dragHandle dragright');
|
||||
});
|
||||
|
||||
it('drag the resizer', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
});
|
||||
|
||||
spyOn(vm, '$emit');
|
||||
triggerEvent('mousedown', vm.$el);
|
||||
triggerEvent('mousemove', document);
|
||||
triggerEvent('mouseup', document);
|
||||
expect(vm.$emit.calls.allArgs()).toEqual([['resize-start', 100], ['update:size', 100], ['resize-end', 100]]);
|
||||
expect(vm.size).toBe(100);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue