Read about our upcoming Code of Conduct on this issue

This instance was upgraded to Heptapod 0.28.1 today

Commit b86c3a73 authored by Filipa Lacerda's avatar Filipa Lacerda
Browse files

Merge branch 'multi-file-editor-vuex' into 'master'

Move multi-file editor store to Vuex

See merge request gitlab-org/gitlab-ce!15046
......@@ -16,6 +16,7 @@ const Api = {
usersPath: '/api/:version/users.json',
commitPath: '/api/:version/projects/:id/repository/commits',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
......
<script>
import { mapState, mapActions } from 'vuex';
import flash, { hideFlash } from '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import eventHub from '../event_hub';
export default {
components: {
loadingIcon,
},
props: {
currentBranch: {
type: String,
required: true,
},
},
data() {
return {
branchName: '',
......@@ -20,11 +14,17 @@
};
},
computed: {
...mapState([
'currentBranch',
]),
btnDisabled() {
return this.loading || this.branchName === '';
},
},
methods: {
...mapActions([
'createNewBranch',
]),
toggleDropdown() {
this.$dropdown.dropdown('toggle');
},
......@@ -38,19 +38,21 @@
hideFlash(flashEl, false);
}
eventHub.$emit('createNewBranch', this.branchName);
},
showErrorMessage(message) {
this.loading = false;
flash(message, 'alert', this.$el);
},
createdNewBranch(newBranchName) {
this.loading = false;
this.branchName = '';
this.createNewBranch(this.branchName)
.then(() => {
this.loading = false;
this.branchName = '';
if (this.dropdownText) {
this.dropdownText.textContent = newBranchName;
}
if (this.dropdownText) {
this.dropdownText.textContent = this.currentBranch;
}
this.toggleDropdown();
})
.catch(res => res.json().then((data) => {
this.loading = false;
flash(data.message, 'alert', this.$el);
}));
},
},
created() {
......@@ -59,15 +61,6 @@
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
eventHub.$on('createNewBranchSuccess', this.createdNewBranch);
eventHub.$on('createNewBranchError', this.showErrorMessage);
eventHub.$on('toggleNewBranchDropdown', this.toggleDropdown);
},
destroyed() {
eventHub.$off('createNewBranchSuccess', this.createdNewBranch);
eventHub.$off('toggleNewBranchDropdown', this.toggleDropdown);
eventHub.$off('createNewBranchError', this.showErrorMessage);
},
};
</script>
......
<script>
import RepoStore from '../../stores/repo_store';
import RepoHelper from '../../helpers/repo_helper';
import eventHub from '../../event_hub';
import { mapState } from 'vuex';
import newModal from './modal.vue';
import upload from './upload.vue';
......@@ -14,9 +12,13 @@
return {
openModal: false,
modalType: '',
currentPath: RepoStore.path,
};
},
computed: {
...mapState([
'path',
]),
},
methods: {
createNewItem(type) {
this.modalType = type;
......@@ -25,19 +27,6 @@
toggleModalOpen() {
this.openModal = !this.openModal;
},
createNewEntryInStore(options, openEditMode = true) {
RepoHelper.createNewEntry(options, openEditMode);
if (options.toggleModal) {
this.toggleModalOpen();
}
},
},
created() {
eventHub.$on('createNewEntry', this.createNewEntryInStore);
},
beforeDestroy() {
eventHub.$off('createNewEntry', this.createNewEntryInStore);
},
};
</script>
......@@ -70,7 +59,7 @@
</li>
<li>
<upload
:current-path="currentPath"
:path="path"
/>
</li>
<li>
......@@ -88,7 +77,7 @@
<new-modal
v-if="openModal"
:type="modalType"
:current-path="currentPath"
:path="path"
@toggle="toggleModalOpen"
/>
</div>
......
<script>
import { mapActions } from 'vuex';
import { __ } from '../../../locale';
import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
import eventHub from '../../event_hub';
export default {
props: {
currentPath: {
type: {
type: String,
required: true,
},
type: {
path: {
type: String,
required: true,
},
},
data() {
return {
entryName: this.currentPath !== '' ? `${this.currentPath}/` : '',
entryName: this.path !== '' ? `${this.path}/` : '',
};
},
components: {
popupDialog,
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
eventHub.$emit('createNewEntry', {
name: this.entryName,
this.createTempEntry({
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
toggleModal: true,
});
this.toggleModalOpen();
},
toggleModalOpen() {
this.$emit('toggle');
......
<script>
import eventHub from '../../event_hub';
import { mapActions } from 'vuex';
export default {
props: {
currentPath: {
path: {
type: String,
required: true,
},
},
methods: {
...mapActions([
'createTempEntry',
]),
createFile(target, file, isText) {
const { name } = file;
const nameWithPath = `${this.currentPath !== '' ? `${this.currentPath}/` : ''}${name}`;
let { result } = target;
if (!isText) {
result = result.split('base64,')[1];
}
eventHub.$emit('createNewEntry', {
name: nameWithPath,
this.createTempEntry({
name,
type: 'blob',
content: result,
toggleModal: false,
base64: !isText,
}, isText);
});
},
readFile(file) {
const reader = new FileReader();
......
<script>
import { mapState, mapGetters } from 'vuex';
import RepoSidebar from './repo_sidebar.vue';
import RepoCommitSection from './repo_commit_section.vue';
import RepoTabs from './repo_tabs.vue';
import RepoFileButtons from './repo_file_buttons.vue';
import RepoPreview from './repo_preview.vue';
import RepoMixin from '../mixins/repo_mixin';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import Store from '../stores/repo_store';
import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
import eventHub from '../event_hub';
import repoEditor from './repo_editor.vue';
export default {
data() {
return Store;
computed: {
...mapState([
'currentBlobView',
]),
...mapGetters([
'isCollapsed',
'changedFiles',
]),
},
mixins: [RepoMixin],
components: {
RepoSidebar,
RepoTabs,
RepoFileButtons,
'repo-editor': MonacoLoaderHelper.repoEditorLoader,
repoEditor,
RepoCommitSection,
PopupDialog,
RepoPreview,
},
created() {
eventHub.$on('createNewBranch', this.createNewBranch);
},
mounted() {
Helper.getContent().catch(Helper.loadingError);
},
destroyed() {
eventHub.$off('createNewBranch', this.createNewBranch);
},
methods: {
getCurrentLocation() {
return location.href;
},
toggleDialogOpen(toggle) {
this.dialog.open = toggle;
},
dialogSubmitted(status) {
this.toggleDialogOpen(false);
this.dialog.status = status;
// remove tmp files
Helper.removeAllTmpFiles('openedFiles');
Helper.removeAllTmpFiles('files');
},
toggleBlobView: Store.toggleBlobView,
createNewBranch(branch) {
Service.createBranch({
branch,
ref: Store.currentBranch,
}).then((res) => {
const newBranchName = res.data.name;
const newUrl = this.getCurrentLocation().replace(Store.currentBranch, newBranchName);
Store.currentBranch = newBranchName;
history.pushState({ key: Helper.key }, '', newUrl);
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => {
if (!this.changedFiles.length) return undefined;
eventHub.$emit('createNewBranchSuccess', newBranchName);
eventHub.$emit('toggleNewBranchDropdown');
}).catch((err) => {
eventHub.$emit('createNewBranchError', err.response.data.message);
Object.assign(e, {
returnValue,
});
},
return returnValue;
};
},
};
</script>
<template>
<div class="repository-view">
<div class="tree-content-holder" :class="{'tree-content-holder-mini' : isMini}">
<div class="tree-content-holder" :class="{'tree-content-holder-mini' : isCollapsed}">
<repo-sidebar/>
<div v-if="isMini"
class="panel-right"
:class="{'edit-mode': editMode}">
<div
v-if="isCollapsed"
class="panel-right"
>
<repo-tabs/>
<component
:is="currentBlobView"
class="blob-viewer-container"/>
/>
<repo-file-buttons/>
</div>
</div>
<repo-commit-section/>
<popup-dialog
v-show="dialog.open"
:primary-button-label="__('Discard changes')"
kind="warning"
:title="__('Are you sure?')"
:text="__('Are you sure you want to discard your changes?')"
@toggle="toggleDialogOpen"
@submit="dialogSubmitted"
/>
<repo-commit-section v-if="changedFiles.length" />
</div>
</template>
<script>
import Flash from '../../flash';
import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin';
import Service from '../services/repo_service';
import { mapGetters, mapState, mapActions } from 'vuex';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import { visitUrl } from '../../lib/utils/url_utility';
import { n__ } from '../../locale';
export default {
mixins: [RepoMixin],
data() {
return Store;
},
components: {
PopupDialog,
},
data() {
return {
showNewBranchDialog: false,
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
};
},
computed: {
showCommitable() {
return this.isCommitable && this.changedFiles.length;
},
branchPaths() {
return this.changedFiles.map(f => f.path);
},
cantCommitYet() {
...mapState([
'currentBranch',
]),
...mapGetters([
'changedFiles',
]),
commitButtonDisabled() {
return !this.commitMessage || this.submitCommitsLoading;
},
filePluralize() {
return this.changedFiles.length > 1 ? 'files' : 'file';
commitButtonText() {
return n__('Commit %d file', 'Commit %d files', this.changedFiles.length);
},
},
methods: {
commitToNewBranch(status) {
if (status) {
this.showNewBranchDialog = false;
this.tryCommit(null, true, true);
} else {
// reset the state
}
},
...mapActions([
'checkCommitStatus',
'commitChanges',
'getTreeData',
]),
makeCommit(newBranch = false) {
const createNewBranch = newBranch || this.startNewMR;
makeCommit(newBranch) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
file_path: f.path,
content: f.newContent,
encoding: f.base64 ? 'base64' : 'text',
}));
const branch = newBranch ? `${this.currentBranch}-${this.currentShortHash}` : this.currentBranch;
const payload = {
branch,
commit_message: commitMessage,
actions,
branch: createNewBranch ? `${this.currentBranch}-${new Date().getTime().toString()}` : this.currentBranch,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
file_path: f.path,
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
})),
start_branch: createNewBranch ? this.currentBranch : undefined,
};
if (newBranch) {
payload.start_branch = this.currentBranch;
}
Service.commitFiles(payload)
this.showNewBranchDialog = false;
this.submitCommitsLoading = true;
this.commitChanges({ payload, newMr: this.startNewMR })
.then(() => {
this.resetCommitState();
if (this.startNewMR) {
this.redirectToNewMr(branch);
} else {
this.redirectToBranch(branch);
}
this.submitCommitsLoading = false;
this.getTreeData();
})
.catch(() => {
Flash('An error occurred while committing your changes');
this.submitCommitsLoading = false;
});
},
tryCommit(e, skipBranchCheck = false, newBranch = false) {
tryCommit() {
this.submitCommitsLoading = true;
if (skipBranchCheck) {
this.makeCommit(newBranch);
} else {
Store.setBranchHash()
.then(() => {
if (Store.branchChanged) {
Store.showNewBranchDialog = true;
return;
}
this.makeCommit(newBranch);
})
.catch(() => {
this.submitCommitsLoading = false;
Flash('An error occurred while committing your changes');
});
}
},
redirectToNewMr(branch) {
visitUrl(this.newMrTemplateUrl.replace('{{source_branch}}', branch));
},
redirectToBranch(branch) {
visitUrl(this.customBranchURL.replace('{{branch}}', branch));
},
resetCommitState() {
this.submitCommitsLoading = false;
this.openedFiles = this.openedFiles.map((file) => {
const f = file;
f.changed = false;
return f;
});
this.changedFiles = [];
this.commitMessage = '';
this.editMode = false;
window.scrollTo(0, 0);
this.checkCommitStatus()
.then((branchChanged) => {
if (branchChanged) {
this.showNewBranchDialog = true;
} else {
this.makeCommit();
}
})
.catch(() => {
this.submitCommitsLoading = false;
});
},
},
};
</script>
<template>
<div
v-if="showCommitable"
id="commit-area">
<div id="commit-area">
<popup-dialog
v-if="showNewBranchDialog"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@submit="commitToNewBranch"
@toggle="showNewBranchDialog = false"
@submit="makeCommit(true)"
/>
<form
class="form-horizontal"
@submit.prevent="tryCommit">
@submit.prevent="tryCommit()">
<fieldset>
<div class="form-group">
<label class="col-md-4 control-label staged-files">
......@@ -145,10 +103,10 @@ export default {
<div class="col-md-6">
<ul class="list-unstyled changed-files">
<li
v-for="branchPath in branchPaths"
:key="branchPath">
v-for="(file, index) in changedFiles"
:key="index">
<span class="help-block">
{{branchPath}}
{{ file.path }}
</span>
</li>
</ul>
......@@ -183,9 +141,8 @@ export default {
</div>
<div class="col-md-offset-4 col-md-6">
<button
ref="submitCommit"
type="submit"
:disabled="cantCommitYet"