diff options
43 files changed, 723 insertions, 918 deletions
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue new file mode 100644 index 00000000000..3e3a70085f3 --- /dev/null +++ b/app/assets/javascripts/ide/components/activity_bar.vue @@ -0,0 +1,71 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; +import { activityBarViews } from '../constants'; + +export default { + components: { + Icon, + }, + computed: { + ...mapGetters(['currentProject']), + ...mapState(['currentActivityView']), + goBackUrl() { + return document.referrer || this.currentProject.web_url; + }, + }, + methods: { + ...mapActions(['updateActivityBarView']), + }, + activityBarViews, +}; +</script> + +<template> + <nav class="ide-activity-bar"> + <ul class="list-unstyled"> + <li v-once> + <a + :href="goBackUrl" + class="ide-sidebar-link" + :aria-label="s__('IDE|Go back')" + > + <icon + :size="16" + name="go-back" + /> + </a> + </li> + <li> + <button + type="button" + class="ide-sidebar-link js-ide-edit-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.edit + }" + @click.prevent="updateActivityBarView($options.activityBarViews.edit)" + :aria-label="s__('IDE|Edit mode')" + > + <icon + name="code" + /> + </button> + </li> + <li> + <button + type="button" + class="ide-sidebar-link js-ide-commit-mode" + :class="{ + active: currentActivityView === $options.activityBarViews.commit + }" + @click.prevent="updateActivityBarView($options.activityBarViews.commit)" + :aria-label="s__('IDE|Commit mode')" + > + <icon + name="commit" + /> + </button> + </li> + </ul> + </nav> +</template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue index 6424b93ce54..ed12b5373b4 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/empty_state.vue @@ -1,5 +1,5 @@ <script> -import { mapActions, mapState, mapGetters } from 'vuex'; +import { mapState } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; @@ -10,26 +10,12 @@ export default { directives: { tooltip, }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, computed: { - ...mapState(['lastCommitMsg', 'rightPanelCollapsed']), - ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']), + ...mapState(['lastCommitMsg', 'noChangesStateSvgPath', 'committedStateSvgPath']), statusSvg() { return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath; }, }, - methods: { - ...mapActions(['toggleRightPanelCollapsed']), - }, }; </script> @@ -37,31 +23,8 @@ export default { <div class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state" > - <header - class="multi-file-commit-panel-header" - :class="{ - 'is-collapsed': rightPanelCollapsed, - }" - > - <button - v-tooltip - :title="collapseButtonTooltip" - data-container="body" - data-placement="left" - type="button" - class="btn btn-transparent multi-file-commit-panel-collapse-btn" - :aria-label="__('Toggle sidebar')" - @click.stop="toggleRightPanelCollapsed" - > - <icon - :name="collapseButtonIcon" - :size="18" - /> - </button> - </header> <div class="ide-commit-empty-state-container" - v-if="!rightPanelCollapsed" > <div class="svg-content svg-80"> <img :src="statusSvg" /> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index ff05ee8682a..e0de30d935b 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -1,16 +1,14 @@ <script> -import { mapActions, mapState, mapGetters } from 'vuex'; +import { mapActions } from 'vuex'; import { __, sprintf } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import ListItem from './list_item.vue'; -import ListCollapsed from './list_collapsed.vue'; export default { components: { Icon, ListItem, - ListCollapsed, }, directives: { tooltip, @@ -24,11 +22,6 @@ export default { type: Array, required: true, }, - showToggle: { - type: Boolean, - required: false, - default: true, - }, iconName: { type: String, required: true, @@ -52,8 +45,6 @@ export default { }, }, computed: { - ...mapState(['rightPanelCollapsed']), - ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']), titleText() { return sprintf(__('%{title} changes'), { title: this.title, @@ -61,7 +52,7 @@ export default { }, }, methods: { - ...mapActions(['toggleRightPanelCollapsed', 'stageAllChanges', 'unstageAllChanges']), + ...mapActions(['stageAllChanges', 'unstageAllChanges']), actionBtnClicked() { this[this.action](); }, @@ -72,19 +63,12 @@ export default { <template> <div class="ide-commit-list-container" - :class="{ - 'is-collapsed': rightPanelCollapsed, - }" > <header class="multi-file-commit-panel-header" > <div - v-if="!rightPanelCollapsed" class="multi-file-commit-panel-header-title" - :class="{ - 'append-right-10': showToggle, - }" > <icon v-once @@ -100,52 +84,28 @@ export default { {{ actionBtnText }} </button> </div> - <button - v-if="showToggle" - v-tooltip - :title="collapseButtonTooltip" - data-container="body" - data-placement="left" - type="button" - class="btn btn-transparent multi-file-commit-panel-collapse-btn" - :aria-label="__('Toggle sidebar')" - @click.stop="toggleRightPanelCollapsed" - > - <icon - :name="collapseButtonIcon" - :size="18" - /> - </button> </header> - <list-collapsed - v-if="rightPanelCollapsed" - :files="fileList" - :icon-name="iconName" - :title="title" - /> - <template v-else> - <ul - v-if="fileList.length" - class="multi-file-commit-list list-unstyled append-bottom-0" - > - <li - v-for="file in fileList" - :key="file.key" - > - <list-item - :file="file" - :action-component="itemActionComponent" - :key-prefix="title" - :staged-list="stagedList" - /> - </li> - </ul> - <p - v-else - class="multi-file-commit-list help-block" + <ul + v-if="fileList.length" + class="multi-file-commit-list list-unstyled append-bottom-0" + > + <li + v-for="file in fileList" + :key="file.key" > - {{ __('No changes') }} - </p> - </template> + <list-item + :file="file" + :action-component="itemActionComponent" + :key-prefix="title" + :staged-list="stagedList" + /> + </li> + </ul> + <p + v-else + class="multi-file-commit-list help-block" + > + {{ __('No changes') }} + </p> </div> </template> diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 0274fc7d299..77b35078e56 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -1,82 +1,67 @@ <script> - import { mapActions, mapState, mapGetters } from 'vuex'; - import Mousetrap from 'mousetrap'; - import ideSidebar from './ide_side_bar.vue'; - import ideContextbar from './ide_context_bar.vue'; - import repoTabs from './repo_tabs.vue'; - import ideStatusBar from './ide_status_bar.vue'; - import repoEditor from './repo_editor.vue'; - import FindFile from './file_finder/index.vue'; +import Mousetrap from 'mousetrap'; +import { mapActions, mapState, mapGetters } from 'vuex'; +import IdeSidebar from './ide_side_bar.vue'; +import RepoTabs from './repo_tabs.vue'; +import IdeStatusBar from './ide_status_bar.vue'; +import RepoEditor from './repo_editor.vue'; +import FindFile from './file_finder/index.vue'; - const originalStopCallback = Mousetrap.stopCallback; +const originalStopCallback = Mousetrap.stopCallback; - export default { - components: { - ideSidebar, - ideContextbar, - repoTabs, - ideStatusBar, - repoEditor, - FindFile, - }, - props: { - emptyStateSvgPath: { - type: String, - required: true, - }, - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, - computed: { - ...mapState([ - 'changedFiles', - 'openFiles', - 'viewer', - 'currentMergeRequestId', - 'fileFindVisible', - ]), - ...mapGetters(['activeFile', 'hasChanges']), - }, - mounted() { - const returnValue = 'Are you sure you want to lose unsaved changes?'; - window.onbeforeunload = e => { - if (!this.changedFiles.length) return undefined; +export default { + components: { + IdeSidebar, + RepoTabs, + IdeStatusBar, + RepoEditor, + FindFile, + }, + computed: { + ...mapState([ + 'changedFiles', + 'openFiles', + 'viewer', + 'currentMergeRequestId', + 'fileFindVisible', + 'emptyStateSvgPath', + ]), + ...mapGetters(['activeFile', 'hasChanges']), + }, + mounted() { + const returnValue = 'Are you sure you want to lose unsaved changes?'; + window.onbeforeunload = e => { + if (!this.changedFiles.length) return undefined; - Object.assign(e, { - returnValue, - }); - return returnValue; - }; + Object.assign(e, { + returnValue, + }); + return returnValue; + }; - Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { - if (e.preventDefault) { - e.preventDefault(); - } + Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { + if (e.preventDefault) { + e.preventDefault(); + } - this.toggleFileFinder(!this.fileFindVisible); - }); + this.toggleFileFinder(!this.fileFindVisible); + }); - Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); - }, - methods: { - ...mapActions(['toggleFileFinder']), - mousetrapStopCallback(e, el, combo) { - if (combo === 't' && el.classList.contains('dropdown-input-field')) { - return true; - } else if (combo === 'command+p' || combo === 'ctrl+p') { - return false; - } + Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); + }, + methods: { + ...mapActions(['toggleFileFinder']), + mousetrapStopCallback(e, el, combo) { + if (combo === 't' && el.classList.contains('dropdown-input-field')) { + return true; + } else if (combo === 'command+p' || combo === 'ctrl+p') { + return false; + } - return originalStopCallback(e, el, combo); - }, + return originalStopCallback(e, el, combo); }, - }; + }, +}; </script> <template> @@ -136,9 +121,5 @@ </div> </template> </div> - <ide-contextbar - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" - /> </div> </template> diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue deleted file mode 100644 index 627fbeb9adf..00000000000 --- a/app/assets/javascripts/ide/components/ide_context_bar.vue +++ /dev/null @@ -1,42 +0,0 @@ -<script> -import icon from '~/vue_shared/components/icon.vue'; -import panelResizer from '~/vue_shared/components/panel_resizer.vue'; -import repoCommitSection from './repo_commit_section.vue'; -import ResizablePanel from './resizable_panel.vue'; - -export default { - components: { - repoCommitSection, - icon, - panelResizer, - ResizablePanel, - }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, -}; -</script> - -<template> - <resizable-panel - :collapsible="true" - :initial-width="340" - side="right" - > - <div - class="multi-file-commit-panel-section" - > - <repo-commit-section - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" - /> - </div> - </resizable-panel> -</template> diff --git a/app/assets/javascripts/ide/components/ide_external_links.vue b/app/assets/javascripts/ide/components/ide_external_links.vue deleted file mode 100644 index c6f6e0d2348..00000000000 --- a/app/assets/javascripts/ide/components/ide_external_links.vue +++ /dev/null @@ -1,43 +0,0 @@ -<script> -import icon from '~/vue_shared/components/icon.vue'; - -export default { - components: { - icon, - }, - props: { - projectUrl: { - type: String, - required: true, - }, - }, - computed: { - goBackUrl() { - return document.referrer || this.projectUrl; - }, - }, -}; -</script> - -<template> - <nav - class="ide-external-links" - v-once - > - <p> - <a - :href="goBackUrl" - class="ide-sidebar-link" - > - <icon - :size="16" - class="append-right-8" - name="go-back" - /> - <span class="ide-external-links-text"> - {{ s__('Go back') }} - </span> - </a> - </p> - </nav> -</template> diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue deleted file mode 100644 index eb2749e6151..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue +++ /dev/null @@ -1,47 +0,0 @@ -<script> - import icon from '~/vue_shared/components/icon.vue'; - import repoTree from './ide_repo_tree.vue'; - import newDropdown from './new_dropdown/index.vue'; - - export default { - components: { - repoTree, - icon, - newDropdown, - }, - props: { - projectId: { - type: String, - required: true, - }, - branch: { - type: Object, - required: true, - }, - }, - }; -</script> - -<template> - <div class="branch-container"> - <div class="branch-header"> - <div class="branch-header-title str-truncated ref-name"> - <icon - name="branch" - :size="12" - /> - {{ branch.name }} - </div> - <div class="branch-header-btns"> - <new-dropdown - :project-id="projectId" - :branch="branch.name" - path="" - /> - </div> - </div> - <repo-tree - :tree="branch.tree" - /> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue deleted file mode 100644 index a6f40286ac1..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_tree.vue +++ /dev/null @@ -1,65 +0,0 @@ -<script> -import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue'; -import Identicon from '../../vue_shared/components/identicon.vue'; -import BranchesTree from './ide_project_branches_tree.vue'; -import ExternalLinks from './ide_external_links.vue'; - -export default { - components: { - BranchesTree, - ExternalLinks, - ProjectAvatarImage, - Identicon, - }, - props: { - project: { - type: Object, - required: true, - }, - }, -}; -</script> - -<template> - <div class="projects-sidebar"> - <div class="context-header"> - <a - :title="project.name" - :href="project.web_url" - > - <div - v-if="project.avatar_url" - class="avatar-container s40 project-avatar" - > - <project-avatar-image - class="avatar-container project-avatar" - :link-href="project.path" - :img-src="project.avatar_url" - :img-alt="project.name" - :img-size="40" - /> - </div> - <identicon - v-else - size-class="s40" - :entity-id="project.id" - :entity-name="project.name" - /> - <div class="sidebar-context-title"> - {{ project.name }} - </div> - </a> - </div> - <external-links - :project-url="project.web_url" - /> - <div class="multi-file-commit-panel-inner-scroll"> - <branches-tree - v-for="branch in project.branches" - :key="branch.name" - :project-id="project.path_with_namespace" - :branch="branch" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 8cf1ccb4fce..191210fd828 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -1,36 +1,43 @@ <script> - import { mapState, mapGetters } from 'vuex'; - import icon from '~/vue_shared/components/icon.vue'; - import panelResizer from '~/vue_shared/components/panel_resizer.vue'; - import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; - import projectTree from './ide_project_tree.vue'; - import ResizablePanel from './resizable_panel.vue'; +import { mapState, mapGetters } from 'vuex'; +import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; +import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import Identicon from '../../vue_shared/components/identicon.vue'; +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'; - export default { - components: { - projectTree, - icon, - panelResizer, - skeletonLoadingContainer, - ResizablePanel, - }, - computed: { - ...mapState([ - 'loading', - ]), - ...mapGetters([ - 'projectsWithTrees', - ]), - }, - }; +export default { + components: { + Icon, + PanelResizer, + SkeletonLoadingContainer, + ResizablePanel, + ActivityBar, + ProjectAvatarImage, + Identicon, + CommitSection, + IdeTree, + }, + computed: { + ...mapState(['loading', 'currentBranchId', 'currentActivityView']), + ...mapGetters(['currentProject']), + }, +}; </script> <template> <resizable-panel :collapsible="false" - :initial-width="290" + :initial-width="340" side="left" > + <activity-bar + v-if="!loading" + /> <div class="multi-file-commit-panel-inner"> <template v-if="loading"> <div @@ -41,11 +48,50 @@ <skeleton-loading-container /> </div> </template> - <project-tree - v-for="project in projectsWithTrees" - :key="project.id" - :project="project" - /> + <template v-else> + <div class="context-header ide-context-header"> + <a + :href="currentProject.web_url" + > + <div + v-if="currentProject.avatar_url" + class="avatar-container s40 project-avatar" + > + <project-avatar-image + class="avatar-container project-avatar" + :link-href="currentProject.path" + :img-src="currentProject.avatar_url" + :img-alt="currentProject.name" + :img-size="40" + /> + </div> + <identicon + v-else + size-class="s40" + :entity-id="currentProject.id" + :entity-name="currentProject.name" + /> + <div class="ide-sidebar-project-title"> + <div class="sidebar-context-title"> + {{ currentProject.name }} + </div> + <div + class="sidebar-context-title ide-sidebar-branch-title" + > + <icon + name="branch" + css-classes="append-right-5" + />{{ currentBranchId }} + </div> + </div> + </a> + </div> + <div class="multi-file-commit-panel-inner-scroll"> + <component + :is="currentActivityView" + /> + </div> + </template> </div> </resizable-panel> </template> diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue index e6af88e04bc..ed83bf4109b 100644 --- a/app/assets/javascripts/ide/components/ide_repo_tree.vue +++ b/app/assets/javascripts/ide/components/ide_tree.vue @@ -1,17 +1,20 @@ <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 NewDropdown from './new_dropdown/index.vue'; export default { components: { + Icon, RepoFile, SkeletonLoadingContainer, + NewDropdown, }, - props: { - tree: { - type: Object, - required: true, - }, + computed: { + ...mapState(['currentBranchId']), + ...mapGetters(['currentProject', 'currentTree']), }, }; </script> @@ -20,7 +23,7 @@ export default { <div class="ide-file-list" > - <template v-if="tree.loading"> + <template v-if="!currentTree || currentTree.loading"> <div class="multi-file-loading-container" v-for="n in 3" @@ -30,8 +33,16 @@ export default { </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 tree.tree" + v-for="file in currentTree.tree" :key="file.key" :file="file" :level="0" diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index 877d1b5e026..a7d4253d3d7 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -23,18 +23,8 @@ export default { directives: { tooltip, }, - props: { - noChangesStateSvgPath: { - type: String, - required: true, - }, - committedStateSvgPath: { - type: String, - required: true, - }, - }, computed: { - ...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed']), + ...mapState(['changedFiles', 'stagedFiles']), ...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']), }, @@ -86,13 +76,11 @@ export default { action="unstageAllChanges" :action-btn-text="__('Unstage all')" item-action-component="unstage-button" - :show-toggle="false" :staged-list="true" /> <form class="form-horizontal multi-file-commit-form" @submit.prevent.stop="commitChanges" - v-if="!rightPanelCollapsed" > <commit-message-field :text="commitMessage" @@ -120,8 +108,6 @@ export default { </template> <empty-state v-else - :no-changes-state-svg-path="noChangesStateSvgPath" - :committed-state-svg-path="committedStateSvgPath" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index 35a362b01e0..99f67862636 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -66,10 +66,26 @@ export default { <template> <li + :class="{ + active: tab.active + }" @click="clickFile(tab)" @mouseover="mouseOverTab" @mouseout="mouseOutTab" > + <div + class="multi-file-tab" + :title="tab.url" + > + <file-icon + :file-name="tab.name" + :size="16" + /> + {{ tab.name }} + <file-status-icon + :file="tab" + /> + </div> <button type="button" class="multi-file-tab-close" @@ -86,22 +102,5 @@ export default { :file="tab" /> </button> - - <div - class="multi-file-tab" - :class="{ - active: tab.active - }" - :title="tab.url" - > - <file-icon - :file-name="tab.name" - :size="16" - /> - {{ tab.name }} - <file-status-icon - :file="tab" - /> - </div> </li> </template> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index b06da9f95d1..e497946a135 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -6,3 +6,8 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; // Commit message textarea export const MAX_TITLE_LENGTH = 50; export const MAX_BODY_LENGTH = 72; + +export const activityBarViews = { + edit: 'ide-tree', + commit: 'commit-section', +}; diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 4a0a303d5a6..ee30e5074b3 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -63,6 +63,8 @@ router.beforeEach((to, from, next) => { const fullProjectId = `${to.params.namespace}/${to.params.project}`; if (to.params.branch) { + store.dispatch('setCurrentBranchId', to.params.branch); + store.dispatch('getBranchData', { projectId: fullProjectId, branchId: to.params.branch, diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index cbfb3dc54f2..e5c47d09886 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -14,15 +14,16 @@ function initIde(el) { components: { ide, }, - render(createElement) { - return createElement('ide', { - props: { - emptyStateSvgPath: el.dataset.emptyStateSvgPath, - noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, - committedStateSvgPath: el.dataset.committedStateSvgPath, - }, + created() { + this.$store.dispatch('setEmptyStateSvgs', { + emptyStateSvgPath: el.dataset.emptyStateSvgPath, + noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, + committedStateSvgPath: el.dataset.committedStateSvgPath, }); }, + render(createElement) { + return createElement('ide'); + }, }); } diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index cbe43f5f7f2..ab3fb1422b8 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { } }; -export const toggleRightPanelCollapsed = ( - { dispatch, state }, - e = undefined, -) => { +export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => { if (e) { $(e.currentTarget) .tooltip('hide') @@ -137,6 +134,18 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); }; +export const updateActivityBarView = ({ commit }, view) => { + commit(types.UPDATE_ACTIVITY_BAR_VIEW, view); +}; + +export const setEmptyStateSvgs = ({ commit }, svgs) => { + commit(types.SET_EMPTY_STATE_SVGS, svgs); +}; + +export const setCurrentBranchId = ({ commit }, currentBranchId) => { + commit(types.SET_CURRENT_BRANCH, currentBranchId); +}; + export const toggleFileFinder = ({ commit }, fileFindVisible) => commit(types.TOGGLE_FILE_FINDER, fileFindVisible); diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index d782e0a84d2..a6ad7d54ae1 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -193,7 +193,7 @@ export const unstageChange = ({ commit }, path) => { }; export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => { - if (getters.activeFile && getters.activeFile === file && state.viewer === 'diff') { + if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') { return false; } diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index 4eb23b2ee0f..971a15a4036 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -55,7 +55,6 @@ export const getBranchData = ( branch: data, }); commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id }); - commit(types.SET_CURRENT_BRANCH, branchId); resolve(data); }) .catch(() => { diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index ec1ea155aee..a3a711cf096 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -1,5 +1,3 @@ -import { __ } from '~/locale'; - export const activeFile = state => state.openFiles.find(file => file.active) || null; export const addedFiles = state => state.changedFiles.filter(f => f.tempFile); @@ -30,15 +28,12 @@ export const currentMergeRequest = state => { return null; }; -// eslint-disable-next-line no-confusing-arrow -export const collapseButtonIcon = state => - state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right'; +export const currentProject = state => state.projects[state.currentProjectId]; -export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length; +export const currentTree = state => + state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; -// eslint-disable-next-line no-confusing-arrow -export const collapseButtonTooltip = state => - state.rightPanelCollapsed ? __('Expand sidebar') : __('Collapse sidebar'); +export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length; export const hasMergeRequest = state => !!state.currentMergeRequestId; diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index c7f08449d03..0f331f9a578 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -5,6 +5,7 @@ export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG'; 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'; +export const SET_EMPTY_STATE_SVGS = 'SET_EMPTY_STATE_SVGS'; // Project Mutation Types export const SET_PROJECT = 'SET_PROJECT'; @@ -59,4 +60,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; +export const UPDATE_ACTIVITY_BAR_VIEW = 'UPDATE_ACTIVITY_BAR_VIEW'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 2a6c136aeed..6590d7c146a 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -100,6 +100,21 @@ export default { delayViewerUpdated, }); }, + [types.UPDATE_ACTIVITY_BAR_VIEW](state, currentActivityView) { + Object.assign(state, { + currentActivityView, + }); + }, + [types.SET_EMPTY_STATE_SVGS]( + state, + { emptyStateSvgPath, noChangesStateSvgPath, committedStateSvgPath }, + ) { + Object.assign(state, { + emptyStateSvgPath, + noChangesStateSvgPath, + committedStateSvgPath, + }); + }, [types.TOGGLE_FILE_FINDER](state, fileFindVisible) { Object.assign(state, { fileFindVisible, diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js index c3041c77199..7f670eb744c 100644 --- a/app/assets/javascripts/ide/stores/mutations/file.js +++ b/app/assets/javascripts/ide/stores/mutations/file.js @@ -192,6 +192,10 @@ export default { return acc.concat(f); }, []); + } else { + openFiles = state.openFiles.map(f => + Object.assign(f, { active: f.key === key, opened: f.key === key }), + ); } Object.assign(state, { openFiles }); diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 3470bb9aec0..908df02f964 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -1,3 +1,5 @@ +import { activityBarViews } from '../constants'; + export default () => ({ currentProjectId: '', currentBranchId: '', @@ -18,5 +20,6 @@ export default () => ({ entries: {}, viewer: 'editor', delayViewerUpdated: false, + currentActivityView: activityBarViews.edit, fileFindVisible: false, }); diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 05cb0196ced..6a03c302d9c 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -177,25 +177,6 @@ } } - // Web IDE - .ide-sidebar-link { - color: $color-200; - background-color: $color-700; - - &:hover, - &:focus { - background-color: $color-500; - } - - &:active { - background: $color-800; - } - } - - .branch-container { - border-left-color: $color-700; - } - .branch-header-title { color: $color-700; } @@ -203,6 +184,18 @@ .ide-file-list .file.file-active { color: $color-700; } + + .ide-sidebar-link { + &:hover, + &:focus, + &.active { + color: $color-700; + } + + &.active { + box-shadow: inset 3px 0 $color-700; + } + } } body { @@ -343,9 +336,5 @@ body { .sidebar-top-level-items > li.active .badge { color: $theme-gray-900; } - - .ide-sidebar-link { - color: $white-light; - } } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 6d77260b658..fa918283d2d 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -55,6 +55,7 @@ white-space: nowrap; text-overflow: ellipsis; max-width: inherit; + line-height: 22px; svg { vertical-align: middle; @@ -78,7 +79,6 @@ .ide-new-btn { display: none; margin-bottom: -4px; - margin-right: -8px; } &:hover, @@ -111,20 +111,12 @@ .file-col-commit-message { display: flex; overflow: visible; - padding: 6px 12px; + padding: 6px 10px; } .multi-file-loading-container { margin-top: 10px; padding: 10px; - - .animation-container { - background: $gray-light; - - div { - background: $gray-light; - } - } } .multi-file-table-col-commit-message { @@ -151,7 +143,17 @@ } li { - position: relative; + display: flex; + align-items: center; + padding: $grid-size $gl-padding; + background-color: $gray-normal; + border-right: 1px solid $white-dark; + border-bottom: 1px solid $white-dark; + + &.active { + background-color: $white-light; + border-bottom-color: $white-light; + } } .dropdown { @@ -174,41 +176,36 @@ } .multi-file-tab { - @include str-truncated(150px); - padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding; - background-color: $gray-normal; - border-right: 1px solid $white-dark; - border-bottom: 1px solid $white-dark; + @include str-truncated(141px); cursor: pointer; svg { vertical-align: middle; } - - &.active { - background-color: $white-light; - border-bottom-color: $white-light; - } } .multi-file-tab-close { - position: absolute; - right: 8px; - top: 50%; width: 16px; height: 16px; padding: 0; + margin-left: $grid-size; background: none; border: 0; border-radius: $border-radius-default; color: $theme-gray-900; - transform: translateY(-50%); svg { position: relative; top: -1px; } + .ide-file-changed-icon { + display: block; + position: relative; + top: 1px; + right: 3px; + } + &:hover { background-color: $theme-gray-200; } @@ -311,6 +308,7 @@ .multi-file-editor-holder { height: 100%; + min-height: 0; } .preview-container { @@ -429,27 +427,27 @@ .multi-file-commit-panel { display: flex; position: relative; - flex-direction: column; width: 340px; padding: 0; background-color: $gray-light; - padding-right: 3px; + padding-right: 1px; - .projects-sidebar { - display: flex; - flex-direction: column; - height: 100%; + .context-header { + width: auto; + margin-right: 0; - .context-header { - width: auto; - margin-right: 0; + a:hover, + a:focus { + text-decoration: none; } } .multi-file-commit-panel-inner { display: flex; + flex: 1; flex-direction: column; height: 100%; + width: 0; } .multi-file-commit-panel-inner-scroll { @@ -457,68 +455,10 @@ flex: 1; flex-direction: column; overflow: auto; - } - - &.is-collapsed { - width: 60px; - - .multi-file-commit-list { - padding-top: $gl-padding; - overflow: hidden; - } - - .multi-file-context-bar-icon { - align-items: center; - - svg { - float: none; - margin: 0; - } - } - } - - .branch-container { - border-left: 4px solid; - margin-bottom: $gl-bar-padding; - } - - .branch-header { - background: $white-dark; - display: flex; - } - - .branch-header-title { - flex: 1; - padding: $grid-size $gl-padding; - font-weight: $gl-font-weight-bold; - - svg { - vertical-align: middle; - } - } - - .branch-header-btns { - padding: $gl-vert-padding $gl-padding; - } - - .left-collapse-btn { - display: none; - background: $gray-light; - text-align: left; + background-color: $white-light; + border-left: 1px solid $white-dark; border-top: 1px solid $white-dark; - - svg { - vertical-align: middle; - } - } -} - -.multi-file-context-bar-icon { - padding: 10px; - - svg { - margin-right: 10px; - float: left; + border-top-left-radius: $border-radius-small; } } @@ -802,7 +742,7 @@ position: absolute; top: 0; bottom: 0; - width: 3px; + width: 1px; background-color: $white-dark; &.dragright { @@ -816,32 +756,11 @@ .ide-commit-list-container { display: flex; + flex: 1; flex-direction: column; width: 100%; + min-height: 140px; padding: 0 16px; - - &:not(.is-collapsed) { - flex: 1; - min-height: 140px; - } - - &.is-collapsed { - .multi-file-commit-panel-header { - margin-left: -$gl-padding; - margin-right: -$gl-padding; - - svg { - margin-left: auto; - margin-right: auto; - } - - .multi-file-commit-panel-collapse-btn { - margin-right: auto; - margin-left: auto; - border-left: 0; - } - } - } } .ide-staged-action-btn { @@ -864,17 +783,55 @@ margin-left: 25px; } -.ide-external-links { - p { - margin: 0; - } -} - .ide-sidebar-link { - padding: $gl-padding-8 $gl-padding; display: flex; align-items: center; - font-weight: $gl-font-weight-bold; + position: relative; + height: 60px; + width: 100%; + padding: 0 $gl-padding; + color: $gl-text-color-secondary; + background-color: transparent; + border: 0; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + outline: 0; + + svg { + margin: 0 auto; + } + + &:hover { + background-color: $theme-gray-100; + } + + &:focus { + background-color: $theme-gray-200; + } + + &.active { + // extend width over border of sidebar section + width: calc(100% + 1px); + padding-right: $gl-padding + 1px; + background-color: $white-light; + border-top-color: $white-dark; + border-bottom-color: $white-dark; + + &::after { + content: ''; + position: absolute; + right: -1px; + top: 0; + bottom: 0; + width: 1px; + background: $white-light; + } + } +} + +.ide-activity-bar { + position: relative; + flex: 0 0 60px; } .ide-file-finder-overlay { @@ -967,3 +924,26 @@ background: transparent; resize: none; } + +.ide-tree-header { + display: flex; + align-items: center; + padding: 10px 0; + margin-left: 10px; + margin-right: 10px; + border-bottom: 1px solid $white-dark; + + .ide-new-btn { + margin-left: auto; + } +} + +.ide-sidebar-branch-title { + font-weight: $gl-font-weight-normal; + + svg { + position: relative; + top: 3px; + margin-top: -1px; + } +} diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index b242e41df1c..43581c5cdca 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -44,12 +44,16 @@ feature 'Multi-file editor new directory', :js do wait_for_requests + find('.js-ide-commit-mode').click + click_button 'Stage all' fill_in('commit-message', with: 'commit message ide') click_button('Commit') + find('.js-ide-edit-mode').click + expect(page).to have_content('folder name') end end diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index 7d65456e049..3d938df2ca5 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -34,6 +34,8 @@ feature 'Multi-file editor new file', :js do wait_for_requests + find('.js-ide-commit-mode').click + click_button 'Stage all' fill_in('commit-message', with: 'commit message ide') diff --git a/spec/javascripts/ide/components/activity_bar_spec.js b/spec/javascripts/ide/components/activity_bar_spec.js new file mode 100644 index 00000000000..68277ed15eb --- /dev/null +++ b/spec/javascripts/ide/components/activity_bar_spec.js @@ -0,0 +1,86 @@ +import Vue from 'vue'; +import store from '~/ide/stores'; +import { activityBarViews } from '~/ide/constants'; +import ActivityBar from '~/ide/components/activity_bar.vue'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { resetStore } from '../helpers'; + +describe('IDE activity bar', () => { + const Component = Vue.extend(ActivityBar); + let vm; + + beforeEach(() => { + Vue.set(store.state.projects, 'abcproject', { + web_url: 'testing', + }); + Vue.set(store.state, 'currentProjectId', 'abcproject'); + + vm = createComponentWithStore(Component, store); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + describe('goBackUrl', () => { + it('renders the Go Back link with the referrer when present', () => { + const fakeReferrer = '/example/README.md'; + spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer); + + vm.$mount(); + + expect(vm.goBackUrl).toEqual(fakeReferrer); + }); + + it('renders the Go Back link with the project url when referrer is not present', () => { + const fakeReferrer = ''; + spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer); + + vm.$mount(); + + expect(vm.goBackUrl).toEqual('testing'); + }); + }); + + describe('updateActivityBarView', () => { + beforeEach(() => { + spyOn(vm, 'updateActivityBarView'); + + vm.$mount(); + }); + + it('calls updateActivityBarView with edit value on click', () => { + vm.$el.querySelector('.js-ide-edit-mode').click(); + + expect(vm.updateActivityBarView).toHaveBeenCalledWith(activityBarViews.edit); + }); + + it('calls updateActivityBarView with commit value on click', () => { + vm.$el.querySelector('.js-ide-commit-mode').click(); + + expect(vm.updateActivityBarView).toHaveBeenCalledWith(activityBarViews.commit); + }); + }); + + describe('active item', () => { + beforeEach(() => { + vm.$mount(); + }); + + it('sets edit item active', () => { + expect(vm.$el.querySelector('.js-ide-edit-mode').classList).toContain('active'); + }); + + it('sets commit item active', done => { + vm.$store.state.currentActivityView = activityBarViews.commit; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-ide-commit-mode').classList).toContain('active'); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js b/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js index b80d08de7b1..5df2d46ca6b 100644 --- a/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/empty_state_spec.js @@ -10,10 +10,10 @@ describe('IDE commit panel empty state', () => { beforeEach(() => { const Component = Vue.extend(emptyState); - vm = createComponentWithStore(Component, store, { - noChangesStateSvgPath: 'no-changes', - committedStateSvgPath: 'committed-state', - }); + Vue.set(store.state, 'noChangesStateSvgPath', 'no-changes'); + Vue.set(store.state, 'committedStateSvgPath', 'committed-state'); + + vm = createComponentWithStore(Component, store); vm.$mount(); }); @@ -27,9 +27,7 @@ describe('IDE commit panel empty state', () => { describe('statusSvg', () => { it('uses noChangesStateSvgPath when commit message is empty', () => { expect(vm.statusSvg).toBe('no-changes'); - expect(vm.$el.querySelector('img').getAttribute('src')).toBe( - 'no-changes', - ); + expect(vm.$el.querySelector('img').getAttribute('src')).toBe('no-changes'); }); it('uses committedStateSvgPath when commit message exists', done => { @@ -37,9 +35,7 @@ describe('IDE commit panel empty state', () => { Vue.nextTick(() => { expect(vm.statusSvg).toBe('committed-state'); - expect(vm.$el.querySelector('img').getAttribute('src')).toBe( - 'committed-state', - ); + expect(vm.$el.querySelector('img').getAttribute('src')).toBe('committed-state'); done(); }); @@ -59,37 +55,4 @@ describe('IDE commit panel empty state', () => { done(); }); }); - - describe('toggle button', () => { - it('calls store action', () => { - spyOn(vm, 'toggleRightPanelCollapsed'); - - vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); - - expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled(); - }); - - it('renders collapsed class', done => { - vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('collapsed state', () => { - beforeEach(done => { - vm.$store.state.rightPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('does not render text & svg', () => { - expect(vm.$el.querySelector('img')).toBeNull(); - expect(vm.$el.textContent).not.toContain('No changes'); - }); - }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_spec.js index 62fc3f90ad1..412c272c8cc 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_spec.js @@ -50,35 +50,6 @@ describe('Multi-file editor commit sidebar list', () => { }); }); - describe('collapsed', () => { - beforeEach(done => { - vm.$store.state.rightPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('hides list', () => { - expect(vm.$el.querySelector('.list-unstyled')).toBeNull(); - expect(vm.$el.querySelector('.help-block')).toBeNull(); - }); - }); - - describe('with toggle', () => { - beforeEach(done => { - spyOn(vm, 'toggleRightPanelCollapsed'); - - vm.showToggle = true; - - Vue.nextTick(done); - }); - - it('calls setPanelCollapsedStatus when clickin toggle', () => { - vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); - - expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled(); - }); - }); - describe('action button', () => { beforeEach(() => { spyOn(vm, 'stageAllChanges'); diff --git a/spec/javascripts/ide/components/ide_context_bar_spec.js b/spec/javascripts/ide/components/ide_context_bar_spec.js deleted file mode 100644 index e17b051f137..00000000000 --- a/spec/javascripts/ide/components/ide_context_bar_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ideContextBar from '~/ide/components/ide_context_bar.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; - -describe('Multi-file editor right context bar', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(ideContextBar); - - vm = createComponentWithStore(Component, store, { - noChangesStateSvgPath: 'svg', - committedStateSvgPath: 'svg', - }); - - vm.$store.state.rightPanelCollapsed = false; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('collapsed', () => { - beforeEach(done => { - vm.$store.state.rightPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('adds collapsed class', () => { - expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/ide/components/ide_external_links_spec.js b/spec/javascripts/ide/components/ide_external_links_spec.js deleted file mode 100644 index 9f6cb459f3b..00000000000 --- a/spec/javascripts/ide/components/ide_external_links_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; -import ideExternalLinks from '~/ide/components/ide_external_links.vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('ide external links component', () => { - let vm; - let fakeReferrer; - let Component; - - const fakeProjectUrl = '/project/'; - - beforeEach(() => { - Component = Vue.extend(ideExternalLinks); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('goBackUrl', () => { - it('renders the Go Back link with the referrer when present', () => { - fakeReferrer = '/example/README.md'; - spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer); - - vm = createComponent(Component, { - projectUrl: fakeProjectUrl, - }).$mount(); - - expect(vm.goBackUrl).toEqual(fakeReferrer); - }); - - it('renders the Go Back link with the project url when referrer is not present', () => { - fakeReferrer = ''; - spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer); - - vm = createComponent(Component, { - projectUrl: fakeProjectUrl, - }).$mount(); - - expect(vm.goBackUrl).toEqual(fakeProjectUrl); - }); - }); -}); diff --git a/spec/javascripts/ide/components/ide_project_tree_spec.js b/spec/javascripts/ide/components/ide_project_tree_spec.js deleted file mode 100644 index 657682cb39c..00000000000 --- a/spec/javascripts/ide/components/ide_project_tree_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import Vue from 'vue'; -import ProjectTree from '~/ide/components/ide_project_tree.vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('IDE project tree', () => { - const Component = Vue.extend(ProjectTree); - let vm; - - beforeEach(() => { - vm = createComponent(Component, { - project: { - id: 1, - name: 'test', - web_url: gl.TEST_HOST, - avatar_url: '', - branches: [], - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders identicon when projct has no avatar', () => { - expect(vm.$el.querySelector('.identicon')).not.toBeNull(); - }); - - it('renders avatar image if project has avatar', done => { - vm.project.avatar_url = gl.TEST_HOST; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.identicon')).toBeNull(); - expect(vm.$el.querySelector('img.avatar')).not.toBeNull(); - - done(); - }); - }); -}); diff --git a/spec/javascripts/ide/components/ide_repo_tree_spec.js b/spec/javascripts/ide/components/ide_repo_tree_spec.js deleted file mode 100644 index e0fbc90ca61..00000000000 --- a/spec/javascripts/ide/components/ide_repo_tree_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; -import ideRepoTree from '~/ide/components/ide_repo_tree.vue'; -import createComponent from '../../helpers/vue_mount_component_helper'; -import { file } from '../helpers'; - -describe('IdeRepoTree', () => { - let vm; - let tree; - - beforeEach(() => { - const IdeRepoTree = Vue.extend(ideRepoTree); - - tree = { - tree: [file()], - loading: false, - }; - - vm = createComponent(IdeRepoTree, { - tree, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders a sidebar', () => { - expect(vm.$el.querySelector('.loading-file')).toBeNull(); - expect(vm.$el.querySelector('.file')).not.toBeNull(); - }); - - it('renders 3 loading files if tree is loading', done => { - tree.loading = true; - - vm.$nextTick(() => { - expect( - vm.$el.querySelectorAll('.multi-file-loading-container').length, - ).toEqual(3); - - done(); - }); - }); -}); diff --git a/spec/javascripts/ide/components/ide_side_bar_spec.js b/spec/javascripts/ide/components/ide_side_bar_spec.js index 699dae1ce2f..20ee20bc1d7 100644 --- a/spec/javascripts/ide/components/ide_side_bar_spec.js +++ b/spec/javascripts/ide/components/ide_side_bar_spec.js @@ -1,8 +1,10 @@ import Vue from 'vue'; import store from '~/ide/stores'; import ideSidebar from '~/ide/components/ide_side_bar.vue'; +import { activityBarViews } from '~/ide/constants'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; +import { projectData } from '../mock_data'; describe('IdeSidebar', () => { let vm; @@ -10,6 +12,9 @@ describe('IdeSidebar', () => { beforeEach(() => { const Component = Vue.extend(ideSidebar); + store.state.currentProjectId = 'abcproject'; + store.state.projects.abcproject = projectData; + vm = createComponentWithStore(Component, store).$mount(); }); @@ -20,23 +25,33 @@ describe('IdeSidebar', () => { }); it('renders a sidebar', () => { - expect( - vm.$el.querySelector('.multi-file-commit-panel-inner'), - ).not.toBeNull(); + expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull(); }); it('renders loading icon component', done => { vm.$store.state.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); + expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull(); + expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); done(); }); }); + + describe('activityBarComponent', () => { + it('renders tree component', () => { + expect(vm.$el.querySelector('.ide-file-list')).not.toBeNull(); + }); + + it('renders commit component', done => { + vm.$store.state.currentActivityView = activityBarViews.commit; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.multi-file-commit-panel-section')).not.toBeNull(); + + done(); + }); + }); + }); }); diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 7bfcfc90572..6f580e1f7af 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -4,6 +4,7 @@ import store from '~/ide/stores'; import ide from '~/ide/components/ide.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../helpers'; +import { projectData } from '../mock_data'; describe('ide component', () => { let vm; @@ -11,6 +12,10 @@ describe('ide component', () => { beforeEach(() => { const Component = Vue.extend(ide); + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = Object.assign({}, projectData); + vm = createComponentWithStore(Component, store, { emptyStateSvgPath: 'svg', noChangesStateSvgPath: 'svg', @@ -24,11 +29,11 @@ describe('ide component', () => { resetStore(vm.$store); }); - it('does not render panel right when no files open', () => { + it('does not render right right when no files open', () => { expect(vm.$el.querySelector('.panel-right')).toBeNull(); }); - it('renders panel right when files are open', done => { + it('renders right panel when files are open', done => { vm.$store.state.trees['abcproject/mybranch'] = { tree: [file()], }; diff --git a/spec/javascripts/ide/components/ide_tree_spec.js b/spec/javascripts/ide/components/ide_tree_spec.js new file mode 100644 index 00000000000..bdc03692b40 --- /dev/null +++ b/spec/javascripts/ide/components/ide_tree_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; +import IdeTree from '~/ide/components/ide_tree.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('IdeRepoTree', () => { + let vm; + + beforeEach(() => { + const IdeRepoTree = Vue.extend(IdeTree); + + 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(IdeRepoTree, store).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + 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_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js index 768f6e99bf1..c2b2761cf61 100644 --- a/spec/javascripts/ide/components/repo_commit_section_spec.js +++ b/spec/javascripts/ide/components/repo_commit_section_spec.js @@ -12,10 +12,10 @@ describe('RepoCommitSection', () => { function createComponent() { const Component = Vue.extend(repoCommitSection); - vm = createComponentWithStore(Component, store, { - noChangesStateSvgPath: 'svg', - committedStateSvgPath: 'commitsvg', - }); + store.state.noChangesStateSvgPath = 'svg'; + store.state.committedStateSvgPath = 'commitsvg'; + + vm = createComponentWithStore(Component, store); vm.$store.state.currentProjectId = 'abcproject'; vm.$store.state.currentBranchId = 'master'; @@ -93,28 +93,20 @@ describe('RepoCommitSection', () => { resetStore(vm.$store); const Component = Vue.extend(repoCommitSection); - vm = createComponentWithStore(Component, store, { - noChangesStateSvgPath: 'nochangessvg', - committedStateSvgPath: 'svg', - }).$mount(); + store.state.noChangesStateSvgPath = 'nochangessvg'; + store.state.committedStateSvgPath = 'svg'; - expect( - vm.$el.querySelector('.js-empty-state').textContent.trim(), - ).toContain('No changes'); - expect( - vm.$el.querySelector('.js-empty-state img').getAttribute('src'), - ).toBe('nochangessvg'); + vm = createComponentWithStore(Component, store).$mount(); + + expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('No changes'); + expect(vm.$el.querySelector('.js-empty-state img').getAttribute('src')).toBe('nochangessvg'); }); }); it('renders a commit section', () => { - const changedFileElements = [ - ...vm.$el.querySelectorAll('.multi-file-commit-list li'), - ]; + const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')]; const submitCommit = vm.$el.querySelector('form .btn'); - const allFiles = vm.$store.state.changedFiles.concat( - vm.$store.state.stagedFiles, - ); + const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles); expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull(); expect(changedFileElements.length).toEqual(4); @@ -131,9 +123,9 @@ describe('RepoCommitSection', () => { vm.$el.querySelector('.ide-staged-action-btn').click(); Vue.nextTick(() => { - expect( - vm.$el.querySelector('.ide-commit-list-container').textContent, - ).toContain('No changes'); + expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain( + 'No changes', + ); done(); }); @@ -143,11 +135,9 @@ describe('RepoCommitSection', () => { vm.$el.querySelector('.multi-file-discard-btn .btn').click(); Vue.nextTick(() => { - expect( - vm.$el - .querySelector('.ide-commit-list-container') - .querySelectorAll('li').length, - ).toBe(1); + expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe( + 1, + ); done(); }); @@ -157,14 +147,10 @@ describe('RepoCommitSection', () => { vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click(); Vue.nextTick(() => { - expect( - vm.$el.querySelector('.ide-commit-list-container').textContent, - ).not.toContain('file1'); - expect( - vm.$el - .querySelector('.ide-commit-list-container') - .querySelectorAll('li').length, - ).toBe(1); + expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1'); + expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe( + 1, + ); done(); }); @@ -174,9 +160,9 @@ describe('RepoCommitSection', () => { vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click(); Vue.nextTick(() => { - expect( - vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent, - ).toContain('No changes'); + expect(vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent).toContain( + 'No changes', + ); done(); }); @@ -190,9 +176,7 @@ describe('RepoCommitSection', () => { Vue.nextTick(() => { expect( - vm.$el - .querySelectorAll('.ide-commit-list-container')[1] - .querySelectorAll('li').length, + vm.$el.querySelectorAll('.ide-commit-list-container')[1].querySelectorAll('li').length, ).toBe(1); done(); @@ -208,9 +192,7 @@ describe('RepoCommitSection', () => { getSetTimeoutPromise() .then(() => { - expect(vm.$store.state.commit.commitMessage).toBe( - 'testing commit message', - ); + expect(vm.$store.state.commit.commitMessage).toBe('testing commit message'); }) .then(done) .catch(done.fail); @@ -218,9 +200,7 @@ describe('RepoCommitSection', () => { describe('discard draft button', () => { it('hidden when commitMessage is empty', () => { - expect( - vm.$el.querySelector('.multi-file-commit-form .btn-default'), - ).toBeNull(); + expect(vm.$el.querySelector('.multi-file-commit-form .btn-default')).toBeNull(); }); it('resets commitMessage when clicking discard button', done => { @@ -232,9 +212,7 @@ describe('RepoCommitSection', () => { }) .then(Vue.nextTick) .then(() => { - expect(vm.$store.state.commit.commitMessage).not.toBe( - 'testing commit message', - ); + expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message'); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/ide/components/repo_tabs_spec.js b/spec/javascripts/ide/components/repo_tabs_spec.js index cb785ba2cd3..5c4bcc372bc 100644 --- a/spec/javascripts/ide/components/repo_tabs_spec.js +++ b/spec/javascripts/ide/components/repo_tabs_spec.js @@ -26,8 +26,8 @@ describe('RepoTabs', () => { const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')]; expect(tabs.length).toEqual(2); - expect(tabs[0].classList.contains('active')).toEqual(true); - expect(tabs[1].classList.contains('active')).toEqual(false); + expect(tabs[0].parentNode.classList.contains('active')).toEqual(true); + expect(tabs[1].parentNode.classList.contains('active')).toEqual(false); done(); }); diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js new file mode 100644 index 00000000000..c8206778639 --- /dev/null +++ b/spec/javascripts/ide/mock_data.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/prefer-default-export +export const projectData = { + id: 1, + name: 'abcproject', + web_url: '', + avatar_url: '', + path: '', + name_with_namespace: 'namespace/abcproject', + branches: { + master: { + treeId: 'abcproject/master', + }, + }, +}; diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index b6eadf56f9d..2e773062524 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -1,4 +1,11 @@ -import actions, { stageAllChanges, unstageAllChanges, toggleFileFinder } from '~/ide/stores/actions'; +import actions, { + stageAllChanges, + unstageAllChanges, + toggleFileFinder, + setCurrentBranchId, + setEmptyStateSvgs, + updateActivityBarView, +} from '~/ide/stores/actions'; import store from '~/ide/stores'; import * as types from '~/ide/stores/mutation_types'; import router from '~/ide/ide_router'; @@ -340,6 +347,45 @@ describe('Multi-file store actions', () => { }); }); + describe('updateActivityBarView', () => { + it('commits UPDATE_ACTIVITY_BAR_VIEW', done => { + testAction( + updateActivityBarView, + 'test', + {}, + [{ type: 'UPDATE_ACTIVITY_BAR_VIEW', payload: 'test' }], + [], + done, + ); + }); + }); + + describe('setEmptyStateSvgs', () => { + it('commits setEmptyStateSvgs', done => { + testAction( + setEmptyStateSvgs, + 'svg', + {}, + [{ type: 'SET_EMPTY_STATE_SVGS', payload: 'svg' }], + [], + done, + ); + }); + }); + + describe('setCurrentBranchId', () => { + it('commits setCurrentBranchId', done => { + testAction( + setCurrentBranchId, + 'branchId', + {}, + [{ type: 'SET_CURRENT_BRANCH', payload: 'branchId' }], + [], + done, + ); + }); + }); + describe('toggleFileFinder', () => { it('commits TOGGLE_FILE_FINDER', done => { testAction( diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js index b6b4dd28729..68e0cceef76 100644 --- a/spec/javascripts/ide/stores/getters_spec.js +++ b/spec/javascripts/ide/stores/getters_spec.js @@ -37,12 +37,6 @@ describe('IDE store getters', () => { expect(modifiedFiles.length).toBe(1); expect(modifiedFiles[0].name).toBe('changed'); }); - - it('returns angle left when collapsed', () => { - localState.rightPanelCollapsed = true; - - expect(getters.collapseButtonIcon(localState)).toBe('angle-double-left'); - }); }); describe('currentMergeRequest', () => { diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 575039e755e..6a7897a9e4d 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => { }); }); + describe('UPDATE_ACTIVITY_BAR_VIEW', () => { + it('updates currentActivityBar', () => { + mutations.UPDATE_ACTIVITY_BAR_VIEW(localState, 'test'); + + expect(localState.currentActivityView).toBe('test'); + }); + }); + + describe('SET_EMPTY_STATE_SVGS', () => { + it('updates empty state SVGs', () => { + mutations.SET_EMPTY_STATE_SVGS(localState, { + emptyStateSvgPath: 'emptyState', + noChangesStateSvgPath: 'noChanges', + committedStateSvgPath: 'commited', + }); + + expect(localState.emptyStateSvgPath).toBe('emptyState'); + expect(localState.noChangesStateSvgPath).toBe('noChanges'); + expect(localState.committedStateSvgPath).toBe('commited'); + }); + }); + describe('TOGGLE_FILE_FINDER', () => { it('updates fileFindVisible', () => { mutations.TOGGLE_FILE_FINDER(localState, true); |