summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ide/stores
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/assets/javascripts/ide/stores
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
downloadgitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/assets/javascripts/ide/stores')
-rw-r--r--app/assets/javascripts/ide/stores/actions.js42
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js23
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js3
-rw-r--r--app/assets/javascripts/ide/stores/extend.js14
-rw-r--r--app/assets/javascripts/ide/stores/getters.js20
-rw-r--r--app/assets/javascripts/ide/stores/index.js35
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/clientside/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js8
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/getters.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/router/actions.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/router/index.js10
-rw-r--r--app/assets/javascripts/ide/stores/modules/router/mutation_types.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/router/mutations.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/router/state.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js98
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/index.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js118
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js64
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js14
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/constants.js9
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/getters.js19
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/index.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/messages.js55
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js11
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/mutations.js64
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/state.js13
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/utils.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/actions.js41
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/index.js10
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/messages.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/mutation_types.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/mutations.js22
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal_sync/state.js6
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js12
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js8
-rw-r--r--app/assets/javascripts/ide/stores/mutations/tree.js5
-rw-r--r--app/assets/javascripts/ide/stores/plugins/terminal.js25
-rw-r--r--app/assets/javascripts/ide/stores/plugins/terminal_sync.js49
-rw-r--r--app/assets/javascripts/ide/stores/state.js3
-rw-r--r--app/assets/javascripts/ide/stores/utils.js44
46 files changed, 781 insertions, 138 deletions
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index e32b5ac7bdc..c881f1221e5 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -7,7 +7,6 @@ import * as types from './mutation_types';
import { decorateFiles } from '../lib/files';
import { stageKeys } from '../constants';
import service from '../services';
-import router from '../ide_router';
import eventHub from '../eventhub';
export const redirectToUrl = (self, url) => visitUrl(url);
@@ -20,21 +19,25 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
commit(types.REMOVE_ALL_CHANGES_FILES);
};
-export const closeAllFiles = ({ state, dispatch }) => {
- state.openFiles.forEach(file => dispatch('closeFile', file));
-};
-
export const setResizingStatus = ({ commit }, resizing) => {
commit(types.SET_RESIZING_STATUS, resizing);
};
export const createTempEntry = (
{ state, commit, dispatch, getters },
- { name, type, content = '', base64 = false, binary = false, rawPath = '' },
+ {
+ name,
+ type,
+ content = '',
+ binary = false,
+ rawPath = '',
+ openFile = true,
+ makeFileActive = true,
+ },
) => {
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
- if (state.entries[name] && !state.entries[name].deleted) {
+ if (getters.entryExists(name)) {
flash(
sprintf(__('The name "%{name}" is already taken in this directory.'), {
name: name.split('/').pop(),
@@ -46,7 +49,7 @@ export const createTempEntry = (
true,
);
- return;
+ return undefined;
}
const data = decorateFiles({
@@ -56,7 +59,6 @@ export const createTempEntry = (
type,
tempFile: true,
content,
- base64,
binary,
rawPath,
});
@@ -69,18 +71,31 @@ export const createTempEntry = (
});
if (type === 'blob') {
- commit(types.TOGGLE_FILE_OPEN, file.path);
+ if (openFile) commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.STAGE_CHANGE, { path: file.path, diffInfo: getters.getDiffInfo(file.path) });
- dispatch('setFileActive', file.path);
+ if (openFile && makeFileActive) dispatch('setFileActive', file.path);
dispatch('triggerFilesChange');
}
if (parentPath && !state.entries[parentPath].opened) {
commit(types.TOGGLE_TREE_OPEN, parentPath);
}
+
+ return file;
};
+export const addTempImage = ({ dispatch, getters }, { name, rawPath = '' }) =>
+ dispatch('createTempEntry', {
+ name: getters.getAvailableFileName(name),
+ type: 'blob',
+ content: rawPath.split('base64,')[1],
+ binary: true,
+ rawPath,
+ openFile: false,
+ makeFileActive: false,
+ });
+
export const scrollToTab = () => {
Vue.nextTick(() => {
const tabs = document.getElementById('tabs');
@@ -239,7 +254,7 @@ export const renameEntry = ({ dispatch, commit, state, getters }, { path, name,
}
if (newEntry.opened) {
- router.push(`/project${newEntry.url}`);
+ dispatch('router/push', `/project${newEntry.url}`, { root: true });
}
}
@@ -297,6 +312,3 @@ export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
export * from './actions/merge_request';
-
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index da7d4a44bde..47f9337a288 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -3,8 +3,7 @@ import { __ } from '~/locale';
import eventHub from '../../eventhub';
import service from '../../services';
import * as types from '../mutation_types';
-import router from '../../ide_router';
-import { addFinalNewlineIfNeeded, setPageTitleForFile } from '../utils';
+import { setPageTitleForFile } from '../utils';
import { viewerTypes, stageKeys } from '../../constants';
export const closeFile = ({ commit, state, dispatch }, file) => {
@@ -30,10 +29,10 @@ export const closeFile = ({ commit, state, dispatch }, file) => {
keyPrefix: nextFileToOpen.staged ? 'staged' : 'unstaged',
});
} else {
- router.push(`/project${nextFileToOpen.url}`);
+ dispatch('router/push', `/project${nextFileToOpen.url}`, { root: true });
}
} else if (!state.openFiles.length) {
- router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
+ dispatch('router/push', `/project/${file.projectId}/tree/${file.branchId}/`, { root: true });
}
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
@@ -152,7 +151,7 @@ export const changeFileContent = ({ commit, state, getters }, { path, content })
const file = state.entries[path];
commit(types.UPDATE_FILE_CONTENT, {
path,
- content: addFinalNewlineIfNeeded(content),
+ content,
});
const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
@@ -170,12 +169,6 @@ export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => {
}
};
-export const setFileEOL = ({ getters, commit }, { eol }) => {
- if (getters.activeFile) {
- commit(types.SET_FILE_EOL, { file: getters.activeFile, eol });
- }
-};
-
export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn }) => {
if (getters.activeFile) {
commit(types.SET_FILE_POSITION, {
@@ -226,7 +219,7 @@ export const discardFileChanges = ({ dispatch, state, commit, getters }, path) =
if (!isDestructiveDiscard && file.path === getters.activeFile?.path) {
dispatch('updateDelayViewerUpdated', true)
.then(() => {
- router.push(`/project${file.url}`);
+ dispatch('router/push', `/project${file.url}`, { root: true });
})
.catch(e => {
throw e;
@@ -275,14 +268,16 @@ export const unstageChange = ({ commit, dispatch, getters }, path) => {
}
};
-export const openPendingTab = ({ commit, getters, state }, { file, keyPrefix }) => {
+export const openPendingTab = ({ commit, dispatch, getters, state }, { file, keyPrefix }) => {
if (getters.activeFile && getters.activeFile.key === `${keyPrefix}-${file.key}`) return false;
state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
commit(types.ADD_PENDING_TAB, { file, keyPrefix });
- router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
+ dispatch('router/push', `/project/${file.projectId}/tree/${state.currentBranchId}/`, {
+ root: true,
+ });
return true;
};
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 6c8fb9f90aa..d172bb31ae5 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -4,7 +4,6 @@ import { __, sprintf } from '~/locale';
import service from '../../services';
import api from '../../../api';
import * as types from '../mutation_types';
-import router from '../../ide_router';
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
new Promise((resolve, reject) => {
@@ -57,7 +56,7 @@ export const createNewBranchFromDefault = ({ state, dispatch, getters }, branch)
})
.then(() => {
dispatch('setErrorMessage', null);
- router.push(`${router.currentRoute.path}?${Date.now()}`);
+ window.location.reload();
})
.catch(() => {
dispatch('setErrorMessage', {
diff --git a/app/assets/javascripts/ide/stores/extend.js b/app/assets/javascripts/ide/stores/extend.js
new file mode 100644
index 00000000000..1c1636cf6ca
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/extend.js
@@ -0,0 +1,14 @@
+import terminal from './plugins/terminal';
+import terminalSync from './plugins/terminal_sync';
+
+const plugins = () => [
+ terminal,
+ ...(gon.features && gon.features.buildServiceProxy ? [terminalSync] : []),
+];
+
+export default (store, el) => {
+ // plugins is actually an array of plugin factories, so we have to create first then call
+ plugins().forEach(plugin => plugin(el)(store));
+
+ return store;
+};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 5d0a8570906..53734fa626b 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -50,9 +50,6 @@ export const emptyRepo = state =>
export const currentTree = state =>
state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
-export const hasChanges = state =>
- Boolean(state.changedFiles.length) || Boolean(state.stagedFiles.length);
-
export const hasMergeRequest = state => Boolean(state.currentMergeRequestId);
export const allBlobs = state =>
@@ -162,5 +159,18 @@ export const canCreateMergeRequests = (state, getters) =>
export const canPushCode = (state, getters) =>
Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_PUSH_CODE]);
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
+export const entryExists = state => path =>
+ Boolean(state.entries[path] && !state.entries[path].deleted);
+
+export const getAvailableFileName = (state, getters) => path => {
+ let newPath = path;
+
+ while (getters.entryExists(newPath)) {
+ newPath = newPath.replace(
+ /([ _-]?)(\d*)(\..+?$|$)/,
+ (_, before, number, after) => `${before || '_'}${Number(number) + 1}${after}`,
+ );
+ }
+
+ return newPath;
+};
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index 85550578e94..18c466cc93d 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -11,24 +11,27 @@ import branches from './modules/branches';
import fileTemplates from './modules/file_templates';
import paneModule from './modules/pane';
import clientsideModule from './modules/clientside';
+import routerModule from './modules/router';
Vue.use(Vuex);
-export const createStore = () =>
- new Vuex.Store({
- state: state(),
- actions,
- mutations,
- getters,
- modules: {
- commit: commitModule,
- pipelines,
- mergeRequests,
- branches,
- fileTemplates: fileTemplates(),
- rightPane: paneModule(),
- clientside: clientsideModule(),
- },
- });
+export const createStoreOptions = () => ({
+ state: state(),
+ actions,
+ mutations,
+ getters,
+ modules: {
+ commit: commitModule,
+ pipelines,
+ mergeRequests,
+ branches,
+ fileTemplates: fileTemplates(),
+ rightPane: paneModule(),
+ clientside: clientsideModule(),
+ router: routerModule,
+ },
+});
+
+export const createStore = () => new Vuex.Store(createStoreOptions());
export default createStore();
diff --git a/app/assets/javascripts/ide/stores/modules/branches/index.js b/app/assets/javascripts/ide/stores/modules/branches/index.js
index 04e7e0f08f1..deda95cd0c9 100644
--- a/app/assets/javascripts/ide/stores/modules/branches/index.js
+++ b/app/assets/javascripts/ide/stores/modules/branches/index.js
@@ -4,7 +4,7 @@ import mutations from './mutations';
export default {
namespaced: true,
- state: state(),
+ state,
actions,
mutations,
};
diff --git a/app/assets/javascripts/ide/stores/modules/clientside/actions.js b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
index eb3bcdff2ae..2bebf8b90ce 100644
--- a/app/assets/javascripts/ide/stores/modules/clientside/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
@@ -8,5 +8,4 @@ export const pingUsage = ({ rootGetters }) => {
return axios.post(url);
};
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
+export default pingUsage;
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 592c7e15918..005bd0240e2 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -3,7 +3,6 @@ import flash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status';
import * as rootTypes from '../../mutation_types';
import { createCommitPayload, createNewMergeRequestUrl } from '../../utils';
-import router from '../../../ide_router';
import service from '../../../services';
import * as types from './mutation_types';
import consts from './constants';
@@ -196,8 +195,10 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
dispatch('updateViewer', 'editor', { root: true });
if (rootGetters.activeFile) {
- router.push(
+ dispatch(
+ 'router/push',
`/project/${rootState.currentProjectId}/blob/${branchName}/-/${rootGetters.activeFile.path}`,
+ { root: true },
);
}
}
@@ -234,6 +235,3 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
window.dispatchEvent(new Event('resize'));
});
};
-
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 413c4b0110d..37f887bcf0a 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -59,6 +59,3 @@ export const shouldDisableNewMrOption = (state, getters, rootState, rootGetters)
export const shouldCreateMR = (state, getters) =>
state.shouldCreateMR && !getters.shouldDisableNewMrOption;
-
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/index.js b/app/assets/javascripts/ide/stores/modules/commit/index.js
index 3bf65b02847..5cec73bde2e 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/index.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/index.js
@@ -5,7 +5,7 @@ import * as getters from './getters';
export default {
namespaced: true,
- state: state(),
+ state,
mutations,
actions,
getters,
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
index 59ead8a3dcf..6b2c929cd44 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
@@ -117,6 +117,3 @@ export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => {
dispatch('discardFileChanges', file.path, { root: true });
}
};
-
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/pane/actions.js b/app/assets/javascripts/ide/stores/modules/pane/actions.js
index a8fcdf539ec..b7cff368fe4 100644
--- a/app/assets/javascripts/ide/stores/modules/pane/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/pane/actions.js
@@ -25,6 +25,3 @@ export const open = ({ state, commit }, view) => {
export const close = ({ commit }) => {
commit(types.SET_OPEN, false);
};
-
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/pane/getters.js b/app/assets/javascripts/ide/stores/modules/pane/getters.js
index c346cf13689..7816172bb6f 100644
--- a/app/assets/javascripts/ide/stores/modules/pane/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/pane/getters.js
@@ -1,4 +1,3 @@
-export const isActiveView = state => view => state.currentView === view;
-
-export const isAliveView = (state, getters) => view =>
- state.keepAliveViews[view] || (state.isOpen && getters.isActiveView(view));
+// eslint-disable-next-line import/prefer-default-export
+export const isAliveView = state => view =>
+ state.keepAliveViews[view] || (state.isOpen && state.currentView === view);
diff --git a/app/assets/javascripts/ide/stores/modules/router/actions.js b/app/assets/javascripts/ide/stores/modules/router/actions.js
new file mode 100644
index 00000000000..849067599f2
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/router/actions.js
@@ -0,0 +1,6 @@
+import * as types from './mutation_types';
+
+// eslint-disable-next-line import/prefer-default-export
+export const push = ({ commit }, fullPath) => {
+ commit(types.PUSH, fullPath);
+};
diff --git a/app/assets/javascripts/ide/stores/modules/router/index.js b/app/assets/javascripts/ide/stores/modules/router/index.js
new file mode 100644
index 00000000000..68c81bb4509
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/router/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import mutations from './mutations';
+import * as actions from './actions';
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions,
+};
diff --git a/app/assets/javascripts/ide/stores/modules/router/mutation_types.js b/app/assets/javascripts/ide/stores/modules/router/mutation_types.js
new file mode 100644
index 00000000000..ae99073cc4c
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/router/mutation_types.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const PUSH = 'PUSH';
diff --git a/app/assets/javascripts/ide/stores/modules/router/mutations.js b/app/assets/javascripts/ide/stores/modules/router/mutations.js
new file mode 100644
index 00000000000..471cace314c
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/router/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.PUSH](state, fullPath) {
+ state.fullPath = fullPath;
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/router/state.js b/app/assets/javascripts/ide/stores/modules/router/state.js
new file mode 100644
index 00000000000..abb6c5239e4
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/router/state.js
@@ -0,0 +1,3 @@
+export default () => ({
+ fullPath: '',
+});
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js
new file mode 100644
index 00000000000..43b6650b241
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js
@@ -0,0 +1,98 @@
+import Api from '~/api';
+import httpStatus from '~/lib/utils/http_status';
+import * as types from '../mutation_types';
+import * as messages from '../messages';
+import { CHECK_CONFIG, CHECK_RUNNERS, RETRY_RUNNERS_INTERVAL } from '../constants';
+import * as terminalService from '../../../../services/terminals';
+
+export const requestConfigCheck = ({ commit }) => {
+ commit(types.REQUEST_CHECK, CHECK_CONFIG);
+};
+
+export const receiveConfigCheckSuccess = ({ commit }) => {
+ commit(types.SET_VISIBLE, true);
+ commit(types.RECEIVE_CHECK_SUCCESS, CHECK_CONFIG);
+};
+
+export const receiveConfigCheckError = ({ commit, state }, e) => {
+ const { status } = e.response;
+ const { paths } = state;
+
+ const isVisible = status !== httpStatus.FORBIDDEN && status !== httpStatus.NOT_FOUND;
+ commit(types.SET_VISIBLE, isVisible);
+
+ const message = messages.configCheckError(status, paths.webTerminalConfigHelpPath);
+ commit(types.RECEIVE_CHECK_ERROR, { type: CHECK_CONFIG, message });
+};
+
+export const fetchConfigCheck = ({ dispatch, rootState, rootGetters }) => {
+ dispatch('requestConfigCheck');
+
+ const { currentBranchId } = rootState;
+ const { currentProject } = rootGetters;
+
+ terminalService
+ .checkConfig(currentProject.path_with_namespace, currentBranchId)
+ .then(() => {
+ dispatch('receiveConfigCheckSuccess');
+ })
+ .catch(e => {
+ dispatch('receiveConfigCheckError', e);
+ });
+};
+
+export const requestRunnersCheck = ({ commit }) => {
+ commit(types.REQUEST_CHECK, CHECK_RUNNERS);
+};
+
+export const receiveRunnersCheckSuccess = ({ commit, dispatch, state }, data) => {
+ if (data.length) {
+ commit(types.RECEIVE_CHECK_SUCCESS, CHECK_RUNNERS);
+ } else {
+ const { paths } = state;
+
+ commit(types.RECEIVE_CHECK_ERROR, {
+ type: CHECK_RUNNERS,
+ message: messages.runnersCheckEmpty(paths.webTerminalRunnersHelpPath),
+ });
+
+ dispatch('retryRunnersCheck');
+ }
+};
+
+export const receiveRunnersCheckError = ({ commit }) => {
+ commit(types.RECEIVE_CHECK_ERROR, {
+ type: CHECK_RUNNERS,
+ message: messages.UNEXPECTED_ERROR_RUNNERS,
+ });
+};
+
+export const retryRunnersCheck = ({ dispatch, state }) => {
+ // if the overall check has failed, don't worry about retrying
+ const check = state.checks[CHECK_CONFIG];
+ if (!check.isLoading && !check.isValid) {
+ return;
+ }
+
+ setTimeout(() => {
+ dispatch('fetchRunnersCheck', { background: true });
+ }, RETRY_RUNNERS_INTERVAL);
+};
+
+export const fetchRunnersCheck = ({ dispatch, rootGetters }, options = {}) => {
+ const { background = false } = options;
+
+ if (!background) {
+ dispatch('requestRunnersCheck');
+ }
+
+ const { currentProject } = rootGetters;
+
+ Api.projectRunners(currentProject.id, { params: { scope: 'active' } })
+ .then(({ data }) => {
+ dispatch('receiveRunnersCheckSuccess', data);
+ })
+ .catch(e => {
+ dispatch('receiveRunnersCheckError', e);
+ });
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js
new file mode 100644
index 00000000000..112b3794114
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js
@@ -0,0 +1,5 @@
+export * from './setup';
+export * from './checks';
+export * from './session_controls';
+export * from './session_status';
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js
new file mode 100644
index 00000000000..d3dcb9dd125
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js
@@ -0,0 +1,118 @@
+import axios from '~/lib/utils/axios_utils';
+import httpStatus from '~/lib/utils/http_status';
+import flash from '~/flash';
+import * as types from '../mutation_types';
+import * as messages from '../messages';
+import * as terminalService from '../../../../services/terminals';
+import { STARTING, STOPPING, STOPPED } from '../constants';
+
+export const requestStartSession = ({ commit }) => {
+ commit(types.SET_SESSION_STATUS, STARTING);
+};
+
+export const receiveStartSessionSuccess = ({ commit, dispatch }, data) => {
+ commit(types.SET_SESSION, {
+ id: data.id,
+ status: data.status,
+ showPath: data.show_path,
+ cancelPath: data.cancel_path,
+ retryPath: data.retry_path,
+ terminalPath: data.terminal_path,
+ proxyWebsocketPath: data.proxy_websocket_path,
+ services: data.services,
+ });
+
+ dispatch('pollSessionStatus');
+};
+
+export const receiveStartSessionError = ({ dispatch }) => {
+ flash(messages.UNEXPECTED_ERROR_STARTING);
+ dispatch('killSession');
+};
+
+export const startSession = ({ state, dispatch, rootGetters, rootState }) => {
+ if (state.session && state.session.status === STARTING) {
+ return;
+ }
+
+ const { currentProject } = rootGetters;
+ const { currentBranchId } = rootState;
+
+ dispatch('requestStartSession');
+
+ terminalService
+ .create(currentProject.path_with_namespace, currentBranchId)
+ .then(({ data }) => {
+ dispatch('receiveStartSessionSuccess', data);
+ })
+ .catch(error => {
+ dispatch('receiveStartSessionError', error);
+ });
+};
+
+export const requestStopSession = ({ commit }) => {
+ commit(types.SET_SESSION_STATUS, STOPPING);
+};
+
+export const receiveStopSessionSuccess = ({ dispatch }) => {
+ dispatch('killSession');
+};
+
+export const receiveStopSessionError = ({ dispatch }) => {
+ flash(messages.UNEXPECTED_ERROR_STOPPING);
+ dispatch('killSession');
+};
+
+export const stopSession = ({ state, dispatch }) => {
+ const { cancelPath } = state.session;
+
+ dispatch('requestStopSession');
+
+ axios
+ .post(cancelPath)
+ .then(() => {
+ dispatch('receiveStopSessionSuccess');
+ })
+ .catch(err => {
+ dispatch('receiveStopSessionError', err);
+ });
+};
+
+export const killSession = ({ commit, dispatch }) => {
+ dispatch('stopPollingSessionStatus');
+ commit(types.SET_SESSION_STATUS, STOPPED);
+};
+
+export const restartSession = ({ state, dispatch, rootState }) => {
+ const { status, retryPath } = state.session;
+ const { currentBranchId } = rootState;
+
+ if (status !== STOPPED) {
+ return;
+ }
+
+ if (!retryPath) {
+ dispatch('startSession');
+ return;
+ }
+
+ dispatch('requestStartSession');
+
+ axios
+ .post(retryPath, { branch: currentBranchId, format: 'json' })
+ .then(({ data }) => {
+ dispatch('receiveStartSessionSuccess', data);
+ })
+ .catch(error => {
+ const responseStatus = error.response && error.response.status;
+ // We may have removed the build, in this case we'll just create a new session
+ if (
+ responseStatus === httpStatus.NOT_FOUND ||
+ responseStatus === httpStatus.UNPROCESSABLE_ENTITY
+ ) {
+ dispatch('startSession');
+ } else {
+ dispatch('receiveStartSessionError', error);
+ }
+ });
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js
new file mode 100644
index 00000000000..59ba1605c47
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js
@@ -0,0 +1,64 @@
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import * as types from '../mutation_types';
+import * as messages from '../messages';
+import { isEndingStatus } from '../utils';
+
+export const pollSessionStatus = ({ state, dispatch, commit }) => {
+ dispatch('stopPollingSessionStatus');
+ dispatch('fetchSessionStatus');
+
+ const interval = setInterval(() => {
+ if (!state.session) {
+ dispatch('stopPollingSessionStatus');
+ } else {
+ dispatch('fetchSessionStatus');
+ }
+ }, 5000);
+
+ commit(types.SET_SESSION_STATUS_INTERVAL, interval);
+};
+
+export const stopPollingSessionStatus = ({ state, commit }) => {
+ const { sessionStatusInterval } = state;
+
+ if (!sessionStatusInterval) {
+ return;
+ }
+
+ clearInterval(sessionStatusInterval);
+
+ commit(types.SET_SESSION_STATUS_INTERVAL, 0);
+};
+
+export const receiveSessionStatusSuccess = ({ commit, dispatch }, data) => {
+ const status = data && data.status;
+
+ commit(types.SET_SESSION_STATUS, status);
+
+ if (isEndingStatus(status)) {
+ dispatch('killSession');
+ }
+};
+
+export const receiveSessionStatusError = ({ dispatch }) => {
+ flash(messages.UNEXPECTED_ERROR_STATUS);
+ dispatch('killSession');
+};
+
+export const fetchSessionStatus = ({ dispatch, state }) => {
+ if (!state.session) {
+ return;
+ }
+
+ const { showPath } = state.session;
+
+ axios
+ .get(showPath)
+ .then(({ data }) => {
+ dispatch('receiveSessionStatusSuccess', data);
+ })
+ .catch(error => {
+ dispatch('receiveSessionStatusError', error);
+ });
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js
new file mode 100644
index 00000000000..78ad94f8a91
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js
@@ -0,0 +1,14 @@
+import * as types from '../mutation_types';
+
+export const init = ({ dispatch }) => {
+ dispatch('fetchConfigCheck');
+ dispatch('fetchRunnersCheck');
+};
+
+export const hideSplash = ({ commit }) => {
+ commit(types.HIDE_SPLASH);
+};
+
+export const setPaths = ({ commit }, paths) => {
+ commit(types.SET_PATHS, paths);
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/constants.js b/app/assets/javascripts/ide/stores/modules/terminal/constants.js
new file mode 100644
index 00000000000..f7ae9d8f4ea
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/constants.js
@@ -0,0 +1,9 @@
+export const CHECK_CONFIG = 'config';
+export const CHECK_RUNNERS = 'runners';
+export const RETRY_RUNNERS_INTERVAL = 10000;
+
+export const STARTING = 'starting';
+export const PENDING = 'pending';
+export const RUNNING = 'running';
+export const STOPPING = 'stopping';
+export const STOPPED = 'stopped';
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/getters.js b/app/assets/javascripts/ide/stores/modules/terminal/getters.js
new file mode 100644
index 00000000000..6d64ee4ab6e
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/getters.js
@@ -0,0 +1,19 @@
+export const allCheck = state => {
+ const checks = Object.values(state.checks);
+
+ if (checks.some(check => check.isLoading)) {
+ return { isLoading: true };
+ }
+
+ const invalidCheck = checks.find(check => !check.isValid);
+ const isValid = !invalidCheck;
+ const message = !invalidCheck ? '' : invalidCheck.message;
+
+ return {
+ isLoading: false,
+ isValid,
+ message,
+ };
+};
+
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/index.js b/app/assets/javascripts/ide/stores/modules/terminal/index.js
new file mode 100644
index 00000000000..ef1289e1722
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/index.js
@@ -0,0 +1,12 @@
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+export default () => ({
+ namespaced: true,
+ actions,
+ getters,
+ mutations,
+ state: state(),
+});
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/messages.js b/app/assets/javascripts/ide/stores/modules/terminal/messages.js
new file mode 100644
index 00000000000..38c5a8a28d8
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/messages.js
@@ -0,0 +1,55 @@
+import { escape } from 'lodash';
+import { __, sprintf } from '~/locale';
+import httpStatus from '~/lib/utils/http_status';
+
+export const UNEXPECTED_ERROR_CONFIG = __(
+ 'An unexpected error occurred while checking the project environment.',
+);
+export const UNEXPECTED_ERROR_RUNNERS = __(
+ 'An unexpected error occurred while checking the project runners.',
+);
+export const UNEXPECTED_ERROR_STATUS = __(
+ 'An unexpected error occurred while communicating with the Web Terminal.',
+);
+export const UNEXPECTED_ERROR_STARTING = __(
+ 'An unexpected error occurred while starting the Web Terminal.',
+);
+export const UNEXPECTED_ERROR_STOPPING = __(
+ 'An unexpected error occurred while stopping the Web Terminal.',
+);
+export const EMPTY_RUNNERS = __(
+ 'Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}',
+);
+export const ERROR_CONFIG = __(
+ 'Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}',
+);
+export const ERROR_PERMISSION = __(
+ 'You do not have permission to run the Web Terminal. Please contact a project administrator.',
+);
+
+export const configCheckError = (status, helpUrl) => {
+ if (status === httpStatus.UNPROCESSABLE_ENTITY) {
+ return sprintf(
+ ERROR_CONFIG,
+ {
+ helpStart: `<a href="${escape(helpUrl)}" target="_blank">`,
+ helpEnd: '</a>',
+ },
+ false,
+ );
+ } else if (status === httpStatus.FORBIDDEN) {
+ return ERROR_PERMISSION;
+ }
+
+ return UNEXPECTED_ERROR_CONFIG;
+};
+
+export const runnersCheckEmpty = helpUrl =>
+ sprintf(
+ EMPTY_RUNNERS,
+ {
+ helpStart: `<a href="${escape(helpUrl)}" target="_blank">`,
+ helpEnd: '</a>',
+ },
+ false,
+ );
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js b/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js
new file mode 100644
index 00000000000..b6a6f28abfa
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js
@@ -0,0 +1,11 @@
+export const SET_VISIBLE = 'SET_VISIBLE';
+export const HIDE_SPLASH = 'HIDE_SPLASH';
+export const SET_PATHS = 'SET_PATHS';
+
+export const REQUEST_CHECK = 'REQUEST_CHECK';
+export const RECEIVE_CHECK_SUCCESS = 'RECEIVE_CHECK_SUCCESS';
+export const RECEIVE_CHECK_ERROR = 'RECEIVE_CHECK_ERROR';
+
+export const SET_SESSION = 'SET_SESSION';
+export const SET_SESSION_STATUS = 'SET_SESSION_STATUS';
+export const SET_SESSION_STATUS_INTERVAL = 'SET_SESSION_STATUS_INTERVAL';
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/mutations.js b/app/assets/javascripts/ide/stores/modules/terminal/mutations.js
new file mode 100644
index 00000000000..37f40af9c2e
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/mutations.js
@@ -0,0 +1,64 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_VISIBLE](state, isVisible) {
+ Object.assign(state, {
+ isVisible,
+ });
+ },
+ [types.HIDE_SPLASH](state) {
+ Object.assign(state, {
+ isShowSplash: false,
+ });
+ },
+ [types.SET_PATHS](state, paths) {
+ Object.assign(state, {
+ paths,
+ });
+ },
+ [types.REQUEST_CHECK](state, type) {
+ Object.assign(state.checks, {
+ [type]: {
+ isLoading: true,
+ },
+ });
+ },
+ [types.RECEIVE_CHECK_ERROR](state, { type, message }) {
+ Object.assign(state.checks, {
+ [type]: {
+ isLoading: false,
+ isValid: false,
+ message,
+ },
+ });
+ },
+ [types.RECEIVE_CHECK_SUCCESS](state, type) {
+ Object.assign(state.checks, {
+ [type]: {
+ isLoading: false,
+ isValid: true,
+ message: null,
+ },
+ });
+ },
+ [types.SET_SESSION](state, session) {
+ Object.assign(state, {
+ session,
+ });
+ },
+ [types.SET_SESSION_STATUS](state, status) {
+ const session = {
+ ...(state.session || {}),
+ status,
+ };
+
+ Object.assign(state, {
+ session,
+ });
+ },
+ [types.SET_SESSION_STATUS_INTERVAL](state, sessionStatusInterval) {
+ Object.assign(state, {
+ sessionStatusInterval,
+ });
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/state.js b/app/assets/javascripts/ide/stores/modules/terminal/state.js
new file mode 100644
index 00000000000..f35a10ed2fe
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/state.js
@@ -0,0 +1,13 @@
+import { CHECK_CONFIG, CHECK_RUNNERS } from './constants';
+
+export default () => ({
+ checks: {
+ [CHECK_CONFIG]: { isLoading: true },
+ [CHECK_RUNNERS]: { isLoading: true },
+ },
+ isVisible: false,
+ isShowSplash: true,
+ paths: {},
+ session: null,
+ sessionStatusInterval: 0,
+});
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/utils.js b/app/assets/javascripts/ide/stores/modules/terminal/utils.js
new file mode 100644
index 00000000000..c30136b5277
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal/utils.js
@@ -0,0 +1,5 @@
+import { STARTING, PENDING, RUNNING } from './constants';
+
+export const isStartingStatus = status => status === STARTING || status === PENDING;
+export const isRunningStatus = status => status === RUNNING;
+export const isEndingStatus = status => !isStartingStatus(status) && !isRunningStatus(status);
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/actions.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/actions.js
new file mode 100644
index 00000000000..2fee6b4e974
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/actions.js
@@ -0,0 +1,41 @@
+import * as types from './mutation_types';
+import mirror, { canConnect } from '../../../lib/mirror';
+
+export const upload = ({ rootState, commit }) => {
+ commit(types.START_LOADING);
+
+ return mirror
+ .upload(rootState)
+ .then(() => {
+ commit(types.SET_SUCCESS);
+ })
+ .catch(err => {
+ commit(types.SET_ERROR, err);
+ });
+};
+
+export const stop = ({ commit }) => {
+ mirror.disconnect();
+
+ commit(types.STOP);
+};
+
+export const start = ({ rootState, commit }) => {
+ const { session } = rootState.terminal;
+ const path = session && session.proxyWebsocketPath;
+ if (!path || !canConnect(session)) {
+ return Promise.reject();
+ }
+
+ commit(types.START_LOADING);
+
+ return mirror
+ .connect(path)
+ .then(() => {
+ commit(types.SET_SUCCESS);
+ })
+ .catch(err => {
+ commit(types.SET_ERROR, err);
+ throw err;
+ });
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/index.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/index.js
new file mode 100644
index 00000000000..795c2fad724
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+export default () => ({
+ namespaced: true,
+ actions,
+ mutations,
+ state: state(),
+});
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/messages.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/messages.js
new file mode 100644
index 00000000000..e50e1a1406b
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/messages.js
@@ -0,0 +1,5 @@
+import { __ } from '~/locale';
+
+export const MSG_TERMINAL_SYNC_CONNECTING = __('Connecting to terminal sync service');
+export const MSG_TERMINAL_SYNC_UPLOADING = __('Uploading changes to terminal');
+export const MSG_TERMINAL_SYNC_RUNNING = __('Terminal sync service is running');
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/mutation_types.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/mutation_types.js
new file mode 100644
index 00000000000..ec809540c18
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/mutation_types.js
@@ -0,0 +1,4 @@
+export const START_LOADING = 'START_LOADING';
+export const SET_ERROR = 'SET_ERROR';
+export const SET_SUCCESS = 'SET_SUCCESS';
+export const STOP = 'STOP';
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/mutations.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/mutations.js
new file mode 100644
index 00000000000..70ed137776a
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/mutations.js
@@ -0,0 +1,22 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.START_LOADING](state) {
+ state.isLoading = true;
+ state.isError = false;
+ },
+ [types.SET_ERROR](state, { message }) {
+ state.isLoading = false;
+ state.isError = true;
+ state.message = message;
+ },
+ [types.SET_SUCCESS](state) {
+ state.isLoading = false;
+ state.isError = false;
+ state.isStarted = true;
+ },
+ [types.STOP](state) {
+ state.isLoading = false;
+ state.isStarted = false;
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/terminal_sync/state.js b/app/assets/javascripts/ide/stores/modules/terminal_sync/state.js
new file mode 100644
index 00000000000..7ec3e38f675
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/terminal_sync/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+ isLoading: false,
+ isStarted: false,
+ isError: false,
+ message: '',
+});
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index 5c78bfefa04..d94adc3760f 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -27,7 +27,6 @@ export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
export const SET_TREE_OPEN = 'SET_TREE_OPEN';
-export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
export const CREATE_TREE = 'CREATE_TREE';
export const REMOVE_ALL_CHANGES_FILES = 'REMOVE_ALL_CHANGES_FILES';
@@ -41,7 +40,6 @@ export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
export const SET_FILE_POSITION = 'SET_FILE_POSITION';
export const SET_FILE_VIEWMODE = 'SET_FILE_VIEWMODE';
-export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED';
export const REMOVE_FILE_FROM_CHANGED = 'REMOVE_FILE_FROM_CHANGED';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 12ac10df206..e827aacac13 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -65,14 +65,10 @@ export default {
// NOTE: We can't clone `entry` in any of the below assignments because
// we need `state.entries` and the `entry.tree` to reference the same object.
- if (!foundEntry) {
+ if (!foundEntry || foundEntry.deleted) {
Object.assign(state.entries, {
[key]: entry,
});
- } else if (foundEntry.deleted) {
- Object.assign(state.entries, {
- [key]: Object.assign(entry, { replaces: true }),
- });
} else {
const tree = entry.tree.filter(
f => foundEntry.tree.find(e => e.path === f.path) === undefined,
@@ -147,7 +143,6 @@ export default {
raw: file.content,
changed: Boolean(changedFile),
staged: false,
- replaces: false,
lastCommitSha: lastCommit.commit.id,
prevId: undefined,
@@ -164,9 +159,6 @@ export default {
Object.assign(state.entries[file.path], {
rawPath: file.rawPath.replace(regex, file.path),
- permalink: file.permalink.replace(regex, file.path),
- commitsPath: file.commitsPath.replace(regex, file.path),
- blamePath: file.blamePath.replace(regex, file.path),
});
}
},
@@ -207,8 +199,6 @@ export default {
state.changedFiles = state.changedFiles.concat(entry);
}
}
-
- state.unusedSeal = false;
},
[types.RENAME_ENTRY](state, { path, name, parentPath }) {
const oldEntry = state.entries[path];
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 5c5920a3027..c90bc2a3320 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -99,11 +99,6 @@ export default {
fileLanguage,
});
},
- [types.SET_FILE_EOL](state, { file, eol }) {
- Object.assign(state.entries[file.path], {
- eol,
- });
- },
[types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
Object.assign(state.entries[file.path], {
editorRow,
@@ -153,13 +148,11 @@ export default {
[types.ADD_FILE_TO_CHANGED](state, path) {
Object.assign(state, {
changedFiles: state.changedFiles.concat(state.entries[path]),
- unusedSeal: false,
});
},
[types.REMOVE_FILE_FROM_CHANGED](state, path) {
Object.assign(state, {
changedFiles: state.changedFiles.filter(f => f.path !== path),
- unusedSeal: false,
});
},
[types.STAGE_CHANGE](state, { path, diffInfo }) {
@@ -175,7 +168,6 @@ export default {
deleted: diffInfo.deleted,
}),
}),
- unusedSeal: false,
});
if (stagedFile) {
diff --git a/app/assets/javascripts/ide/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
index c8f14a680c2..cce43a99bd9 100644
--- a/app/assets/javascripts/ide/stores/mutations/tree.js
+++ b/app/assets/javascripts/ide/stores/mutations/tree.js
@@ -34,11 +34,6 @@ export default {
Object.assign(selectedTree, { tree });
},
- [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
- Object.assign(tree, {
- lastCommitPath: url,
- });
- },
[types.REMOVE_ALL_CHANGES_FILES](state) {
Object.assign(state, {
changedFiles: [],
diff --git a/app/assets/javascripts/ide/stores/plugins/terminal.js b/app/assets/javascripts/ide/stores/plugins/terminal.js
new file mode 100644
index 00000000000..66539c7bd4f
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/plugins/terminal.js
@@ -0,0 +1,25 @@
+import * as mutationTypes from '~/ide/stores/mutation_types';
+import terminalModule from '../modules/terminal';
+
+function getPathsFromData(el) {
+ return {
+ webTerminalSvgPath: el.dataset.eeWebTerminalSvgPath,
+ webTerminalHelpPath: el.dataset.eeWebTerminalHelpPath,
+ webTerminalConfigHelpPath: el.dataset.eeWebTerminalConfigHelpPath,
+ webTerminalRunnersHelpPath: el.dataset.eeWebTerminalRunnersHelpPath,
+ };
+}
+
+export default function createTerminalPlugin(el) {
+ return store => {
+ store.registerModule('terminal', terminalModule());
+
+ store.dispatch('terminal/setPaths', getPathsFromData(el));
+
+ store.subscribe(({ type }) => {
+ if (type === mutationTypes.SET_BRANCH_WORKING_REFERENCE) {
+ store.dispatch('terminal/init');
+ }
+ });
+ };
+}
diff --git a/app/assets/javascripts/ide/stores/plugins/terminal_sync.js b/app/assets/javascripts/ide/stores/plugins/terminal_sync.js
new file mode 100644
index 00000000000..c60bba4293a
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/plugins/terminal_sync.js
@@ -0,0 +1,49 @@
+import { debounce } from 'lodash';
+import eventHub from '~/ide/eventhub';
+import terminalSyncModule from '../modules/terminal_sync';
+import { isEndingStatus, isRunningStatus } from '../modules/terminal/utils';
+
+const UPLOAD_DEBOUNCE = 200;
+
+/**
+ * Registers and controls the terminalSync vuex module based on IDE events.
+ *
+ * - Watches the terminal session status state to control start/stop.
+ * - Listens for file change event to control upload.
+ */
+export default function createMirrorPlugin() {
+ return store => {
+ store.registerModule('terminalSync', terminalSyncModule());
+
+ const upload = debounce(() => {
+ store.dispatch(`terminalSync/upload`);
+ }, UPLOAD_DEBOUNCE);
+
+ const stop = () => {
+ store.dispatch(`terminalSync/stop`);
+ eventHub.$off('ide.files.change', upload);
+ };
+
+ const start = () => {
+ store
+ .dispatch(`terminalSync/start`)
+ .then(() => {
+ eventHub.$on('ide.files.change', upload);
+ })
+ .catch(() => {
+ // error is handled in store
+ });
+ };
+
+ store.watch(
+ x => x.terminal && x.terminal.session && x.terminal.session.status,
+ val => {
+ if (isRunningStatus(val)) {
+ start();
+ } else if (isEndingStatus(val)) {
+ stop();
+ }
+ },
+ );
+ };
+}
diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index 0c95c22e8f8..c1a83bf0726 100644
--- a/app/assets/javascripts/ide/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -9,10 +9,8 @@ export default () => ({
stagedFiles: [],
endpoints: {},
lastCommitMsg: '',
- lastCommitPath: '',
loading: false,
openFiles: [],
- parentTreeUrl: '',
trees: {},
projects: {},
panelResizing: false,
@@ -20,7 +18,6 @@ export default () => ({
viewer: viewerTypes.edit,
delayViewerUpdated: false,
currentActivityView: leftSidebarViews.edit.name,
- unusedSeal: true,
fileFindVisible: false,
links: {},
errorMessage: null,
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 56671142bd4..1c5fe9fe9a5 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,5 +1,10 @@
import { commitActionTypes, FILE_VIEW_MODE_EDITOR } from '../constants';
-import { relativePathToAbsolute, isAbsolute, isRootRelative } from '~/lib/utils/url_utility';
+import {
+ relativePathToAbsolute,
+ isAbsolute,
+ isRootRelative,
+ isBase64DataUrl,
+} from '~/lib/utils/url_utility';
export const dataStructure = () => ({
id: '',
@@ -19,8 +24,6 @@ export const dataStructure = () => ({
active: false,
changed: false,
staged: false,
- replaces: false,
- lastCommitPath: '',
lastCommitSha: '',
lastCommit: {
id: '',
@@ -29,23 +32,14 @@ export const dataStructure = () => ({
updatedAt: '',
author: '',
},
- blamePath: '',
- commitsPath: '',
- permalink: '',
rawPath: '',
binary: false,
- html: '',
raw: '',
content: '',
- parentTreeUrl: '',
- renderError: false,
- base64: false,
editorRow: 1,
editorColumn: 1,
fileLanguage: '',
- eol: '',
viewMode: FILE_VIEW_MODE_EDITOR,
- previewMode: null,
size: 0,
parentPath: null,
lastOpenedAt: 0,
@@ -63,19 +57,14 @@ export const decorateData = entity => {
url,
name,
path,
- renderError,
content = '',
tempFile = false,
active = false,
opened = false,
changed = false,
- parentTreeUrl = '',
- base64 = false,
binary = false,
rawPath = '',
- previewMode,
file_lock,
- html,
parentPath = '',
} = entity;
@@ -91,25 +80,15 @@ export const decorateData = entity => {
tempFile,
opened,
active,
- parentTreeUrl,
changed,
- renderError,
content,
- base64,
binary,
rawPath,
- previewMode,
file_lock,
- html,
parentPath,
});
};
-export const findEntry = (tree, type, name, prop = 'name') =>
- tree.find(f => f.type === type && f[prop] === name);
-
-export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
-
export const setPageTitle = title => {
document.title = title;
};
@@ -124,7 +103,7 @@ export const commitActionForFile = file => {
return commitActionTypes.move;
} else if (file.deleted) {
return commitActionTypes.delete;
- } else if (file.tempFile && !file.replaces) {
+ } else if (file.tempFile) {
return commitActionTypes.create;
}
@@ -155,9 +134,8 @@ export const createCommitPayload = ({
file_path: f.path,
previous_path: f.prevPath || undefined,
content: f.prevPath && !f.changed ? null : f.content || undefined,
- encoding: f.base64 ? 'base64' : 'text',
- last_commit_id:
- newBranch || f.deleted || f.prevPath || f.replaces ? undefined : f.lastCommitSha,
+ encoding: isBase64DataUrl(f.rawPath) ? 'base64' : 'text',
+ last_commit_id: newBranch || f.deleted || f.prevPath ? undefined : f.lastCommitSha,
})),
start_sha: newBranch ? rootGetters.lastCommit.id : undefined,
});
@@ -272,10 +250,6 @@ export const pathsAreEqual = (a, b) => {
return cleanA === cleanB;
};
-// if the contents of a file dont end with a newline, this function adds a newline
-export const addFinalNewlineIfNeeded = content =>
- content.charAt(content.length - 1) !== '\n' ? `${content}\n` : content;
-
export function extractMarkdownImagesFromEntries(mdFile, entries) {
/**
* Regex to identify an image tag in markdown, like: