diff options
author | Phil Hughes <me@iamphill.com> | 2017-10-13 10:08:10 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-10-16 11:29:30 +0100 |
commit | b1b91aa0658d81107327884ca56f579cf6146078 (patch) | |
tree | 999ed5273e0b42aa6848b76bbfefc6acb39c85f5 | |
parent | 3a7623fc010832c337e0ba0eb8650d7f6fca3562 (diff) | |
download | gitlab-ce-b1b91aa0658d81107327884ca56f579cf6146078.tar.gz |
Refactored multi-file data structure
This moves away from storing in a single array just to render the table.
It now stores in a multi-dimensional array/object type where each entry
in the array can have its own tree. This makes storing the data for
future feature a little easier as there is only one way to store the
data.
Previously to insert a directory the code had to insert the directory
& then the file at the right point in the array. Now the directory
can be inserted anywhere & then a file can be quickly added into this
directory.
The rendering is still done with a single array, but this is handled
through underscore. Underscore takes the array & then goes through
each item to flatten it into one. It is done this way to save changing
the markup away from table, keeping it as a table keeps it semantically
correct.
17 files changed, 301 insertions, 483 deletions
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue index 02d9c775046..5d648de4405 100644 --- a/app/assets/javascripts/repo/components/repo_editor.vue +++ b/app/assets/javascripts/repo/components/repo_editor.vue @@ -92,7 +92,7 @@ const RepoEditor = { }, blobRaw() { - if (Helper.monacoInstance && !this.isTree) { + if (Helper.monacoInstance) { this.setupEditor(); } }, diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue index 8b9cbd23456..6236126fad9 100644 --- a/app/assets/javascripts/repo/components/repo_file.vue +++ b/app/assets/javascripts/repo/components/repo_file.vue @@ -1,107 +1,77 @@ <script> -import TimeAgoMixin from '../../vue_shared/mixins/timeago'; + import timeAgoMixin from '../../vue_shared/mixins/timeago'; + import eventHub from '../event_hub'; + import repoMixin from '../mixins/repo_mixin'; -const RepoFile = { - mixins: [TimeAgoMixin], - props: { - file: { - type: Object, - required: true, + export default { + mixins: [ + repoMixin, + timeAgoMixin, + ], + props: { + file: { + type: Object, + required: true, + }, }, - isMini: { - type: Boolean, - required: false, - default: false, + computed: { + fileIcon() { + const classObj = { + 'fa-spinner fa-spin': this.file.loading, + [this.file.icon]: !this.file.loading, + 'fa-folder-open': !this.file.loading && this.file.opened, + }; + return classObj; + }, + levelIndentation() { + return { + marginLeft: `${this.file.level * 16}px`, + }; + }, }, - loading: { - type: Object, - required: false, - default() { return { tree: false }; }, + methods: { + linkClicked(file) { + eventHub.$emit('linkclicked', file); + }, }, - hasFiles: { - type: Boolean, - required: false, - default: false, - }, - activeFile: { - type: Object, - required: true, - }, - }, - - computed: { - canShowFile() { - return !this.loading.tree || this.hasFiles; - }, - - fileIcon() { - const classObj = { - 'fa-spinner fa-spin': this.file.loading, - [this.file.icon]: !this.file.loading, - }; - return classObj; - }, - - fileIndentation() { - return { - 'margin-left': `${this.file.level * 10}px`, - }; - }, - - activeFileClass() { - return { - active: this.activeFile.url === this.file.url, - }; - }, - }, - - methods: { - linkClicked(file) { - this.$emit('linkclicked', file); - }, - }, -}; - -export default RepoFile; + }; </script> <template> -<tr - v-if="canShowFile" - class="file" - :class="activeFileClass" - @click.prevent="linkClicked(file)"> - <td> - <i - class="fa fa-fw file-icon" - :class="fileIcon" - :style="fileIndentation" - aria-label="file icon"> - </i> - <a - :href="file.url" - class="repo-file-name" - :title="file.url"> - {{file.name}} - </a> - </td> + <tr + class="file" + @click.stop="linkClicked(file)"> + <td> + <i + class="fa fa-fw file-icon" + :class="fileIcon" + :style="levelIndentation" + aria-hidden="true" + > + </i> + <a + :href="file.url" + class="repo-file-name" + > + {{ file.name }} + </a> + </td> - <template v-if="!isMini"> - <td class="hidden-sm hidden-xs"> - <div class="commit-message"> - <a @click.stop :href="file.lastCommitUrl"> - {{file.lastCommitMessage}} + <template v-if="!isMini"> + <td class="hidden-sm hidden-xs"> + <a + @click.stop + :href="file.lastCommit.url" + > + {{ file.lastCommit.message }} </a> - </div> - </td> + </td> - <td class="hidden-xs text-right"> - <span - class="commit-update" - :title="tooltipTitle(file.lastCommitUpdate)"> - {{timeFormated(file.lastCommitUpdate)}} - </span> - </td> - </template> -</tr> + <td class="hidden-xs text-right"> + <span :title="tooltipTitle(file.lastCommit.updatedAt)"> + {{ timeFormated(file.lastCommit.updatedAt) }} + </span> + </td> + </template> + </tr> </template> diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue deleted file mode 100644 index 6a15755f029..00000000000 --- a/app/assets/javascripts/repo/components/repo_file_options.vue +++ /dev/null @@ -1,25 +0,0 @@ -<script> -const RepoFileOptions = { - props: { - isMini: { - type: Boolean, - required: false, - default: false, - }, - projectName: { - type: String, - required: true, - }, - }, -}; - -export default RepoFileOptions; -</script> - -<template> - <tr v-if="isMini" class="repo-file-options"> - <td> - <span class="title">{{projectName}}</span> - </td> - </tr> -</template> diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue index bc8c64c8362..fb82fb260b6 100644 --- a/app/assets/javascripts/repo/components/repo_loading_file.vue +++ b/app/assets/javascripts/repo/components/repo_loading_file.vue @@ -1,42 +1,20 @@ <script> -const RepoLoadingFile = { - props: { - loading: { - type: Object, - required: false, - default: {}, - }, - hasFiles: { - type: Boolean, - required: false, - default: false, - }, - isMini: { - type: Boolean, - required: false, - default: false, - }, - }, + import repoMixin from '../mixins/repo_mixin'; - computed: { - showGhostLines() { - return this.loading.tree && !this.hasFiles; + export default { + mixins: [ + repoMixin, + ], + methods: { + lineOfCode(n) { + return `skeleton-line-${n}`; + }, }, - }, - - methods: { - lineOfCode(n) { - return `skeleton-line-${n}`; - }, - }, -}; - -export default RepoLoadingFile; + }; </script> <template> <tr - v-if="showGhostLines" class="loading-file"> <td> <div @@ -64,7 +42,7 @@ export default RepoLoadingFile; <td v-if="!isMini" class="hidden-xs"> - <div class="animation-container animation-container-small"> + <div class="animation-container animation-container-small animation-container-right"> <div v-for="n in 6" :key="n" diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue index bbdbdc61e38..d042fc664b6 100644 --- a/app/assets/javascripts/repo/components/repo_prev_directory.vue +++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue @@ -1,38 +1,34 @@ <script> -import RepoMixin from '../mixins/repo_mixin'; + import eventHub from '../event_hub'; -const RepoPreviousDirectory = { - props: { - prevUrl: { - type: String, - required: true, + export default { + props: { + prevUrl: { + type: String, + required: true, + }, }, - }, - - mixins: [RepoMixin], - - computed: { - colSpanCondition() { - return this.isMini ? undefined : 3; + computed: { + colSpanCondition() { + return this.isMini ? undefined : 3; + }, }, - }, - - methods: { - linkClicked(file) { - this.$emit('linkclicked', file); + methods: { + linkClicked(file) { + eventHub.$emit('goToPreviousDirectoryClicked', file); + }, }, - }, -}; - -export default RepoPreviousDirectory; + }; </script> <template> -<tr class="prev-directory"> - <td - :colspan="colSpanCondition" - @click.prevent="linkClicked(prevUrl)"> - <a :href="prevUrl">..</a> - </td> -</tr> + <tr class="prev-directory"> + <td + :colspan="colSpanCondition" + class="table-cell" + @click.prevent="linkClicked(prevUrl)" + > + <a :href="prevUrl">...</a> + </td> + </tr> </template> diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue index e0f3c33003a..a8eb92b0186 100644 --- a/app/assets/javascripts/repo/components/repo_sidebar.vue +++ b/app/assets/javascripts/repo/components/repo_sidebar.vue @@ -2,8 +2,8 @@ import Service from '../services/repo_service'; import Helper from '../helpers/repo_helper'; import Store from '../stores/repo_store'; +import eventHub from '../event_hub'; import RepoPreviousDirectory from './repo_prev_directory.vue'; -import RepoFileOptions from './repo_file_options.vue'; import RepoFile from './repo_file.vue'; import RepoLoadingFile from './repo_loading_file.vue'; import RepoMixin from '../mixins/repo_mixin'; @@ -11,21 +11,39 @@ import RepoMixin from '../mixins/repo_mixin'; export default { mixins: [RepoMixin], components: { - 'repo-file-options': RepoFileOptions, 'repo-previous-directory': RepoPreviousDirectory, 'repo-file': RepoFile, 'repo-loading-file': RepoLoadingFile, }, - created() { window.addEventListener('popstate', this.checkHistory); }, destroyed() { + eventHub.$off('linkclicked', this.fileClicked); + eventHub.$off('goToPreviousDirectoryClicked', this.goToPreviousDirectoryClicked); window.removeEventListener('popstate', this.checkHistory); }, - + mounted() { + eventHub.$on('linkclicked', this.fileClicked); + eventHub.$on('goToPreviousDirectoryClicked', this.goToPreviousDirectoryClicked); + }, data: () => Store, + computed: { + flattendFiles() { + const map = (arr) => { + if (arr && arr.tree.length === 0) { + return []; + } + + return _.map(arr.tree, a => [a, map(a)]); + }; + return _.chain(this.files) + .map(arr => [arr, map(arr)]) + .flatten() + .value(); + }, + }, methods: { checkHistory() { let selectedFile = this.files.find(file => location.pathname.indexOf(file.url) > -1); @@ -52,16 +70,17 @@ export default { }, fileClicked(clickedFile, lineNumber) { - let file = clickedFile; + const file = clickedFile; + if (file.loading) return; - file.loading = true; if (file.type === 'tree' && file.opened) { - file = Store.removeChildFilesOfTree(file); - file.loading = false; + Helper.setDirectoryToClosed(file); Store.setActiveLine(lineNumber); } else { const openFile = Helper.getFileFromPath(file.url); + file.loading = true; + if (openFile) { file.loading = false; Store.setActiveFiles(openFile); @@ -92,38 +111,43 @@ export default { <template> <div id="sidebar" :class="{'sidebar-mini' : isMini}"> <table class="table"> - <thead v-if="!isMini"> + <thead> <tr> - <th class="name">Name</th> - <th class="hidden-sm hidden-xs last-commit">Last commit</th> - <th class="hidden-xs last-update text-right">Last update</th> + <th + v-if="isMini" + class="repo-file-options title" + > + <strong class="clgray"> + {{ projectName }} + </strong> + </th> + <template v-else> + <th class="name"> + Name + </th> + <th class="hidden-sm hidden-xs last-commit"> + Last commit + </th> + <th class="hidden-xs last-update text-right"> + Last update + </th> + </template> </tr> </thead> <tbody> - <repo-file-options - :is-mini="isMini" - :project-name="projectName" - /> <repo-previous-directory - v-if="isRoot" + v-if="!isRoot" :prev-url="prevURL" - @linkclicked="goToPreviousDirectoryClicked(prevURL)"/> + /> <repo-loading-file + v-if="!flattendFiles.length && loading.tree" v-for="n in 5" :key="n" - :loading="loading" - :has-files="!!files.length" - :is-mini="isMini" /> <repo-file - v-for="file in files" + v-for="file in flattendFiles" :key="file.id" :file="file" - :is-mini="isMini" - @linkclicked="fileClicked(file)" - :is-tree="isTree" - :has-files="!!files.length" - :active-file="activeFile" /> </tbody> </table> diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue index 0d0c34ec741..098715915b0 100644 --- a/app/assets/javascripts/repo/components/repo_tab.vue +++ b/app/assets/javascripts/repo/components/repo_tab.vue @@ -26,11 +26,13 @@ const RepoTab = { }, methods: { - tabClicked: Store.setActiveFiles, - + tabClicked(file) { + Store.setActiveFiles(file); + }, closeTab(file) { if (file.changed) return; - this.$emit('tabclosed', file); + + Store.removeFromOpenedFiles(file); }, }, }; @@ -39,25 +41,28 @@ export default RepoTab; </script> <template> -<li @click="tabClicked(tab)"> - <a - href="#0" - class="close" - @click.stop.prevent="closeTab(tab)" - :aria-label="closeLabel"> - <i - class="fa" - :class="changedClass" - aria-hidden="true"> - </i> - </a> + <li + :class="{ active : tab.active }" + @click="tabClicked(tab)" + > + <button + type="button" + class="close-btn" + @click.stop.prevent="closeTab(tab)" + :aria-label="closeLabel"> + <i + class="fa" + :class="changedClass" + aria-hidden="true"> + </i> + </button> - <a - href="#" - class="repo-tab" - :title="tab.url" - @click.prevent="tabClicked(tab)"> - {{tab.name}} - </a> -</li> + <a + href="#" + class="repo-tab" + :title="tab.url" + @click.prevent="tabClicked(tab)"> + {{tab.name}} + </a> + </li> </template> diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue index 9c5bfc5d0cf..2759335cc67 100644 --- a/app/assets/javascripts/repo/components/repo_tabs.vue +++ b/app/assets/javascripts/repo/components/repo_tabs.vue @@ -1,36 +1,27 @@ <script> -import Store from '../stores/repo_store'; -import RepoTab from './repo_tab.vue'; -import RepoMixin from '../mixins/repo_mixin'; + import Store from '../stores/repo_store'; + import RepoTab from './repo_tab.vue'; + import RepoMixin from '../mixins/repo_mixin'; -const RepoTabs = { - mixins: [RepoMixin], - - components: { - 'repo-tab': RepoTab, - }, - - data: () => Store, - - methods: { - tabClosed(file) { - Store.removeFromOpenedFiles(file); + export default { + mixins: [RepoMixin], + components: { + 'repo-tab': RepoTab, }, - }, -}; - -export default RepoTabs; + data: () => Store, + }; </script> <template> -<ul id="tabs"> - <repo-tab - v-for="tab in openedFiles" - :key="tab.id" - :tab="tab" - :class="{'active' : tab.active}" - @tabclosed="tabClosed" - /> - <li class="tabs-divider" /> -</ul> + <ul + id="tabs" + class="list-unstyled" + > + <repo-tab + v-for="tab in openedFiles" + :key="tab.id" + :tab="tab" + /> + <li class="tabs-divider" /> + </ul> </template> diff --git a/app/assets/javascripts/repo/event_hub.js b/app/assets/javascripts/repo/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/repo/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js index 46204598e1d..9d05db0e414 100644 --- a/app/assets/javascripts/repo/helpers/repo_helper.js +++ b/app/assets/javascripts/repo/helpers/repo_helper.js @@ -1,3 +1,4 @@ +import { convertPermissionToBoolean } from '../../lib/utils/common_utils'; import Service from '../services/repo_service'; import Store from '../stores/repo_store'; import Flash from '../../flash'; @@ -25,10 +26,6 @@ const RepoHelper = { key: '', - isTree(data) { - return Object.hasOwnProperty.call(data, 'blobs'); - }, - Time: window.performance && window.performance.now ? window.performance @@ -59,12 +56,17 @@ const RepoHelper = { setDirectoryOpen(tree, title) { const file = tree; - if (!file) return undefined; + if (!file) return; file.opened = true; - file.icon = 'fa-folder-open'; RepoHelper.updateHistoryEntry(file.url, title); - return file; + }, + + setDirectoryToClosed(entry) { + const dir = entry; + + dir.opened = false; + dir.tree = []; }, isRenderable() { @@ -81,63 +83,20 @@ const RepoHelper = { .catch(RepoHelper.loadingError); }, - // when you open a directory you need to put the directory files under - // the directory... This will merge the list of the current directory and the new list. - getNewMergedList(inDirectory, currentList, newList) { - const newListSorted = newList.sort(this.compareFilesCaseInsensitive); - if (!inDirectory) return newListSorted; - const indexOfFile = currentList.findIndex(file => file.url === inDirectory.url); - if (!indexOfFile) return newListSorted; - return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile); - }, - - // within the get new merged list this does the merging of the current list of files - // and the new list of files. The files are never "in" another directory they just - // appear like they are because of the margin. - mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) { - newList.reverse().forEach((newFile) => { - const fileIndex = indexOfFile + 1; - const file = newFile; - file.level = inDirectory.level + 1; - oldList.splice(fileIndex, 0, file); - }); - - return oldList; - }, - - compareFilesCaseInsensitive(a, b) { - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - if (a.level > 0) return 0; - if (aName < bName) { return -1; } - if (aName > bName) { return 1; } - return 0; - }, - - isRoot(url) { - // the url we are requesting -> split by the project URL. Grab the right side. - const isRoot = !!url.split(Store.projectUrl)[1] - // remove the first "/" - .slice(1) - // split this by "/" - .split('/') - // remove the first two items of the array... usually /tree/master. - .slice(2) - // we want to know the length of the array. - // If greater than 0 not root. - .length; - return isRoot; - }, - getContent(treeOrFile) { let file = treeOrFile; + + if (!Store.files.length) { + Store.loading.tree = true; + } + return Service.getContent() .then((response) => { const data = response.data; if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title']; + if (response.headers && response.headers['is-root']) Store.isRoot = convertPermissionToBoolean(response.headers['is-root']); - Store.isTree = RepoHelper.isTree(data); - if (!Store.isTree) { + if (file && file.type === 'blob') { if (!file) file = data; Store.binary = data.binary; @@ -145,33 +104,28 @@ const RepoHelper = { // file might be undefined RepoHelper.setBinaryDataAsBase64(data); Store.setViewToPreview(); - } else if (!Store.isPreviewView()) { - if (!data.render_error) { - Service.getRaw(data.raw_path) - .then((rawResponse) => { - Store.blobRaw = rawResponse.data; - data.plain = rawResponse.data; - RepoHelper.setFile(data, file); - }).catch(RepoHelper.loadingError); - } + } else if (!Store.isPreviewView() && !data.render_error) { + Service.getRaw(data.raw_path) + .then((rawResponse) => { + Store.blobRaw = rawResponse.data; + data.plain = rawResponse.data; + RepoHelper.setFile(data, file); + }).catch(RepoHelper.loadingError); } if (Store.isPreviewView()) { RepoHelper.setFile(data, file); } + } else { + Store.loading.tree = false; + RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name); - // if the file tree is empty - if (Store.files.length === 0) { - const parentURL = Service.blobURLtoParentTree(Service.url); - Service.url = parentURL; - RepoHelper.getContent(); + if (!file) { + Store.files = this.dataToListOfFiles(data); + } else { + file.tree = this.dataToListOfFiles(data, file.level + 1); } - } else { - // it's a tree - if (!file) Store.isRoot = RepoHelper.isRoot(Service.url); - file = RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name); - const newDirectory = RepoHelper.dataToListOfFiles(data); - Store.addFilesToDirectory(file, Store.files, newDirectory); + Store.prevURL = Service.blobURLtoParentTree(Service.url); } }).catch(RepoHelper.loadingError); @@ -190,57 +144,57 @@ const RepoHelper = { Store.setActiveFiles(newFile); }, - serializeBlob(blob) { - const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob); - simpleBlob.lastCommitMessage = blob.last_commit.message; - simpleBlob.lastCommitUpdate = blob.last_commit.committed_date; - simpleBlob.loading = false; - - return simpleBlob; + serializeBlob(blob, level) { + return RepoHelper.serializeRepoEntity('blob', blob, level); }, - serializeTree(tree) { - return RepoHelper.serializeRepoEntity('tree', tree); + serializeTree(tree, level) { + return RepoHelper.serializeRepoEntity('tree', tree, level); }, - serializeSubmodule(submodule) { - return RepoHelper.serializeRepoEntity('submodule', submodule); + serializeSubmodule(submodule, level) { + return RepoHelper.serializeRepoEntity('submodule', submodule, level); }, - serializeRepoEntity(type, entity) { + serializeRepoEntity(type, entity, level = 0) { const { url, name, icon, last_commit } = entity; const returnObj = { type, name, url, + level, icon: `fa-${icon}`, - level: 0, + tree: [], loading: false, + opened: false, }; - if (entity.last_commit) { - returnObj.lastCommitUrl = `${Store.projectUrl}/commit/${last_commit.id}`; + // eslint-disable-next-line camelcase + if (last_commit) { + returnObj.lastCommit = { + url: `${Store.projectUrl}/commit/${last_commit.id}`, + message: last_commit.message, + updatedAt: last_commit.committed_date, + }; } else { - returnObj.lastCommitUrl = ''; + returnObj.lastCommit = {}; } + return returnObj; }, scrollTabsRight() { - // wait for the transition. 0.1 seconds. - setTimeout(() => { - const tabs = document.getElementById('tabs'); - if (!tabs) return; - tabs.scrollLeft = tabs.scrollWidth; - }, 200); + const tabs = document.getElementById('tabs'); + if (!tabs) return; + tabs.scrollLeft = tabs.scrollWidth; }, - dataToListOfFiles(data) { + dataToListOfFiles(data, level) { const { blobs, trees, submodules } = data; return [ - ...blobs.map(blob => RepoHelper.serializeBlob(blob)), - ...trees.map(tree => RepoHelper.serializeTree(tree)), - ...submodules.map(submodule => RepoHelper.serializeSubmodule(submodule)), + ...trees.map(tree => RepoHelper.serializeTree(tree, level)), + ...blobs.map(blob => RepoHelper.serializeBlob(blob, level)), + ...submodules.map(submodule => RepoHelper.serializeSubmodule(submodule, level)), ]; }, diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js index 1a09f411b22..f0a059d7ae3 100644 --- a/app/assets/javascripts/repo/index.js +++ b/app/assets/javascripts/repo/index.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import Vue from 'vue'; +import { convertPermissionToBoolean } from '../lib/utils/common_utils'; import Service from './services/repo_service'; import Store from './stores/repo_store'; import Repo from './components/repo.vue'; @@ -33,6 +34,7 @@ function setInitialStore(data) { Store.onTopOfBranch = data.onTopOfBranch; Store.newMrTemplateUrl = decodeURIComponent(data.newMrTemplateUrl); Store.customBranchURL = decodeURIComponent(data.blobUrl); + Store.isRoot = convertPermissionToBoolean(data.root); Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref'); Store.checkIsCommitable(); Store.setBranchHash(); diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js index f8d29af7ffe..2950e5702d6 100644 --- a/app/assets/javascripts/repo/stores/repo_store.js +++ b/app/assets/javascripts/repo/stores/repo_store.js @@ -8,7 +8,6 @@ const RepoStore = { canCommit: false, onTopOfBranch: false, editMode: false, - isTree: false, isRoot: false, prevURL: '', projectId: '', @@ -72,10 +71,6 @@ const RepoStore = { RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit; }, - addFilesToDirectory(inDirectory, currentList, newList) { - RepoStore.files = Helper.getNewMergedList(inDirectory, currentList, newList); - }, - toggleRawPreview() { RepoStore.activeFile.raw = !RepoStore.activeFile.raw; RepoStore.activeFileLabel = RepoStore.activeFile.raw ? 'Display rendered file' : 'Display source'; @@ -129,30 +124,6 @@ const RepoStore = { RepoStore.activeFileLabel = 'Display source'; }, - removeChildFilesOfTree(tree) { - let foundTree = false; - const treeToClose = tree; - let canStopSearching = false; - RepoStore.files = RepoStore.files.filter((file) => { - const isItTheTreeWeWant = file.url === treeToClose.url; - // if it's the next tree - if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) { - canStopSearching = true; - return true; - } - if (canStopSearching) return true; - - if (isItTheTreeWeWant) foundTree = true; - - if (foundTree) return file.level <= treeToClose.level; - return true; - }); - - treeToClose.opened = false; - treeToClose.icon = 'fa-folder'; - return treeToClose; - }, - removeFromOpenedFiles(file) { if (file.type === 'tree') return; let foundIndex; @@ -186,6 +157,7 @@ const RepoStore = { if (openedFilesAlreadyExists) return; openFile.changed = false; + openFile.active = true; RepoStore.openedFiles.push(openFile); }, diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index f0e6b23757f..374988bb590 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -198,6 +198,13 @@ a { height: 12px; } + &.animation-container-right { + .skeleton-line-2 { + left: 0; + right: 150px; + } + } + &::before { animation-duration: 1s; animation-fill-mode: forwards; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index c36fe25f74d..ea37ccf5e3d 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -153,28 +153,13 @@ overflow-x: auto; li { - animation: swipeRightAppear ease-in 0.1s; - animation-iteration-count: 1; - transform-origin: 0% 50%; - list-style-type: none; + position: relative; background: $gray-normal; - display: inline-block; padding: #{$gl-padding / 2} $gl-padding; border-right: 1px solid $white-dark; border-bottom: 1px solid $white-dark; - white-space: nowrap; cursor: pointer; - &.remove { - animation: swipeRightDissapear ease-in 0.1s; - animation-iteration-count: 1; - transform-origin: 0% 50%; - - a { - width: 0; - } - } - &.active { background: $white-light; border-bottom: none; @@ -182,17 +167,21 @@ a { @include str-truncated(100px); - color: $black; + color: $gl-text-color; vertical-align: middle; text-decoration: none; margin-right: 12px; + } - &.close { - width: auto; - font-size: 15px; - opacity: 1; - margin-right: -6px; - } + .close-btn { + position: absolute; + right: 8px; + top: 50%; + padding: 0; + background: none; + border: 0; + font-size: $gl-font-size; + transform: translateY(-50%); } .close-icon:hover { @@ -201,9 +190,6 @@ .close-icon, .unsaved-icon { - float: right; - margin-top: 3px; - margin-left: 15px; color: $gray-darkest; } @@ -222,9 +208,7 @@ #repo-file-buttons { background-color: $white-light; - border-bottom: 1px solid $white-normal; padding: 5px 10px; - position: relative; border-top: 1px solid $white-normal; } @@ -287,37 +271,23 @@ overflow: auto; } - table { + .table { margin-bottom: 0; } tr { - animation: fadein 0.5s; - cursor: pointer; - - &.repo-file-options td { - padding: 0; - border-top: none; - background: $gray-light; + .repo-file-options { + padding: 2px 16px; width: 100%; - display: inline-block; - - &:first-child { - border-top-left-radius: 2px; - } + } - .title { - display: inline-block; - font-size: 10px; - text-transform: uppercase; - font-weight: $gl-font-weight-bold; - color: $gray-darkest; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - vertical-align: middle; - padding: 2px 16px; - } + .title { + font-size: 10px; + text-transform: uppercase; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; } .file-icon { @@ -329,11 +299,13 @@ } } + .file { + cursor: pointer; + } + a { @include str-truncated(250px); color: $almost-black; - display: inline-block; - vertical-align: middle; } } } diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index f3719059f88..d3de3509988 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -36,6 +36,7 @@ class Projects::TreeController < Projects::ApplicationController format.json do page_title @path.presence || _("Files"), @ref, @project.name_with_namespace + response.header['Is-Root'] = @path.empty? # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261 Gitlab::GitalyClient.allow_n_plus_1_calls do diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml index 919f19f2c23..7185f5bcc5b 100644 --- a/app/views/shared/repo/_repo.html.haml +++ b/app/views/shared/repo/_repo.html.haml @@ -1,4 +1,5 @@ -#repo{ data: { url: content_url, +#repo{ data: { root: @path.empty?.to_s, + url: content_url, project_name: project.name, refs_url: refs_project_path(project, format: :json), project_url: project_path(project), diff --git a/spec/javascripts/repo/components/repo_file_options_spec.js b/spec/javascripts/repo/components/repo_file_options_spec.js deleted file mode 100644 index 9759b4bf12d..00000000000 --- a/spec/javascripts/repo/components/repo_file_options_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import Vue from 'vue'; -import repoFileOptions from '~/repo/components/repo_file_options.vue'; - -describe('RepoFileOptions', () => { - const projectName = 'projectName'; - - function createComponent(propsData) { - const RepoFileOptions = Vue.extend(repoFileOptions); - - return new RepoFileOptions({ - propsData, - }).$mount(); - } - - it('renders the title and new file/folder buttons if isMini is true', () => { - const vm = createComponent({ - isMini: true, - projectName, - }); - - expect(vm.$el.classList.contains('repo-file-options')).toBeTruthy(); - expect(vm.$el.querySelector('.title').textContent).toEqual(projectName); - }); - - it('does not render if isMini is false', () => { - const vm = createComponent({ - isMini: false, - projectName, - }); - - expect(vm.$el.innerHTML).toBeFalsy(); - }); -}); |