diff options
author | Phil Hughes <me@iamphill.com> | 2017-10-26 11:14:31 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-10-26 11:14:31 +0100 |
commit | 05728e785ce7cd39c4c517aa0ea50b3bba44d537 (patch) | |
tree | faa93529ccd3b8a7e6ac386192d9a442ea55af25 /app/assets/javascripts/repo/stores | |
parent | 95e56a617500d6ef94d7e857370cda5eadde614f (diff) | |
download | gitlab-ce-05728e785ce7cd39c4c517aa0ea50b3bba44d537.tar.gz |
[WIP] Move multi-file editor store to Vuex
Diffstat (limited to 'app/assets/javascripts/repo/stores')
-rw-r--r-- | app/assets/javascripts/repo/stores/actions.js | 94 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/actions/branch.js | 20 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/actions/file.js | 50 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/actions/tree.js | 70 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/getters.js | 39 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/index.js | 15 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/mutation_types.js | 26 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/mutations.js | 54 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/mutations/branch.js | 9 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/mutations/file.js | 50 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/mutations/tree.js | 27 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/repo_store.js | 7 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/state.js | 21 | ||||
-rw-r--r-- | app/assets/javascripts/repo/stores/utils.js | 73 |
14 files changed, 548 insertions, 7 deletions
diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js new file mode 100644 index 00000000000..c0b5ce47398 --- /dev/null +++ b/app/assets/javascripts/repo/stores/actions.js @@ -0,0 +1,94 @@ +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); + +export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); + +export const closeDiscardPopup = ({ commit }) => commit(types.TOGGLE_DISCARD_POPUP, false); + +export const discardAllChanges = ({ commit, state }) => { + const changedFiles = getters.changedFiles(state); + + changedFiles.forEach(file => commit(types.DISCARD_FILE_CHANGES, file)); +}; + +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); + + if (changedFiles.length && !force) { + commit(types.TOGGLE_DISCARD_POPUP, true); + } else { + commit(types.TOGGLE_EDIT_MODE); + commit(types.TOGGLE_DISCARD_POPUP, false); + dispatch('toggleBlobView'); + dispatch('discardAllChanges'); + } +}; + +export const toggleBlobView = ({ commit, state }) => { + if (state.editMode) { + commit(types.SET_EDIT_MODE); + } else { + commit(types.SET_PREVIEW_MODE); + } +}; + +export const checkCommitStatus = ({ state }) => service.getBranchData( + state.project.id, + state.currentBranch, +) + .then((data) => { + const { id } = data.commit; + + if (state.currentRef !== id) { + return true; + } + + return false; + }) + .catch(() => flash('Error checking branch data. Please try again.')); + +export const commitChanges = ({ commit, state, dispatch }, { payload, newMr }) => + service.commit(state.project.id, payload) + .then((data) => { + if (!data.short_id) { + flash(data.message); + return; + } + + flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); + + if (newMr) { + redirectToUrl(`${state.endpoints.newMergeRequestUrl}${payload.branch}`); + } else { + // TODO: push a new state with the branch name + commit(types.SET_COMMIT_REF, data.id); + dispatch('discardAllChanges'); + dispatch('closeAllFiles'); + dispatch('toggleEditMode'); + } + }) + .catch(() => flash('Error committing changes. Please try again.')); + +export const popHistoryState = ({ state, dispatch }) => { + const treeList = getters.treeList(state); + const tree = treeList.find(file => file.url === state.previousUrl); + + if (!tree) return; + + if (tree.type === 'tree') { + dispatch('toggleTreeOpen', { endpoint: tree.url, tree }); + } +}; + +export * from './actions/tree'; +export * from './actions/file'; +export * from './actions/branch'; diff --git a/app/assets/javascripts/repo/stores/actions/branch.js b/app/assets/javascripts/repo/stores/actions/branch.js new file mode 100644 index 00000000000..b81a70dfd1e --- /dev/null +++ b/app/assets/javascripts/repo/stores/actions/branch.js @@ -0,0 +1,20 @@ +import service from '../../services'; +import * as types from '../mutation_types'; +import { pushState } from '../utils'; + +// eslint-disable-next-line import/prefer-default-export +export const createNewBranch = ({ rootState, commit }, branch) => service.createBranch( + rootState.project.id, + { + branch, + ref: rootState.currentBranch, + }, +).then(res => res.json()) +.then((data) => { + const branchName = data.name; + const url = location.href.replace(rootState.currentBranch, branchName); + + pushState(url); + + commit(types.SET_CURRENT_BRANCH, branchName); +}); diff --git a/app/assets/javascripts/repo/stores/actions/file.js b/app/assets/javascripts/repo/stores/actions/file.js new file mode 100644 index 00000000000..887eecf83f8 --- /dev/null +++ b/app/assets/javascripts/repo/stores/actions/file.js @@ -0,0 +1,50 @@ +import flash from '../../../flash'; +import service from '../../services'; +import * as types from '../mutation_types'; +import { activeFile } from '../getters'; + +export const closeFile = ({ commit }, file) => { + if (file.changed || file.tempFile) return; + + commit(types.TOGGLE_FILE_OPEN, file); + commit(types.SET_FILE_ACTIVE, { file, active: false }); +}; + +export const setFileActive = ({ commit, state }, file) => { + const currentActiveFile = activeFile(state); + + if (currentActiveFile) { + commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false }); + } + + commit(types.SET_FILE_ACTIVE, { file, active: true }); +}; + +export const getFileData = ({ commit, dispatch }, file) => { + commit(types.TOGGLE_LOADING, file); + + service.getFileData(file.url) + .then(res => res.json()) + .then((data) => { + commit(types.SET_FILE_DATA, { data, file }); + commit(types.SET_PREVIEW_MODE); + commit(types.TOGGLE_FILE_OPEN, file); + dispatch('setFileActive', file); + commit(types.TOGGLE_LOADING, file); + }) + .catch(() => { + commit(types.TOGGLE_LOADING, file); + flash('Error loading file data. Please try again.'); + }); +}; + +export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file.rawPath) + .then(res => res.text()) + .then((raw) => { + commit(types.SET_FILE_RAW_DATA, { file, raw }); + }) + .catch(() => flash('Error loading file content. Please try again.')); + +export const changeFileContent = ({ commit }, { file, content }) => { + commit(types.UPDATE_FILE_CONTENT, { file, content }); +}; diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js new file mode 100644 index 00000000000..8869bb8da2a --- /dev/null +++ b/app/assets/javascripts/repo/stores/actions/tree.js @@ -0,0 +1,70 @@ +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'; + +export const getTreeData = ( + { commit, state }, + { endpoint = state.endpoints.rootEndpoint, tree = state } = {}, +) => { + commit(types.TOGGLE_LOADING, tree); + + service.getTreeData(endpoint) + .then((res) => { + const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); + + setPageTitle(pageTitle); + + return res.json(); + }) + .then((data) => { + if (!state.isInitialRoot) { + commit(types.SET_ROOT, data.path === '/'); + } + + commit(types.SET_DIRECTORY_DATA, { data, tree }); + commit(types.SET_PARENT_TREE_URL, data.parent_tree_url); + commit(types.TOGGLE_LOADING, tree); + + pushState(endpoint); + }) + .catch(() => { + flash('Error loading tree data. Please try again.'); + commit(types.TOGGLE_LOADING, tree); + }); +}; + +export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => { + if (tree.opened) { + // send empty data to clear the tree + const data = { trees: [], blobs: [], submodules: [] }; + + pushState(tree.parentTreeUrl); + + commit(types.SET_PREVIOUS_URL, tree.parentTreeUrl); + commit(types.SET_DIRECTORY_DATA, { data, tree }); + } else { + commit(types.SET_PREVIOUS_URL, endpoint); + dispatch('getTreeData', { endpoint, tree }); + } + + commit(types.TOGGLE_TREE_OPEN, tree); +}; + +export const clickedTreeRow = ({ commit, dispatch }, row) => { + if (row.type === 'tree') { + dispatch('toggleTreeOpen', { + endpoint: row.url, + tree: row, + }); + } else if (row.type === 'submodule') { + commit(types.TOGGLE_LOADING, row); + + gl.utils.visitUrl(row.url); + } else if (row.type === 'blob' && row.opened) { + dispatch('setFileActive', row); + } else { + dispatch('getFileData', row); + } +}; diff --git a/app/assets/javascripts/repo/stores/getters.js b/app/assets/javascripts/repo/stores/getters.js new file mode 100644 index 00000000000..9fa593a82da --- /dev/null +++ b/app/assets/javascripts/repo/stores/getters.js @@ -0,0 +1,39 @@ +import _ from 'underscore'; + +export const treeList = (state) => { + const mapTree = arr => (!arr.tree.length ? [] : _.map(arr.tree, a => [a, mapTree(a)])); + + return _.chain(state.tree) + .map(arr => [arr, mapTree(arr)]) + .flatten() + .value(); +}; + +export const changedFiles = (state) => { + const files = state.openFiles; + + return files.filter(file => file.changed); +}; + +export const activeFile = (state) => { + const openedFiles = state.openFiles; + + return openedFiles.find(file => file.active); +}; + +export const activeFileExtension = (state) => { + const file = activeFile(state); + return file ? `.${file.path.split('.').pop()}` : ''; +}; + +export const isMini = state => !!state.openFiles.length; + +export const canEditFile = (state) => { + const currentActiveFile = activeFile(state); + const openedFiles = state.openFiles; + + return state.canCommit && + state.onTopOfBranch && + openedFiles.length && + (currentActiveFile && !currentActiveFile.render_error && !currentActiveFile.binary); +}; diff --git a/app/assets/javascripts/repo/stores/index.js b/app/assets/javascripts/repo/stores/index.js new file mode 100644 index 00000000000..f8565fb244c --- /dev/null +++ b/app/assets/javascripts/repo/stores/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import * as getters from './getters'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export default new Vuex.Store({ + state, + actions, + mutations, + getters, +}); diff --git a/app/assets/javascripts/repo/stores/mutation_types.js b/app/assets/javascripts/repo/stores/mutation_types.js new file mode 100644 index 00000000000..c6d0816080c --- /dev/null +++ b/app/assets/javascripts/repo/stores/mutation_types.js @@ -0,0 +1,26 @@ +export const SET_INITIAL_DATA = 'SET_INITIAL_DATA'; +export const TOGGLE_LOADING = 'TOGGLE_LOADING'; +export const SET_COMMIT_REF = 'SET_COMMIT_REF'; +export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL'; +export const SET_ROOT = 'SET_ROOT'; +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'; + +// File mutation types +export const SET_FILE_DATA = 'SET_FILE_DATA'; +export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN'; +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'; + +// Viewer mutation types +export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE'; +export const SET_EDIT_MODE = 'SET_EDIT_MODE'; +export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE'; +export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP'; + +export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH'; diff --git a/app/assets/javascripts/repo/stores/mutations.js b/app/assets/javascripts/repo/stores/mutations.js new file mode 100644 index 00000000000..2f9b038322b --- /dev/null +++ b/app/assets/javascripts/repo/stores/mutations.js @@ -0,0 +1,54 @@ +import * as types from './mutation_types'; +import fileMutations from './mutations/file'; +import treeMutations from './mutations/tree'; +import branchMutations from './mutations/branch'; + +export default { + [types.SET_INITIAL_DATA](state, data) { + Object.assign(state, data); + }, + [types.SET_PREVIEW_MODE](state) { + Object.assign(state, { + currentBlobView: 'repo-preview', + }); + }, + [types.SET_EDIT_MODE](state) { + Object.assign(state, { + currentBlobView: 'repo-editor', + }); + }, + [types.TOGGLE_LOADING](state, entry) { + Object.assign(entry, { + loading: !entry.loading, + }); + }, + [types.TOGGLE_EDIT_MODE](state) { + Object.assign(state, { + editMode: !state.editMode, + }); + }, + [types.TOGGLE_DISCARD_POPUP](state, discardPopupOpen) { + Object.assign(state, { + discardPopupOpen, + }); + }, + [types.SET_COMMIT_REF](state, ref) { + Object.assign(state, { + currentRef: ref, + }); + }, + [types.SET_ROOT](state, isRoot) { + Object.assign(state, { + isRoot, + isInitialRoot: isRoot, + }); + }, + [types.SET_PREVIOUS_URL](state, previousUrl) { + Object.assign(state, { + previousUrl, + }); + }, + ...fileMutations, + ...treeMutations, + ...branchMutations, +}; diff --git a/app/assets/javascripts/repo/stores/mutations/branch.js b/app/assets/javascripts/repo/stores/mutations/branch.js new file mode 100644 index 00000000000..d8229e8a620 --- /dev/null +++ b/app/assets/javascripts/repo/stores/mutations/branch.js @@ -0,0 +1,9 @@ +import * as types from '../mutation_types'; + +export default { + [types.SET_CURRENT_BRANCH](state, currentBranch) { + Object.assign(state, { + currentBranch, + }); + }, +}; diff --git a/app/assets/javascripts/repo/stores/mutations/file.js b/app/assets/javascripts/repo/stores/mutations/file.js new file mode 100644 index 00000000000..50d8a51d9b7 --- /dev/null +++ b/app/assets/javascripts/repo/stores/mutations/file.js @@ -0,0 +1,50 @@ +import * as types from '../mutation_types'; +import { findIndexOfFile } from '../utils'; + +export default { + [types.SET_FILE_ACTIVE](state, { file, active }) { + Object.assign(file, { + active, + }); + }, + [types.TOGGLE_FILE_OPEN](state, file) { + Object.assign(file, { + opened: !file.opened, + }); + + if (file.opened) { + state.openFiles.push(file); + } else { + state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1); + } + }, + [types.SET_FILE_DATA](state, { data, file }) { + Object.assign(file, { + blamePath: data.blame_path, + commitsPath: data.commits_path, + permalink: data.permalink, + rawPath: data.raw_path, + binary: data.binary, + html: data.html, + }); + }, + [types.SET_FILE_RAW_DATA](state, { file, raw }) { + Object.assign(file, { + raw, + }); + }, + [types.UPDATE_FILE_CONTENT](state, { file, content }) { + const changed = content !== file.raw; + + Object.assign(file, { + content, + changed, + }); + }, + [types.DISCARD_FILE_CHANGES](state, file) { + Object.assign(file, { + content: '', + changed: false, + }); + }, +}; diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/repo/stores/mutations/tree.js new file mode 100644 index 00000000000..700aff46827 --- /dev/null +++ b/app/assets/javascripts/repo/stores/mutations/tree.js @@ -0,0 +1,27 @@ +import * as types from '../mutation_types'; +import * as utils from '../utils'; + +export default { + [types.TOGGLE_TREE_OPEN](state, tree) { + Object.assign(tree, { + opened: !tree.opened, + }); + }, + [types.SET_DIRECTORY_DATA](state, { data, tree }) { + const level = tree.level !== undefined ? tree.level + 1 : 0; + const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl; + + 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)), + ], + }); + }, + [types.SET_PARENT_TREE_URL](state, url) { + Object.assign(state, { + parentTreeUrl: url, + }); + }, +}; diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js index 38df1e3e0d2..b68e2289319 100644 --- a/app/assets/javascripts/repo/stores/repo_store.js +++ b/app/assets/javascripts/repo/stores/repo_store.js @@ -7,15 +7,12 @@ const RepoStore = { canCommit: false, onTopOfBranch: false, editMode: false, - isRoot: null, - isInitialRoot: null, prevURL: '', projectId: '', projectName: '', projectUrl: '', branchUrl: '', blobRaw: '', - currentBlobView: 'repo-preview', openedFiles: [], submitCommitsLoading: false, dialog: { @@ -40,10 +37,6 @@ const RepoStore = { branchChanged: false, commitMessage: '', path: '', - loading: { - tree: false, - blob: false, - }, setBranchHash() { return Service.getBranch() diff --git a/app/assets/javascripts/repo/stores/state.js b/app/assets/javascripts/repo/stores/state.js new file mode 100644 index 00000000000..6cf7565c955 --- /dev/null +++ b/app/assets/javascripts/repo/stores/state.js @@ -0,0 +1,21 @@ +export default { + project: { + id: 0, + name: '', + }, + currentBranch: '', + endpoints: {}, + isRoot: false, + isInitialRoot: false, + currentRef: '', + canCommit: false, + onTopOfBranch: false, + editMode: false, + loading: false, + currentBlobView: '', + discardPopupOpen: false, + tree: [], + openFiles: [], + parentTreeUrl: '', + previousUrl: '', +}; diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/repo/stores/utils.js new file mode 100644 index 00000000000..ec733200552 --- /dev/null +++ b/app/assets/javascripts/repo/stores/utils.js @@ -0,0 +1,73 @@ +export const dataStructure = ({ + id: '', + type: '', + name: '', + url: '', + path: '', + level: 0, + tempFile: false, + icon: '', + tree: [], + loading: false, + opened: false, + active: false, + changed: false, + lastCommit: {}, + tree_url: '', + blamePath: '', + commitsPath: '', + permalink: '', + rawPath: '', + binary: false, + html: '', + raw: '', + content: '', + parentTreeUrl: '', +}); + +export const decorateData = (entity, type, parentTreeUrl = '', level = 0) => { + const { + id, + url, + name, + icon, + last_commit, + tree_url, + path, + tempFile, + active = false, + opened = false, + } = entity; + + return { + ...dataStructure, + id, + type, + name, + url, + tree_url, + path, + level, + tempFile, + icon: `fa-${icon}`, + opened, + active, + parentTreeUrl, + // eslint-disable-next-line camelcase + lastCommit: last_commit ? { + // url: `${Store.projectUrl}/commit/${last_commit.id}`, + message: last_commit.message, + updatedAt: last_commit.committed_date, + } : {}, + }; +}; + +export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path); + +export const setPageTitle = (title) => { + document.title = title; +}; + +export const pushState = (url) => { + history.pushState({ url }, '', url); +}; |