diff options
19 files changed, 176 insertions, 692 deletions
diff --git a/app/assets/javascripts/repo/components/new_dropdown/index.vue b/app/assets/javascripts/repo/components/new_dropdown/index.vue index 3ccb50213ab..96b2087e1b9 100644 --- a/app/assets/javascripts/repo/components/new_dropdown/index.vue +++ b/app/assets/javascripts/repo/components/new_dropdown/index.vue @@ -1,7 +1,5 @@ <script> - import RepoStore from '../../stores/repo_store'; - import RepoHelper from '../../helpers/repo_helper'; - import eventHub from '../../event_hub'; + import { mapState } from 'vuex'; import newModal from './modal.vue'; export default { @@ -12,9 +10,13 @@ return { openModal: false, modalType: '', - currentPath: RepoStore.path, }; }, + computed: { + ...mapState({ + currentPath: 'path', + }), + }, methods: { createNewItem(type) { this.modalType = type; @@ -23,17 +25,6 @@ toggleModalOpen() { this.openModal = !this.openModal; }, - createNewEntryInStore(name, type) { - RepoHelper.createNewEntry(name, type); - - this.toggleModalOpen(); - }, - }, - created() { - eventHub.$on('createNewEntry', this.createNewEntryInStore); - }, - beforeDestroy() { - eventHub.$off('createNewEntry', this.createNewEntryInStore); }, }; </script> diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/repo/components/new_dropdown/modal.vue index 5ef629e0dde..e3496f26b8b 100644 --- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/repo/components/new_dropdown/modal.vue @@ -1,7 +1,7 @@ <script> + import { mapActions } from 'vuex'; import { __ } from '../../../locale'; import popupDialog from '../../../vue_shared/components/popup_dialog.vue'; - import eventHub from '../../event_hub'; export default { props: { @@ -23,8 +23,16 @@ popupDialog, }, methods: { + ...mapActions([ + 'createTempEntry', + ]), createEntryInStore() { - eventHub.$emit('createNewEntry', this.entryName, this.type); + this.createTempEntry({ + name: this.entryName, + type: this.type, + }); + + this.toggleModalOpen(); }, toggleModalOpen() { this.$emit('toggle'); diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue index 0bc5271f95c..e072af5c151 100644 --- a/app/assets/javascripts/repo/components/repo.vue +++ b/app/assets/javascripts/repo/components/repo.vue @@ -5,7 +5,7 @@ import RepoCommitSection from './repo_commit_section.vue'; import RepoTabs from './repo_tabs.vue'; import RepoFileButtons from './repo_file_buttons.vue'; import RepoPreview from './repo_preview.vue'; -import MonacoLoaderHelper from '../helpers/monaco_loader_helper'; +import repoEditor from './repo_editor.vue'; export default { computed: { @@ -22,7 +22,7 @@ export default { RepoSidebar, RepoTabs, RepoFileButtons, - 'repo-editor': MonacoLoaderHelper.repoEditorLoader, + repoEditor, RepoCommitSection, RepoPreview, }, diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue index 06ded6332e1..0714ba90b6f 100644 --- a/app/assets/javascripts/repo/components/repo_editor.vue +++ b/app/assets/javascripts/repo/components/repo_editor.vue @@ -1,8 +1,8 @@ <script> /* global monaco */ import { mapGetters, mapActions } from 'vuex'; -import Helper from '../helpers/repo_helper'; import flash from '../../flash'; +import monacoLoader from '../monaco_loader'; export default { destroyed() { @@ -11,7 +11,15 @@ export default { } }, mounted() { - this.initMonaco(); + if (this.monaco) { + this.initMonaco(); + } else { + monacoLoader(['vs/editor/editor.main'], () => { + this.monaco = monaco; + + this.initMonaco(); + }); + } }, methods: { ...mapActions([ @@ -26,14 +34,14 @@ export default { this.getRawFileData(this.activeFile) .then(() => { if (!this.monacoInstance) { - this.monacoInstance = Helper.monaco.editor.create(this.$el, { + this.monacoInstance = this.monaco.editor.create(this.$el, { model: null, readOnly: false, contextmenu: true, scrollBeyondLastLine: false, }); - this.languages = Helper.monaco.languages.getLanguages(); + this.languages = this.monaco.languages.getLanguages(); this.addMonacoEvents(); } @@ -46,7 +54,7 @@ export default { const foundLang = this.languages.find(lang => lang.extensions && lang.extensions.indexOf(this.activeFileExtension) === 0, ); - const newModel = Helper.monaco.editor.createModel( + const newModel = this.monaco.editor.createModel( this.activeFile.raw, foundLang ? foundLang.id : 'plaintext', ); diff --git a/app/assets/javascripts/repo/event_hub.js b/app/assets/javascripts/repo/event_hub.js deleted file mode 100644 index 0948c2e5352..00000000000 --- a/app/assets/javascripts/repo/event_hub.js +++ /dev/null @@ -1,3 +0,0 @@ -import Vue from 'vue'; - -export default new Vue(); diff --git a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js deleted file mode 100644 index 4d5ff92fbea..00000000000 --- a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global monaco */ -import RepoEditor from '../components/repo_editor.vue'; -import Helper from '../helpers/repo_helper'; -import monacoLoader from '../monaco_loader'; - -function repoEditorLoader() { - return new Promise((resolve, reject) => { - monacoLoader(['vs/editor/editor.main'], () => { - Helper.monaco = monaco; - resolve(RepoEditor); - }, () => { - reject(); - }); - }); -} - -const MonacoLoaderHelper = { - repoEditorLoader, -}; - -export default MonacoLoaderHelper; diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js deleted file mode 100644 index fb26f3b7380..00000000000 --- a/app/assets/javascripts/repo/helpers/repo_helper.js +++ /dev/null @@ -1,317 +0,0 @@ -import Service from '../services/repo_service'; -import Store from '../stores/repo_store'; -import Flash from '../../flash'; - -const RepoHelper = { - monacoInstance: null, - - getDefaultActiveFile() { - return { - id: '', - active: true, - binary: false, - extension: '', - html: '', - mime_type: '', - name: '', - plain: '', - size: 0, - url: '', - raw: false, - newContent: '', - changed: false, - loading: false, - }; - }, - - key: '', - - Time: window.performance - && window.performance.now - ? window.performance - : Date, - - getFileExtension(fileName) { - return fileName.split('.').pop(); - }, - - getLanguageIDForFile(file, langs) { - const ext = RepoHelper.getFileExtension(file.name); - const foundLang = RepoHelper.findLanguage(ext, langs); - - return foundLang ? foundLang.id : 'plaintext'; - }, - - setMonacoModelFromLanguage() { - RepoHelper.monacoInstance.setModel(null); - const languages = RepoHelper.monaco.languages.getLanguages(); - const languageID = RepoHelper.getLanguageIDForFile(Store.activeFile, languages); - const newModel = RepoHelper.monaco.editor.createModel(Store.blobRaw, languageID); - RepoHelper.monacoInstance.setModel(newModel); - }, - - findLanguage(ext, langs) { - return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1); - }, - - setDirectoryOpen(tree, title) { - if (!tree) return; - - Object.assign(tree, { - opened: true, - }); - - RepoHelper.updateHistoryEntry(tree.url, title); - Store.path = tree.path; - }, - - setDirectoryToClosed(entry) { - Object.assign(entry, { - opened: false, - files: [], - }); - }, - - isRenderable() { - const okExts = ['md', 'svg']; - return okExts.indexOf(Store.activeFile.extension) > -1; - }, - - setBinaryDataAsBase64(file) { - Service.getBase64Content(file.raw_path) - .then((response) => { - Store.blobRaw = response; - file.base64 = response; // eslint-disable-line no-param-reassign - }) - .catch(RepoHelper.loadingError); - }, - - getContent(treeOrFile, emptyFiles = false) { - 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 = decodeURI(response.headers['page-title']); - if (data.path && !Store.isInitialRoot) { - Store.isRoot = data.path === '/'; - Store.isInitialRoot = Store.isRoot; - } - - if (file && file.type === 'blob') { - if (!file) file = data; - Store.binary = data.binary; - - if (data.binary) { - // file might be undefined - RepoHelper.setBinaryDataAsBase64(data); - Store.setViewToPreview(); - } else if (!Store.isPreviewView() && !data.render_error) { - Service.getRaw(data) - .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 (emptyFiles) { - Store.files = []; - } - - this.addToDirectory(file, data); - - Store.prevURL = Service.blobURLtoParentTree(Service.url); - } - }).catch(RepoHelper.loadingError); - }, - - addToDirectory(file, data) { - const tree = file || Store; - - // TODO: Figure out why `popstate` is being trigger in the specs - if (!tree.files) return; - - const files = tree.files.concat(this.dataToListOfFiles(data, file ? file.level + 1 : 0)); - - tree.files = files; - }, - - setFile(data, file) { - const newFile = data; - newFile.url = file.url || Service.url; // Grab the URL from service, happens on page refresh. - - if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') { - newFile.tooLarge = true; - } - newFile.newContent = ''; - - Store.addToOpenedFiles(newFile); - Store.setActiveFiles(newFile); - }, - - serializeRepoEntity(type, entity, level = 0) { - const { - id, - url, - name, - icon, - last_commit, - tree_url, - path, - tempFile, - active, - opened, - } = entity; - - return { - id, - type, - name, - url, - tree_url, - path, - level, - tempFile, - icon: `fa-${icon}`, - files: [], - loading: false, - opened, - active, - // eslint-disable-next-line camelcase - lastCommit: last_commit ? { - url: `${Store.projectUrl}/commit/${last_commit.id}`, - message: last_commit.message, - updatedAt: last_commit.committed_date, - } : {}, - }; - }, - - scrollTabsRight() { - const tabs = document.getElementById('tabs'); - if (!tabs) return; - tabs.scrollLeft = tabs.scrollWidth; - }, - - dataToListOfFiles(data, level) { - const { blobs, trees, submodules } = data; - return [ - ...trees.map(tree => RepoHelper.serializeRepoEntity('tree', tree, level)), - ...submodules.map(submodule => RepoHelper.serializeRepoEntity('submodule', submodule, level)), - ...blobs.map(blob => RepoHelper.serializeRepoEntity('blob', blob, level)), - ]; - }, - - genKey() { - return RepoHelper.Time.now().toFixed(3); - }, - - updateHistoryEntry(url, title) { - const history = window.history; - - RepoHelper.key = RepoHelper.genKey(); - - if (document.location.pathname !== url) { - history.pushState({ key: RepoHelper.key }, '', url); - } - - if (title) { - document.title = title; - } - }, - - findOpenedFileFromActive() { - return Store.openedFiles.find(openedFile => Store.activeFile.id === openedFile.id); - }, - - getFileFromPath(path) { - return Store.openedFiles.find(file => file.url === path); - }, - - loadingError() { - Flash('Unable to load this content at this time.'); - }, - openEditMode() { - Store.editMode = true; - Store.currentBlobView = 'repo-editor'; - }, - updateStorePath(path) { - Store.path = path; - }, - findOrCreateEntry(type, tree, name) { - let exists = true; - let foundEntry = tree.files.find(dir => dir.type === type && dir.name === name); - - if (!foundEntry) { - foundEntry = RepoHelper.serializeRepoEntity(type, { - id: name, - name, - path: tree.path ? `${tree.path}/${name}` : name, - icon: type === 'tree' ? 'folder' : 'file-text-o', - tempFile: true, - opened: true, - active: true, - }, tree.level !== undefined ? tree.level + 1 : 0); - - exists = false; - tree.files.push(foundEntry); - } - - return { - entry: foundEntry, - exists, - }; - }, - removeAllTmpFiles(storeFilesKey) { - Store[storeFilesKey] = Store[storeFilesKey].filter(f => !f.tempFile); - }, - createNewEntry(name, type) { - const originalPath = Store.path; - let entryName = name; - - if (entryName.indexOf(`${originalPath}/`) !== 0) { - this.updateStorePath(''); - } else { - entryName = entryName.replace(`${originalPath}/`, ''); - } - - if (entryName === '') return; - - const fileName = type === 'tree' ? '.gitkeep' : entryName; - let tree = Store; - - if (type === 'tree') { - const dirNames = entryName.split('/'); - - dirNames.forEach((dirName) => { - if (dirName === '') return; - - tree = this.findOrCreateEntry('tree', tree, dirName).entry; - }); - } - - if ((type === 'tree' && tree.tempFile) || type === 'blob') { - const file = this.findOrCreateEntry('blob', tree, fileName); - - if (!file.exists) { - this.setFile(file.entry, file.entry); - this.openEditMode(); - } - } - - this.updateStorePath(originalPath); - }, -}; - -export default RepoHelper; diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js index 475d53e5765..8ca5bb45d05 100644 --- a/app/assets/javascripts/repo/index.js +++ b/app/assets/javascripts/repo/index.js @@ -1,8 +1,6 @@ import Vue from 'vue'; import { mapActions } from 'vuex'; 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'; import RepoEditButton from './components/repo_edit_button.vue'; import newBranchForm from './components/new_branch_form.vue'; @@ -10,24 +8,6 @@ import newDropdown from './components/new_dropdown/index.vue'; import vStore from './stores'; import Translate from '../vue_shared/translate'; -function setInitialStore(data) { - Store.service = Service; - Store.service.refsUrl = data.refsUrl; - Store.path = data.currentPath; - Store.projectId = data.projectId; - Store.projectName = data.projectName; - Store.projectUrl = data.projectUrl; - Store.canCommit = data.canCommit; - Store.onTopOfBranch = data.onTopOfBranch; - Store.newMrTemplateUrl = decodeURIComponent(data.newMrTemplateUrl); - Store.customBranchURL = decodeURIComponent(data.blobUrl); - Store.isRoot = convertPermissionToBoolean(data.root); - Store.isInitialRoot = convertPermissionToBoolean(data.root); - Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref'); - Store.checkIsCommitable(); - Store.setBranchHash(); -} - function initRepo(el) { return new Vue({ el, @@ -47,6 +27,7 @@ function initRepo(el) { project: { id: data.projectId, name: data.projectName, + url: data.projectUrl, }, endpoints: { rootEndpoint: data.url, @@ -56,6 +37,7 @@ function initRepo(el) { canCommit: convertPermissionToBoolean(data.canCommit), onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch), currentRef: data.ref, + path: data.currentPath, // TODO: get through data attribute currentBranch: document.querySelector('.js-project-refs-dropdown').dataset.ref, isRoot: convertPermissionToBoolean(data.root), @@ -81,6 +63,7 @@ function initRepoEditButton(el) { function initNewDropdown(el) { return new Vue({ el, + store: vStore, components: { newDropdown, }, @@ -110,7 +93,6 @@ function initNewBranchForm() { const repo = document.getElementById('repo'); const editButton = document.querySelector('.editable-mode'); const newDropdownHolder = document.querySelector('.js-new-dropdown'); -setInitialStore(repo.dataset); Vue.use(Translate); diff --git a/app/assets/javascripts/repo/services/index.js b/app/assets/javascripts/repo/services/index.js index 57c234cd4c9..49301fb47d5 100644 --- a/app/assets/javascripts/repo/services/index.js +++ b/app/assets/javascripts/repo/services/index.js @@ -11,8 +11,13 @@ export default { getFileData(endpoint) { return Vue.http.get(endpoint, { params: { format: 'json' } }); }, - getRawFileData(endpoint) { - return Vue.http.get(endpoint); + getRawFileData(file) { + if (file.tempFile) { + return Promise.resolve(''); + } + + return Vue.http.get(file.rawPath, { params: { format: 'json' } }) + .then(res => res.text()); }, getBranchData(projectId, currentBranch) { return Api.branchSingle(projectId, currentBranch); diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js deleted file mode 100644 index c9fa5cc8bf8..00000000000 --- a/app/assets/javascripts/repo/services/repo_service.js +++ /dev/null @@ -1,101 +0,0 @@ -import axios from 'axios'; -import csrf from '../../lib/utils/csrf'; -import Store from '../stores/repo_store'; -import Api from '../../api'; -import Helper from '../helpers/repo_helper'; - -axios.defaults.headers.common[csrf.headerKey] = csrf.token; - -const RepoService = { - url: '', - options: { - params: { - format: 'json', - }, - }, - createBranchPath: '/api/:version/projects/:id/repository/branches', - richExtensionRegExp: /md/, - - getRaw(file) { - if (file.tempFile) { - return Promise.resolve({ - data: '', - }); - } - - return axios.get(file.raw_path, { - // Stop Axios from parsing a JSON file into a JS object - transformResponse: [res => res], - }); - }, - - buildParams(url = this.url) { - // shallow clone object without reference - const params = Object.assign({}, this.options.params); - - if (this.urlIsRichBlob(url)) params.viewer = 'rich'; - - return params; - }, - - urlIsRichBlob(url = this.url) { - const extension = Helper.getFileExtension(url); - - return this.richExtensionRegExp.test(extension); - }, - - getContent(url = this.url) { - const params = this.buildParams(url); - - return axios.get(url, { - params, - }); - }, - - getBase64Content(url = this.url) { - const request = axios.get(url, { - responseType: 'arraybuffer', - }); - - return request.then(response => this.bufferToBase64(response.data)); - }, - - bufferToBase64(data) { - return new Buffer(data, 'binary').toString('base64'); - }, - - blobURLtoParentTree(url) { - const urlArray = url.split('/'); - urlArray.pop(); - const blobIndex = urlArray.lastIndexOf('blob'); - - if (blobIndex > -1) urlArray[blobIndex] = 'tree'; - - return urlArray.join('/'); - }, - - getBranch() { - return Api.branchSingle(Store.projectId, Store.currentBranch); - }, - - commitFiles(payload) { - return Api.commitMultiple(Store.projectId, payload) - .then(this.commitFlash); - }, - - createBranch(payload) { - const url = Api.buildUrl(this.createBranchPath) - .replace(':id', Store.projectId); - return axios.post(url, payload); - }, - - commitFlash(data) { - if (data.short_id && data.stats) { - window.Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); - } else { - window.Flash(data.message); - } - }, -}; - -export default RepoService; diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js index c0b5ce47398..20aee8707b4 100644 --- a/app/assets/javascripts/repo/stores/actions.js +++ b/app/assets/javascripts/repo/stores/actions.js @@ -1,7 +1,6 @@ import flash from '../../flash'; import service from '../services'; import * as types from './mutation_types'; -import * as getters from './getters'; import { visitUrl } from '../../lib/utils/url_utility'; export const redirectToUrl = url => visitUrl(url); @@ -10,8 +9,8 @@ export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DAT export const closeDiscardPopup = ({ commit }) => commit(types.TOGGLE_DISCARD_POPUP, false); -export const discardAllChanges = ({ commit, state }) => { - const changedFiles = getters.changedFiles(state); +export const discardAllChanges = ({ commit, getters }) => { + const changedFiles = getters.changedFiles; changedFiles.forEach(file => commit(types.DISCARD_FILE_CHANGES, file)); }; @@ -20,8 +19,8 @@ export const closeAllFiles = ({ state, dispatch }) => { state.openFiles.forEach(file => dispatch('closeFile', file)); }; -export const toggleEditMode = ({ commit, state, dispatch }, force = false) => { - const changedFiles = getters.changedFiles(state); +export const toggleEditMode = ({ commit, getters, dispatch }, force = false) => { + const changedFiles = getters.changedFiles; if (changedFiles.length && !force) { commit(types.TOGGLE_DISCARD_POPUP, true); @@ -78,8 +77,19 @@ export const commitChanges = ({ commit, state, dispatch }, { payload, newMr }) = }) .catch(() => flash('Error committing changes. Please try again.')); -export const popHistoryState = ({ state, dispatch }) => { - const treeList = getters.treeList(state); +export const createTempEntry = ({ state, dispatch }, { name, type }) => { + if (type === 'tree') { + dispatch('createTempTree', name); + } else if (type === 'blob') { + dispatch('createTempFile', { + tree: state, + name, + }); + } +}; + +export const popHistoryState = ({ state, dispatch, getters }) => { + const treeList = getters.treeList; const tree = treeList.find(file => file.url === state.previousUrl); if (!tree) return; diff --git a/app/assets/javascripts/repo/stores/actions/file.js b/app/assets/javascripts/repo/stores/actions/file.js index 887eecf83f8..60964f90d34 100644 --- a/app/assets/javascripts/repo/stores/actions/file.js +++ b/app/assets/javascripts/repo/stores/actions/file.js @@ -1,7 +1,7 @@ import flash from '../../../flash'; import service from '../../services'; import * as types from '../mutation_types'; -import { activeFile } from '../getters'; +import { createTemp } from '../utils'; export const closeFile = ({ commit }, file) => { if (file.changed || file.tempFile) return; @@ -10,8 +10,8 @@ export const closeFile = ({ commit }, file) => { commit(types.SET_FILE_ACTIVE, { file, active: false }); }; -export const setFileActive = ({ commit, state }, file) => { - const currentActiveFile = activeFile(state); +export const setFileActive = ({ commit, state, getters }, file) => { + const currentActiveFile = getters.activeFile; if (currentActiveFile) { commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false }); @@ -38,8 +38,7 @@ export const getFileData = ({ commit, dispatch }, file) => { }); }; -export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file.rawPath) - .then(res => res.text()) +export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file) .then((raw) => { commit(types.SET_FILE_RAW_DATA, { file, raw }); }) @@ -48,3 +47,21 @@ export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFile export const changeFileContent = ({ commit }, { file, content }) => { commit(types.UPDATE_FILE_CONTENT, { file, content }); }; + +export const createTempFile = ({ state, commit, dispatch }, { tree, name }) => { + const file = createTemp({ + name: name.replace(`${state.path}/`, ''), + path: tree.path, + type: 'blob', + level: tree.level !== undefined ? tree.level + 1 : 0, + changed: true, + }); + + commit(types.CREATE_TMP_FILE, { + parent: tree, + file, + }); + commit(types.TOGGLE_FILE_OPEN, file); + dispatch('setFileActive', file); + dispatch('toggleEditMode', true); +}; diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js index 8869bb8da2a..a0be8d4c556 100644 --- a/app/assets/javascripts/repo/stores/actions/tree.js +++ b/app/assets/javascripts/repo/stores/actions/tree.js @@ -2,7 +2,12 @@ import { normalizeHeaders } from '../../../lib/utils/common_utils'; import flash from '../../../flash'; import service from '../../services'; import * as types from '../mutation_types'; -import { pushState, setPageTitle } from '../utils'; +import { + pushState, + setPageTitle, + findEntry, + createTemp, +} from '../utils'; export const getTreeData = ( { commit, state }, @@ -68,3 +73,35 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => { dispatch('getFileData', row); } }; + +export const createTempTree = ({ state, commit, dispatch }, name) => { + let tree = state; + const dirNames = name.replace(`${state.path}/`, '').split('/'); + + dirNames.forEach((dirName) => { + const foundEntry = findEntry(tree, 'tree', dirName); + + if (!foundEntry) { + const tmpEntry = createTemp({ + name: dirName, + path: tree.path, + type: 'tree', + level: tree.level !== undefined ? tree.level + 1 : 0, + }); + + commit(types.CREATE_TMP_TREE, { + parent: tree, + tmpEntry, + }); + + tree = tmpEntry; + } else { + tree = foundEntry; + } + }); + + dispatch('createTempFile', { + tree, + name: '.gitkeep', + }); +}; diff --git a/app/assets/javascripts/repo/stores/mutation_types.js b/app/assets/javascripts/repo/stores/mutation_types.js index c6d0816080c..4722a7dd0df 100644 --- a/app/assets/javascripts/repo/stores/mutation_types.js +++ b/app/assets/javascripts/repo/stores/mutation_types.js @@ -8,6 +8,7 @@ export const SET_PREVIOUS_URL = 'SET_PREVIOUS_URL'; // Tree mutation types export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA'; export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN'; +export const CREATE_TMP_TREE = 'CREATE_TMP_TREE'; // File mutation types export const SET_FILE_DATA = 'SET_FILE_DATA'; @@ -16,6 +17,7 @@ export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE'; export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA'; export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT'; export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES'; +export const CREATE_TMP_FILE = 'CREATE_TMP_FILE'; // Viewer mutation types export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE'; diff --git a/app/assets/javascripts/repo/stores/mutations/file.js b/app/assets/javascripts/repo/stores/mutations/file.js index 50d8a51d9b7..4d10fa8169d 100644 --- a/app/assets/javascripts/repo/stores/mutations/file.js +++ b/app/assets/javascripts/repo/stores/mutations/file.js @@ -47,4 +47,7 @@ export default { changed: false, }); }, + [types.CREATE_TMP_FILE](state, { file, parent }) { + parent.tree.push(file); + }, }; diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/repo/stores/mutations/tree.js index 700aff46827..52be2673107 100644 --- a/app/assets/javascripts/repo/stores/mutations/tree.js +++ b/app/assets/javascripts/repo/stores/mutations/tree.js @@ -13,9 +13,24 @@ export default { Object.assign(tree, { tree: [ - ...data.trees.map(t => utils.decorateData(t, 'tree', parentTreeUrl, level)), - ...data.submodules.map(m => utils.decorateData(m, 'submodule', parentTreeUrl, level)), - ...data.blobs.map(b => utils.decorateData(b, 'blob', parentTreeUrl, level)), + ...data.trees.map(t => utils.decorateData({ + ...t, + type: 'tree', + parentTreeUrl, + level, + }, state.project.url)), + ...data.submodules.map(m => utils.decorateData({ + ...m, + type: 'submodule', + parentTreeUrl, + level, + }, state.project.url)), + ...data.blobs.map(b => utils.decorateData({ + ...b, + type: 'blob', + parentTreeUrl, + level, + }, state.project.url)), ], }); }, @@ -24,4 +39,7 @@ export default { parentTreeUrl: url, }); }, + [types.CREATE_TMP_TREE](state, { parent, tmpEntry }) { + parent.tree.push(tmpEntry); + }, }; diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js deleted file mode 100644 index b68e2289319..00000000000 --- a/app/assets/javascripts/repo/stores/repo_store.js +++ /dev/null @@ -1,182 +0,0 @@ -import Helper from '../helpers/repo_helper'; -import Service from '../services/repo_service'; - -const RepoStore = { - monacoLoading: false, - service: '', - canCommit: false, - onTopOfBranch: false, - editMode: false, - prevURL: '', - projectId: '', - projectName: '', - projectUrl: '', - branchUrl: '', - blobRaw: '', - openedFiles: [], - submitCommitsLoading: false, - dialog: { - open: false, - title: '', - status: false, - }, - showNewBranchDialog: false, - activeFile: Helper.getDefaultActiveFile(), - activeFileIndex: 0, - activeLine: -1, - activeFileLabel: 'Raw', - files: [], - isCommitable: false, - binary: false, - currentBranch: '', - startNewMR: false, - currentHash: '', - currentShortHash: '', - customBranchURL: '', - newMrTemplateUrl: '', - branchChanged: false, - commitMessage: '', - path: '', - - setBranchHash() { - return Service.getBranch() - .then((data) => { - if (RepoStore.currentHash !== '' && data.commit.id !== RepoStore.currentHash) { - RepoStore.branchChanged = true; - } - RepoStore.currentHash = data.commit.id; - RepoStore.currentShortHash = data.commit.short_id; - }); - }, - - // mutations - checkIsCommitable() { - RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit; - }, - - toggleRawPreview() { - RepoStore.activeFile.raw = !RepoStore.activeFile.raw; - RepoStore.activeFileLabel = RepoStore.activeFile.raw ? 'Display rendered file' : 'Display source'; - }, - - setActiveFiles(file) { - if (RepoStore.isActiveFile(file)) return; - RepoStore.openedFiles = RepoStore.openedFiles - .map((openedFile, i) => RepoStore.setFileActivity(file, openedFile, i)); - - RepoStore.setActiveToRaw(); - - if (file.binary) { - RepoStore.blobRaw = file.base64; - } else if (file.newContent || file.plain) { - RepoStore.blobRaw = file.newContent || file.plain; - } else { - Service.getRaw(file) - .then((rawResponse) => { - RepoStore.blobRaw = rawResponse.data; - Helper.findOpenedFileFromActive().plain = rawResponse.data; - }).catch(Helper.loadingError); - } - - if (!file.loading && !file.tempFile) { - Helper.updateHistoryEntry(file.url, file.pageTitle || file.name); - } - RepoStore.binary = file.binary; - RepoStore.setActiveLine(-1); - }, - - setFileActivity(file, openedFile, i) { - const activeFile = openedFile; - activeFile.active = file.id === activeFile.id; - - if (activeFile.active) RepoStore.setActiveFile(activeFile, i); - - return activeFile; - }, - - setActiveFile(activeFile, i) { - RepoStore.activeFile = Object.assign({}, Helper.getDefaultActiveFile(), activeFile); - RepoStore.activeFileIndex = i; - }, - - setActiveLine(activeLine) { - if (!isNaN(activeLine)) RepoStore.activeLine = activeLine; - }, - - setActiveToRaw() { - RepoStore.activeFile.raw = false; - // can't get vue to listen to raw for some reason so RepoStore for now. - RepoStore.activeFileLabel = 'Display source'; - }, - - removeFromOpenedFiles(file) { - if (file.type === 'tree') return; - let foundIndex; - RepoStore.openedFiles = RepoStore.openedFiles.filter((openedFile, i) => { - if (openedFile.path === file.path) foundIndex = i; - return openedFile.path !== file.path; - }); - - // remove the file from the sidebar if it is a tempFile - if (file.tempFile) { - RepoStore.files = RepoStore.files.filter(f => !(f.tempFile && f.path === file.path)); - } - - // now activate the right tab based on what you closed. - if (RepoStore.openedFiles.length === 0) { - RepoStore.activeFile = {}; - return; - } - - if (RepoStore.openedFiles.length === 1 || foundIndex === 0) { - RepoStore.setActiveFiles(RepoStore.openedFiles[0]); - return; - } - - if (foundIndex && foundIndex > 0) { - RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]); - } - }, - - addToOpenedFiles(file) { - const openFile = file; - - const openedFilesAlreadyExists = RepoStore.openedFiles - .some(openedFile => openedFile.path === openFile.path); - - if (openedFilesAlreadyExists) return; - - openFile.changed = false; - openFile.active = true; - RepoStore.openedFiles.push(openFile); - }, - - setActiveFileContents(contents) { - if (!RepoStore.editMode) return; - const currentFile = RepoStore.openedFiles[RepoStore.activeFileIndex]; - RepoStore.activeFile.newContent = contents; - RepoStore.activeFile.changed = RepoStore.activeFile.plain !== RepoStore.activeFile.newContent; - currentFile.changed = RepoStore.activeFile.changed; - currentFile.newContent = contents; - }, - - toggleBlobView() { - RepoStore.currentBlobView = RepoStore.isPreviewView() ? 'repo-editor' : 'repo-preview'; - }, - - setViewToPreview() { - RepoStore.currentBlobView = 'repo-preview'; - }, - - // getters - - isActiveFile(file) { - return file && file.id === RepoStore.activeFile.id; - }, - - isPreviewView() { - return RepoStore.currentBlobView === 'repo-preview'; - }, -}; - -export default RepoStore; diff --git a/app/assets/javascripts/repo/stores/state.js b/app/assets/javascripts/repo/stores/state.js index 6cf7565c955..0166490aac0 100644 --- a/app/assets/javascripts/repo/stores/state.js +++ b/app/assets/javascripts/repo/stores/state.js @@ -2,12 +2,14 @@ export default { project: { id: 0, name: '', + url: '', }, - currentBranch: '', endpoints: {}, isRoot: false, isInitialRoot: false, + currentBranch: '', currentRef: '', + path: '', canCommit: false, onTopOfBranch: false, editMode: false, diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/repo/stores/utils.js index ec733200552..81638e46672 100644 --- a/app/assets/javascripts/repo/stores/utils.js +++ b/app/assets/javascripts/repo/stores/utils.js @@ -1,4 +1,4 @@ -export const dataStructure = ({ +export const dataStructure = () => ({ id: '', type: '', name: '', @@ -25,9 +25,10 @@ export const dataStructure = ({ parentTreeUrl: '', }); -export const decorateData = (entity, type, parentTreeUrl = '', level = 0) => { +export const decorateData = (entity, projectUrl = '') => { const { id, + type, url, name, icon, @@ -37,10 +38,13 @@ export const decorateData = (entity, type, parentTreeUrl = '', level = 0) => { tempFile, active = false, opened = false, + changed = false, + parentTreeUrl = '', + level = 0, } = entity; return { - ...dataStructure, + ...dataStructure(), id, type, name, @@ -53,15 +57,19 @@ export const decorateData = (entity, type, parentTreeUrl = '', level = 0) => { opened, active, parentTreeUrl, + changed, // eslint-disable-next-line camelcase lastCommit: last_commit ? { - // url: `${Store.projectUrl}/commit/${last_commit.id}`, + url: `${projectUrl}/commit/${last_commit.id}`, message: last_commit.message, updatedAt: last_commit.committed_date, } : {}, }; }; +export const findEntry = (state, type, name) => state.tree.find( + f => f.type === type && f.name === name, +); export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path); export const setPageTitle = (title) => { @@ -71,3 +79,20 @@ export const setPageTitle = (title) => { export const pushState = (url) => { history.pushState({ url }, '', url); }; + +export const createTemp = ({ name, path, type, level, changed, content }) => { + const treePath = path ? `${path}/${name}` : name; + + return decorateData({ + id: new Date().getTime().toString(), + name, + type, + tempFile: true, + path: treePath, + icon: type === 'tree' ? 'folder' : 'file-text-o', + changed, + content, + parentTreeUrl: '', + level, + }); +}; |