summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/repo/stores
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-10-26 11:14:31 +0100
committerPhil Hughes <me@iamphill.com>2017-10-26 11:14:31 +0100
commit05728e785ce7cd39c4c517aa0ea50b3bba44d537 (patch)
treefaa93529ccd3b8a7e6ac386192d9a442ea55af25 /app/assets/javascripts/repo/stores
parent95e56a617500d6ef94d7e857370cda5eadde614f (diff)
downloadgitlab-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.js94
-rw-r--r--app/assets/javascripts/repo/stores/actions/branch.js20
-rw-r--r--app/assets/javascripts/repo/stores/actions/file.js50
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js70
-rw-r--r--app/assets/javascripts/repo/stores/getters.js39
-rw-r--r--app/assets/javascripts/repo/stores/index.js15
-rw-r--r--app/assets/javascripts/repo/stores/mutation_types.js26
-rw-r--r--app/assets/javascripts/repo/stores/mutations.js54
-rw-r--r--app/assets/javascripts/repo/stores/mutations/branch.js9
-rw-r--r--app/assets/javascripts/repo/stores/mutations/file.js50
-rw-r--r--app/assets/javascripts/repo/stores/mutations/tree.js27
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js7
-rw-r--r--app/assets/javascripts/repo/stores/state.js21
-rw-r--r--app/assets/javascripts/repo/stores/utils.js73
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);
+};