summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorTim Zallmann <tzallmann@gitlab.com>2018-04-04 10:52:05 +0000
committerTim Zallmann <tzallmann@gitlab.com>2018-04-04 10:52:05 +0000
commiteaed588bf228c833cb666a61bc7d25cf21d5f94b (patch)
treebbe0ff990fec1f5d970b38ce657b64fec2e11cfa /app/assets
parent1f59aa242279acb1b6feaefd9a868a2353520f02 (diff)
parent259adc34155c2a23dfa482cc1d2664e80200dcc8 (diff)
downloadgitlab-ce-eaed588bf228c833cb666a61bc7d25cf21d5f94b.tar.gz
Merge branch 'ide-pending-tab' into 'master'
Added pending tabs to IDE See merge request gitlab-org/gitlab-ce!17936
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue56
-rw-r--r--app/assets/javascripts/ide/components/ide.vue1
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue5
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue106
-rw-r--r--app/assets/javascripts/ide/components/repo_tabs.vue20
-rw-r--r--app/assets/javascripts/ide/ide_router.js6
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js10
-rw-r--r--app/assets/javascripts/ide/lib/common/model_manager.js21
-rw-r--r--app/assets/javascripts/ide/stores/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js48
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js49
-rw-r--r--app/assets/javascripts/ide/stores/utils.js2
14 files changed, 217 insertions, 118 deletions
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 18934af004a..560cdd941cd 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -1,38 +1,36 @@
<script>
- import { mapActions } from 'vuex';
- import icon from '~/vue_shared/components/icon.vue';
- import router from '../../ide_router';
+import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
},
- props: {
- file: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ iconName() {
+ return this.file.tempFile ? 'file-addition' : 'file-modified';
},
- computed: {
- iconName() {
- return this.file.tempFile ? 'file-addition' : 'file-modified';
- },
- iconClass() {
- return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
- },
+ iconClass() {
+ return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
},
- methods: {
- ...mapActions([
- 'discardFileChanges',
- 'updateViewer',
- ]),
- openFileInEditor(file) {
- this.updateViewer('diff');
-
- router.push(`/project${file.url}`);
- },
+ },
+ methods: {
+ ...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
+ openFileInEditor(file) {
+ return this.openPendingTab(file).then(changeViewer => {
+ if (changeViewer) {
+ this.updateViewer('diff');
+ }
+ });
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 5e44af01241..d22869466c9 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -60,6 +60,7 @@ export default {
v-if="activeFile"
>
<repo-tabs
+ :active-file="activeFile"
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index b6f8f8a1c99..b1a16350c19 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -21,7 +21,8 @@ export default {
},
watch: {
file(oldVal, newVal) {
- if (newVal.path !== this.file.path) {
+ // Compare key to allow for files opened in review mode to be cached differently
+ if (newVal.key !== this.file.key) {
this.initMonaco();
}
},
@@ -70,7 +71,7 @@ export default {
})
.then(() => {
const viewerPromise = this.delayViewerUpdated
- ? this.updateViewer('editor')
+ ? this.updateViewer(this.file.pending ? 'diff' : 'editor')
: Promise.resolve();
return viewerPromise;
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index 1935ee1a4bb..3b5068d4910 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -62,11 +62,7 @@ export default {
this.toggleTreeOpen(this.file.path);
}
- const delayPromise = this.file.changed
- ? Promise.resolve()
- : this.updateDelayViewerUpdated(true);
-
- return delayPromise.then(() => {
+ return this.updateDelayViewerUpdated(true).then(() => {
router.push(`/project${this.file.url}`);
});
},
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index c337bc813e6..304a73ed1ad 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -1,60 +1,64 @@
<script>
- import { mapActions } from 'vuex';
+import { mapActions } from 'vuex';
- import fileIcon from '~/vue_shared/components/file_icon.vue';
- import icon from '~/vue_shared/components/icon.vue';
- import fileStatusIcon from './repo_file_status_icon.vue';
- import changedFileIcon from './changed_file_icon.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import FileStatusIcon from './repo_file_status_icon.vue';
+import ChangedFileIcon from './changed_file_icon.vue';
- export default {
- components: {
- fileStatusIcon,
- fileIcon,
- icon,
- changedFileIcon,
+export default {
+ components: {
+ FileStatusIcon,
+ FileIcon,
+ Icon,
+ ChangedFileIcon,
+ },
+ props: {
+ tab: {
+ type: Object,
+ required: true,
},
- props: {
- tab: {
- type: Object,
- required: true,
- },
+ },
+ data() {
+ return {
+ tabMouseOver: false,
+ };
+ },
+ computed: {
+ closeLabel() {
+ if (this.tab.changed || this.tab.tempFile) {
+ return `${this.tab.name} changed`;
+ }
+ return `Close ${this.tab.name}`;
},
- data() {
- return {
- tabMouseOver: false,
- };
- },
- computed: {
- closeLabel() {
- if (this.tab.changed || this.tab.tempFile) {
- return `${this.tab.name} changed`;
- }
- return `Close ${this.tab.name}`;
- },
- showChangedIcon() {
- return this.tab.changed ? !this.tabMouseOver : false;
- },
+ showChangedIcon() {
+ return this.tab.changed ? !this.tabMouseOver : false;
},
+ },
+
+ methods: {
+ ...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
+ clickFile(tab) {
+ this.updateDelayViewerUpdated(true);
- methods: {
- ...mapActions([
- 'closeFile',
- ]),
- clickFile(tab) {
+ if (tab.pending) {
+ this.openPendingTab(tab);
+ } else {
this.$router.push(`/project${tab.url}`);
- },
- mouseOverTab() {
- if (this.tab.changed) {
- this.tabMouseOver = true;
- }
- },
- mouseOutTab() {
- if (this.tab.changed) {
- this.tabMouseOver = false;
- }
- },
+ }
+ },
+ mouseOverTab() {
+ if (this.tab.changed) {
+ this.tabMouseOver = true;
+ }
+ },
+ mouseOutTab() {
+ if (this.tab.changed) {
+ this.tabMouseOver = false;
+ }
},
- };
+ },
+};
</script>
<template>
@@ -66,7 +70,7 @@
<button
type="button"
class="multi-file-tab-close"
- @click.stop.prevent="closeFile(tab.path)"
+ @click.stop.prevent="closeFile(tab)"
:aria-label="closeLabel"
>
<icon
@@ -82,7 +86,9 @@
<div
class="multi-file-tab"
- :class="{active : tab.active }"
+ :class="{
+ active: tab.active
+ }"
:title="tab.url"
>
<file-icon
diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue
index a44e418b2eb..7bd646ba9b0 100644
--- a/app/assets/javascripts/ide/components/repo_tabs.vue
+++ b/app/assets/javascripts/ide/components/repo_tabs.vue
@@ -2,6 +2,7 @@
import { mapActions } from 'vuex';
import RepoTab from './repo_tab.vue';
import EditorMode from './editor_mode_dropdown.vue';
+import router from '../ide_router';
export default {
components: {
@@ -9,6 +10,10 @@ export default {
EditorMode,
},
props: {
+ activeFile: {
+ type: Object,
+ required: true,
+ },
files: {
type: Array,
required: true,
@@ -38,7 +43,18 @@ export default {
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
},
methods: {
- ...mapActions(['updateViewer']),
+ ...mapActions(['updateViewer', 'removePendingTab']),
+ openFileViewer(viewer) {
+ this.updateViewer(viewer);
+
+ if (this.activeFile.pending) {
+ return this.removePendingTab(this.activeFile).then(() => {
+ router.push(`/project${this.activeFile.url}`);
+ });
+ }
+
+ return null;
+ },
},
};
</script>
@@ -60,7 +76,7 @@ export default {
:show-shadow="showShadow"
:has-changes="hasChanges"
:merge-request-id="mergeRequestId"
- @click="updateViewer"
+ @click="openFileViewer"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index be2c12c0487..20983666b4a 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -77,7 +77,11 @@ router.beforeEach((to, from, next) => {
if (to.params[0]) {
const path =
to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
- const treeEntry = store.state.entries[path];
+ const treeEntryKey = Object.keys(store.state.entries).find(
+ key => key === path && !store.state.entries[key].pending,
+ );
+ const treeEntry = store.state.entries[treeEntryKey];
+
if (treeEntry) {
store.dispatch('handleTreeEntryAction', treeEntry);
}
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index d372c2aaad8..e47adae99ed 100644
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -13,12 +13,12 @@ export default class Model {
(this.originalModel = this.monaco.editor.createModel(
this.file.raw,
undefined,
- new this.monaco.Uri(null, null, `original/${this.file.path}`),
+ new this.monaco.Uri(null, null, `original/${this.file.key}`),
)),
(this.model = this.monaco.editor.createModel(
this.content,
undefined,
- new this.monaco.Uri(null, null, this.file.path),
+ new this.monaco.Uri(null, null, this.file.key),
)),
);
if (this.file.mrChange) {
@@ -36,7 +36,7 @@ export default class Model {
this.updateContent = this.updateContent.bind(this);
this.dispose = this.dispose.bind(this);
- eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose);
+ eventHub.$on(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
}
@@ -53,7 +53,7 @@ export default class Model {
}
get path() {
- return this.file.path;
+ return this.file.key;
}
getModel() {
@@ -88,7 +88,7 @@ export default class Model {
this.disposable.dispose();
this.events.clear();
- eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose);
+ eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
}
}
diff --git a/app/assets/javascripts/ide/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js
index 57d5e59a88b..0e7b563b5d6 100644
--- a/app/assets/javascripts/ide/lib/common/model_manager.js
+++ b/app/assets/javascripts/ide/lib/common/model_manager.js
@@ -9,17 +9,17 @@ export default class ModelManager {
this.models = new Map();
}
- hasCachedModel(path) {
- return this.models.has(path);
+ hasCachedModel(key) {
+ return this.models.has(key);
}
- getModel(path) {
- return this.models.get(path);
+ getModel(key) {
+ return this.models.get(key);
}
addModel(file) {
- if (this.hasCachedModel(file.path)) {
- return this.getModel(file.path);
+ if (this.hasCachedModel(file.key)) {
+ return this.getModel(file.key);
}
const model = new Model(this.monaco, file);
@@ -27,7 +27,7 @@ export default class ModelManager {
this.disposable.add(model);
eventHub.$on(
- `editor.update.model.dispose.${file.path}`,
+ `editor.update.model.dispose.${file.key}`,
this.removeCachedModel.bind(this, file),
);
@@ -35,12 +35,9 @@ export default class ModelManager {
}
removeCachedModel(file) {
- this.models.delete(file.path);
+ this.models.delete(file.key);
- eventHub.$off(
- `editor.update.model.dispose.${file.path}`,
- this.removeCachedModel,
- );
+ eventHub.$off(`editor.update.model.dispose.${file.key}`, this.removeCachedModel);
}
dispose() {
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 0a74f4f8925..c6ba679d99c 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -21,7 +21,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
};
export const closeAllFiles = ({ state, dispatch }) => {
- state.openFiles.forEach(file => dispatch('closeFile', file.path));
+ state.openFiles.forEach(file => dispatch('closeFile', file));
};
export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index c21c1a3f5d4..6b034ea1e82 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -6,24 +6,34 @@ import * as types from '../mutation_types';
import router from '../../ide_router';
import { setPageTitle } from '../utils';
-export const closeFile = ({ commit, state, getters, dispatch }, path) => {
- const indexOfClosedFile = state.openFiles.findIndex(f => f.path === path);
- const file = state.entries[path];
+export const closeFile = ({ commit, state, dispatch }, file) => {
+ const path = file.path;
+ const indexOfClosedFile = state.openFiles.findIndex(f => f.key === file.key);
const fileWasActive = file.active;
- commit(types.TOGGLE_FILE_OPEN, path);
- commit(types.SET_FILE_ACTIVE, { path, active: false });
+ if (file.pending) {
+ commit(types.REMOVE_PENDING_TAB, file);
+ } else {
+ commit(types.TOGGLE_FILE_OPEN, path);
+ commit(types.SET_FILE_ACTIVE, { path, active: false });
+ }
if (state.openFiles.length > 0 && fileWasActive) {
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
- const nextFileToOpen = state.entries[state.openFiles[nextIndexToOpen].path];
-
- router.push(`/project${nextFileToOpen.url}`);
+ const nextFileToOpen = state.openFiles[nextIndexToOpen];
+
+ if (nextFileToOpen.pending) {
+ dispatch('updateViewer', 'diff');
+ dispatch('openPendingTab', nextFileToOpen);
+ } else {
+ dispatch('updateDelayViewerUpdated', true);
+ router.push(`/project${nextFileToOpen.url}`);
+ }
} else if (!state.openFiles.length) {
router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
}
- eventHub.$emit(`editor.update.model.dispose.${file.path}`);
+ eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
@@ -151,3 +161,23 @@ export const discardFileChanges = ({ state, commit }, path) => {
eventHub.$emit(`editor.update.model.content.${file.path}`, file.raw);
};
+
+export const openPendingTab = ({ commit, getters, dispatch, state }, file) => {
+ if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') {
+ return false;
+ }
+
+ commit(types.ADD_PENDING_TAB, { file });
+
+ dispatch('scrollToTab');
+
+ router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
+
+ return true;
+};
+
+export const removePendingTab = ({ commit }, file) => {
+ commit(types.REMOVE_PENDING_TAB, file);
+
+ eventHub.$emit(`editor.update.model.dispose.${file.key}`);
+};
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index e06be0a3fe9..ee759bff516 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -49,3 +49,6 @@ export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
export const SET_FILE_MERGE_REQUEST_CHANGE = 'SET_FILE_MERGE_REQUEST_CHANGE';
export const UPDATE_VIEWER = 'UPDATE_VIEWER';
export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
+
+export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
+export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 692fe39b38e..926b6f66d78 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -5,6 +5,14 @@ export default {
Object.assign(state.entries[path], {
active,
});
+
+ if (active && !state.entries[path].pending) {
+ Object.assign(state, {
+ openFiles: state.openFiles.map(f =>
+ Object.assign(f, { active: f.pending ? false : f.active }),
+ ),
+ });
+ }
},
[types.TOGGLE_FILE_OPEN](state, path) {
Object.assign(state.entries[path], {
@@ -12,10 +20,14 @@ export default {
});
if (state.entries[path].opened) {
- state.openFiles.push(state.entries[path]);
+ Object.assign(state, {
+ openFiles: state.openFiles.filter(f => f.path !== path).concat(state.entries[path]),
+ });
} else {
+ const file = state.entries[path];
+
Object.assign(state, {
- openFiles: state.openFiles.filter(f => f.path !== path),
+ openFiles: state.openFiles.filter(f => f.key !== file.key),
});
}
},
@@ -92,4 +104,37 @@ export default {
changed,
});
},
+ [types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
+ const pendingTab = state.openFiles.find(f => f.path === file.path && f.pending);
+ let openFiles = state.openFiles.map(f =>
+ Object.assign(f, { active: f.path === file.path, opened: false }),
+ );
+
+ if (!pendingTab) {
+ const openFile = openFiles.find(f => f.path === file.path);
+
+ openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => {
+ if (!f) return acc;
+
+ if (f.path === file.path) {
+ return acc.concat({
+ ...f,
+ active: true,
+ pending: true,
+ opened: true,
+ key: `${keyPrefix}-${f.key}`,
+ });
+ }
+
+ return acc.concat(f);
+ }, []);
+ }
+
+ Object.assign(state, { openFiles });
+ },
+ [types.REMOVE_PENDING_TAB](state, file) {
+ Object.assign(state, {
+ openFiles: state.openFiles.filter(f => f.key !== file.key),
+ });
+ },
};
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 3389eeeaa2e..63e4de3b17d 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,5 +1,7 @@
export const dataStructure = () => ({
id: '',
+ // Key will contain a mixture of ID and path
+ // it can also contain a prefix `pending-` for files opened in review mode
key: '',
type: '',
projectId: '',