diff options
20 files changed, 293 insertions, 159 deletions
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue index 3e3a70085f3..e27404a08d3 100644 --- a/app/assets/javascripts/ide/components/activity_bar.vue +++ b/app/assets/javascripts/ide/components/activity_bar.vue @@ -54,6 +54,20 @@ export default { <li> <button type="button" + class="ide-sidebar-link js-ide-review-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.review + }" + @click.prevent="updateActivityBarView($options.activityBarViews.review)" + > + <icon + name="file-modified" + /> + </button> + </li> + <li> + <button + type="button" class="ide-sidebar-link js-ide-commit-mode" :class="{ active: currentActivityView === $options.activityBarViews.commit diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue new file mode 100644 index 00000000000..ede1a753f17 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_review.vue @@ -0,0 +1,26 @@ +<script> +import IdeTreeList from './ide_tree_list.vue'; + +export default { + components: { + IdeTreeList, + }, +}; +</script> + +<template> + <ide-tree-list + viewer-type="diff" + header-class="ide-review-header" + :disable-action-dropdown="true" + > + <template + slot="header" + > + {{ __('Review') }} + <div class="prepend-top-5 ide-review-sub-header"> + {{ __('Lastest changes') }} + </div> + </template> + </ide-tree-list> +</template> diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 191210fd828..3fb9b8b241d 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -9,6 +9,7 @@ import IdeTree from './ide_tree.vue'; import ResizablePanel from './resizable_panel.vue'; import ActivityBar from './activity_bar.vue'; import CommitSection from './repo_commit_section.vue'; +import IdeReview from './ide_review.vue'; export default { components: { @@ -21,6 +22,7 @@ export default { Identicon, CommitSection, IdeTree, + IdeReview, }, computed: { ...mapState(['loading', 'currentBranchId', 'currentActivityView']), diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue index ed83bf4109b..7dbedaf2cb2 100644 --- a/app/assets/javascripts/ide/components/ide_tree.vue +++ b/app/assets/javascripts/ide/components/ide_tree.vue @@ -1,52 +1,33 @@ <script> -import { mapGetters, mapState } from 'vuex'; -import Icon from '~/vue_shared/components/icon.vue'; -import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import RepoFile from './repo_file.vue'; +import { mapState, mapGetters } from 'vuex'; import NewDropdown from './new_dropdown/index.vue'; +import IdeTreeList from './ide_tree_list.vue'; export default { components: { - Icon, - RepoFile, - SkeletonLoadingContainer, NewDropdown, + IdeTreeList, }, computed: { ...mapState(['currentBranchId']), - ...mapGetters(['currentProject', 'currentTree']), + ...mapGetters(['currentProject']), }, }; </script> <template> - <div - class="ide-file-list" + <ide-tree-list + viewer-type="editor" > - <template v-if="!currentTree || currentTree.loading"> - <div - class="multi-file-loading-container" - v-for="n in 3" - :key="n" - > - <skeleton-loading-container /> - </div> - </template> - <template v-else> - <header class="ide-tree-header"> - {{ __('Edit') }} - <new-dropdown - :project-id="currentProject.name_with_namespace" - :branch="currentBranchId" - path="" - /> - </header> - <repo-file - v-for="file in currentTree.tree" - :key="file.key" - :file="file" - :level="0" + <template + slot="header" + > + {{ __('Edit') }} + <new-dropdown + :project-id="currentProject.name_with_namespace" + :branch="currentBranchId" + path="" /> </template> - </div> + </ide-tree-list> </template> diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue new file mode 100644 index 00000000000..56baba05b74 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -0,0 +1,73 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; +import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import RepoFile from './repo_file.vue'; +import NewDropdown from './new_dropdown/index.vue'; + +export default { + components: { + Icon, + RepoFile, + SkeletonLoadingContainer, + NewDropdown, + }, + props: { + viewerType: { + type: String, + required: true, + }, + headerClass: { + type: String, + required: false, + default: null, + }, + disableActionDropdown: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + ...mapState(['currentBranchId']), + ...mapGetters(['currentProject', 'currentTree']), + }, + mounted() { + this.updateViewer(this.viewerType); + }, + methods: { + ...mapActions(['updateViewer']), + }, +}; +</script> + +<template> + <div + class="ide-file-list" + > + <template v-if="!currentTree || currentTree.loading"> + <div + class="multi-file-loading-container" + v-for="n in 3" + :key="n" + > + <skeleton-loading-container /> + </div> + </template> + <template v-else> + <header + class="ide-tree-header" + :class="headerClass" + > + <slot name="header"></slot> + </header> + <repo-file + v-for="file in currentTree.tree" + :key="file.key" + :file="file" + :level="0" + :disable-action-dropdown="disableActionDropdown" + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 3a04cdd8e46..6b7e0aa001f 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -19,7 +19,7 @@ export default { }, }, computed: { - ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']), + ...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing']), ...mapGetters(['currentMergeRequest', 'getStagedFile']), shouldHideEditor() { return this.file && this.file.binary && !this.file.content; @@ -77,7 +77,6 @@ export default { 'setFileViewMode', 'setFileEOL', 'updateViewer', - 'updateDelayViewerUpdated', ]), initMonaco() { if (this.shouldHideEditor) return; @@ -89,14 +88,6 @@ export default { baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '', }) .then(() => { - const viewerPromise = this.delayViewerUpdated - ? this.updateViewer(this.file.pending ? 'diff' : 'editor') - : Promise.resolve(); - - return viewerPromise; - }) - .then(() => { - this.updateDelayViewerUpdated(false); this.createEditorInstance(); }) .catch(err => { diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index e86db2da4a6..4f102cfc026 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -27,6 +27,11 @@ export default { type: Number, required: true, }, + disableActionDropdown: { + type: Boolean, + required: false, + default: false, + }, }, computed: { isTree() { @@ -55,16 +60,14 @@ export default { } }, methods: { - ...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']), + ...mapActions(['toggleTreeOpen']), clickFile() { // Manual Action if a tree is selected/opened if (this.isTree && this.$router.currentRoute.path === `/project${this.file.url}`) { this.toggleTreeOpen(this.file.path); } - return this.updateDelayViewerUpdated(true).then(() => { - router.push(`/project${this.file.url}`); - }); + router.push(`/project${this.file.url}`); }, }, }; @@ -111,7 +114,7 @@ export default { /> </span> <new-dropdown - v-if="isTree" + v-if="isTree && !disableActionDropdown" :project-id="file.projectId" :branch="file.branchId" :path="file.path" diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue index 7bd646ba9b0..99e51097e12 100644 --- a/app/assets/javascripts/ide/components/repo_tabs.vue +++ b/app/assets/javascripts/ide/components/repo_tabs.vue @@ -32,16 +32,6 @@ export default { default: '', }, }, - data() { - return { - showShadow: false, - }; - }, - updated() { - if (!this.$refs.tabsScroller) return; - - this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth; - }, methods: { ...mapActions(['updateViewer', 'removePendingTab']), openFileViewer(viewer) { @@ -71,12 +61,5 @@ export default { :tab="tab" /> </ul> - <editor-mode - :viewer="viewer" - :show-shadow="showShadow" - :has-changes="hasChanges" - :merge-request-id="mergeRequestId" - @click="openFileViewer" - /> </div> </template> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index e497946a135..16110b2f5a4 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -10,4 +10,5 @@ export const MAX_BODY_LENGTH = 72; export const activityBarViews = { edit: 'ide-tree', commit: 'commit-section', + review: 'ide-review', }; diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js index b65d9c68a0b..489c7c1a03a 100644 --- a/app/assets/javascripts/ide/lib/editor.js +++ b/app/assets/javascripts/ide/lib/editor.js @@ -68,11 +68,8 @@ export default class Editor { this.disposable.add( (this.instance = this.monaco.editor.createDiffEditor(domElement, { ...defaultEditorOptions, - readOnly: true, quickSuggestions: false, occurrencesHighlight: false, - renderLineHighlight: 'none', - hideCursorInOverviewRuler: true, renderSideBySide: Editor.renderSideBySide(domElement), })), ); diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 2c33e13ce2c..c851bbd0b59 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -29,7 +29,6 @@ export const closeFile = ({ commit, state, dispatch }, file) => { keyPrefix: nextFileToOpen.staged ? 'staged' : 'unstaged', }); } else { - dispatch('updateDelayViewerUpdated', true); router.push(`/project${nextFileToOpen.url}`); } } else if (!state.openFiles.length) { diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 6a03c302d9c..0bbd6eb27c1 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -186,13 +186,8 @@ } .ide-sidebar-link { - &:hover, - &:focus, &.active { color: $color-700; - } - - &.active { box-shadow: inset 3px 0 $color-700; } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 1e088c1aaf9..79f32033af4 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -159,24 +159,6 @@ border-bottom-color: $white-light; } } - - .dropdown { - display: flex; - margin-left: auto; - margin-bottom: 1px; - padding: 0 $grid-size; - border-left: 1px solid $white-dark; - background-color: $white-light; - - &.shadow { - box-shadow: 0 0 10px $dropdown-shadow-color; - } - - .btn { - margin-top: auto; - margin-bottom: auto; - } - } } .multi-file-tab { @@ -304,8 +286,14 @@ opacity: 0.4; } - .cursors-layer { - display: none; + .editor.original { + .view-lines { + cursor: default; + } + + .cursors-layer { + display: none; + } } } } @@ -812,10 +800,12 @@ } &:hover { + color: $gl-text-color; background-color: $theme-gray-100; } &:focus { + color: $gl-text-color; background-color: $theme-gray-200; } @@ -958,6 +948,15 @@ } } +.ide-review-header { + flex-direction: column; + align-items: flex-start; +} + +.ide-review-sub-header { + color: $gl-text-color-secondary; +} + .ide-new-modal-label { line-height: 34px; } diff --git a/spec/javascripts/ide/components/activity_bar_spec.js b/spec/javascripts/ide/components/activity_bar_spec.js index 68277ed15eb..956e6c04ff3 100644 --- a/spec/javascripts/ide/components/activity_bar_spec.js +++ b/spec/javascripts/ide/components/activity_bar_spec.js @@ -62,6 +62,12 @@ describe('IDE activity bar', () => { expect(vm.updateActivityBarView).toHaveBeenCalledWith(activityBarViews.commit); }); + + it('calls updateActivityBarView with review value on click', () => { + vm.$el.querySelector('.js-ide-review-mode').click(); + + expect(vm.updateActivityBarView).toHaveBeenCalledWith(ActivityBarViews.review); + }); }); describe('active item', () => { diff --git a/spec/javascripts/ide/components/ide_review_spec.js b/spec/javascripts/ide/components/ide_review_spec.js new file mode 100644 index 00000000000..13753c6b6fb --- /dev/null +++ b/spec/javascripts/ide/components/ide_review_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import IdeReview from '~/ide/components/ide_review.vue'; +import store from '~/ide/stores'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { resetStore, file } from '../helpers'; +import { projectData } from '../mock_data'; + +describe('IDE review mode', () => { + const Component = Vue.extend(IdeReview); + let vm; + + beforeEach(() => { + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = Object.assign({}, projectData); + Vue.set(store.state.trees, 'abcproject/master', { + tree: [file('fileName')], + loading: false, + }); + + vm = createComponentWithStore(Component, store).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('renders list of files', () => { + expect(vm.$el.textContent).toContain('fileName'); + }); +}); diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js new file mode 100644 index 00000000000..4ecbdb8a55e --- /dev/null +++ b/spec/javascripts/ide/components/ide_tree_list_spec.js @@ -0,0 +1,54 @@ +import Vue from 'vue'; +import IdeTreeList from '~/ide/components/ide_tree_list.vue'; +import store from '~/ide/stores'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { resetStore, file } from '../helpers'; +import { projectData } from '../mock_data'; + +describe('IDE tree list', () => { + const Component = Vue.extend(IdeTreeList); + let vm; + + beforeEach(() => { + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = Object.assign({}, projectData); + Vue.set(store.state.trees, 'abcproject/master', { + tree: [file('fileName')], + loading: false, + }); + + vm = createComponentWithStore(Component, store, { + viewerType: 'edit', + }); + + spyOn(vm, 'updateViewer').and.callThrough(); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('updates viewer on mount', () => { + expect(vm.updateViewer).toHaveBeenCalledWith('edit'); + }); + + it('renders loading indicator', done => { + store.state.trees['abcproject/master'].loading = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull(); + expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); + + done(); + }); + }); + + it('renders list of files', () => { + expect(vm.$el.textContent).toContain('fileName'); + }); +}); diff --git a/spec/javascripts/ide/components/ide_tree_spec.js b/spec/javascripts/ide/components/ide_tree_spec.js index bdc03692b40..97a0a2432f1 100644 --- a/spec/javascripts/ide/components/ide_tree_spec.js +++ b/spec/javascripts/ide/components/ide_tree_spec.js @@ -28,15 +28,6 @@ describe('IdeRepoTree', () => { resetStore(vm.$store); }); - it('renders loading', done => { - vm.currentTree.loading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); - done(); - }); - }); - it('renders list of files', () => { expect(vm.$el.textContent).toContain('fileName'); }); diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js index ff391cb4351..4b144eef744 100644 --- a/spec/javascripts/ide/components/repo_file_spec.js +++ b/spec/javascripts/ide/components/repo_file_spec.js @@ -72,9 +72,47 @@ describe('RepoFile', () => { it('renders a tooltip', () => { expect( - vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset - .originalTitle, + vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle, ).toContain('Locked by testuser'); }); }); + + describe('folder', () => { + it('renders action dropdown', done => { + createComponent({ + file: { + ...file('t4'), + type: 'tree', + branchId: 'master', + projectId: 'project', + }, + level: 0, + }); + + setTimeout(() => { + expect(vm.$el.querySelector('.ide-new-btn')).not.toBeNull(); + + done(); + }); + }); + + it('disables action dropdown', done => { + createComponent({ + file: { + ...file('t4'), + type: 'tree', + branchId: 'master', + projectId: 'project', + }, + level: 0, + disableActionDropdown: true, + }); + + setTimeout(() => { + expect(vm.$el.querySelector('.ide-new-btn')).toBeNull(); + + done(); + }); + }); + }); }); diff --git a/spec/javascripts/ide/components/repo_tabs_spec.js b/spec/javascripts/ide/components/repo_tabs_spec.js index 5c4bcc372bc..583f71e6121 100644 --- a/spec/javascripts/ide/components/repo_tabs_spec.js +++ b/spec/javascripts/ide/components/repo_tabs_spec.js @@ -32,54 +32,4 @@ describe('RepoTabs', () => { done(); }); }); - - describe('updated', () => { - it('sets showShadow as true when scroll width is larger than width', done => { - const el = document.createElement('div'); - el.innerHTML = '<div id="test-app"></div>'; - document.body.appendChild(el); - - const style = document.createElement('style'); - style.innerText = ` - .multi-file-tabs { - width: 100px; - } - - .multi-file-tabs .list-unstyled { - display: flex; - overflow-x: auto; - } - `; - document.head.appendChild(style); - - vm = createComponent( - RepoTabs, - { - files: [], - viewer: 'editor', - hasChanges: false, - activeFile: file('activeFile'), - hasMergeRequest: false, - }, - '#test-app', - ); - - vm - .$nextTick() - .then(() => { - expect(vm.showShadow).toEqual(false); - - vm.files = openedFiles; - }) - .then(vm.$nextTick) - .then(() => { - expect(vm.showShadow).toEqual(true); - - style.remove(); - el.remove(); - }) - .then(done) - .catch(done.fail); - }); - }); }); diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/javascripts/ide/lib/editor_spec.js index 530bdfa2759..c4fda0fb1c2 100644 --- a/spec/javascripts/ide/lib/editor_spec.js +++ b/spec/javascripts/ide/lib/editor_spec.js @@ -70,12 +70,10 @@ describe('Multi-file editor library', () => { minimap: { enabled: false, }, - readOnly: true, + readOnly: false, scrollBeyondLastLine: false, quickSuggestions: false, occurrencesHighlight: false, - renderLineHighlight: 'none', - hideCursorInOverviewRuler: true, wordWrap: 'on', renderSideBySide: true, }); |