Commit caee9d58 authored by Phil Hughes's avatar Phil Hughes

Add new files & directories in the multi-file editor

Closes #38614
parent 5295f23b
<script>
import newModal from './modal.vue';
export default {
components: {
newModal,
},
data() {
return {
openModal: false,
modalType: '',
};
},
methods: {
createNewItem(type) {
this.modalType = type;
this.toggleModalOpen();
},
toggleModalOpen() {
this.openModal = !this.openModal;
},
},
};
</script>
<template>
<div class="breadcrumb repo-breadcrumb">
<div class="dropdown">
<button
type="button"
class="btn btn-default dropdown-toggle add-to-tree"
data-toggle="dropdown"
data-target=".add-to-tree-dropdown"
>
<i
class="fa fa-plus"
aria-hidden="true"
>
</i>
</button>
</div>
<div class="add-to-tree-dropdown">
<ul class="dropdown-menu">
<li>
<a
href="#"
role="button"
@click.prevent="createNewItem('blob')"
>
{{ __('New file') }}
</a>
</li>
<li>
<a
href="#"
role="button"
@click.prevent="createNewItem('tree')"
>
{{ __('New directory') }}
</a>
</li>
</ul>
</div>
<new-modal
v-if="openModal"
:type="modalType"
@toggle="toggleModalOpen"
/>
</div>
</template>
<script>
import { __ } from '../../../locale';
import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
import RepoStore from '../../stores/repo_store';
import RepoHelper from '../../helpers/repo_helper';
export default {
props: {
type: {
type: String,
required: true,
},
},
data() {
return {
entryName: '',
};
},
components: {
popupDialog,
},
methods: {
createEntryInStore() {
if (this.entryName === '') return;
const fileName = this.type === 'tree' ? '.gitkeep' : this.entryName;
let tree = null;
if (this.type === 'tree') {
tree = RepoHelper.serializeTree({
name: this.entryName,
path: this.entryName,
tempFile: true,
});
RepoStore.files.push(tree);
RepoHelper.setDirectoryOpen(tree, tree.name);
}
const file = RepoHelper.serializeBlob({
name: fileName,
path: tree ? `${tree}/${fileName}` : fileName,
tempFile: true,
});
if (tree) {
RepoStore.addFilesToDirectory(tree, RepoStore.files, [file]);
} else {
RepoStore.addFilesToDirectory(tree, RepoStore.files, [...RepoStore.files, file]);
}
RepoHelper.setFile(file, file);
RepoStore.editMode = true;
RepoStore.toggleBlobView();
this.toggleModalOpen();
},
toggleModalOpen() {
this.$emit('toggle');
},
},
computed: {
modalTitle() {
if (this.type === 'tree') {
return __('Create new directory');
}
return __('Create new file');
},
buttonLabel() {
if (this.type === 'tree') {
return __('Create directory');
}
return __('Create file');
},
formLabelName() {
if (this.type === 'tree') {
return __('Directory name');
}
return __('File name');
},
},
};
</script>
<template>
<popup-dialog
:title="modalTitle"
:primary-button-label="buttonLabel"
kind="success"
@toggle="toggleModalOpen"
@submit="createEntryInStore"
>
<form
class="form-horizontal"
slot="body"
@submit.prevent="createEntryInStore"
>
<fieldset class="form-group append-bottom-0">
<label class="label-light col-sm-3">
{{ formLabelName }}
</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
v-model="entryName"
/>
</div>
</fieldset>
</form>
</popup-dialog>
</template>
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const commitMessage = this.commitMessage; const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({ const actions = this.changedFiles.map(f => ({
action: 'update', action: f.tempFile ? 'create' : 'update',
file_path: f.path, file_path: f.path,
content: f.newContent, content: f.newContent,
})); }));
......
...@@ -16,28 +16,35 @@ const RepoEditor = { ...@@ -16,28 +16,35 @@ const RepoEditor = {
}, },
mounted() { mounted() {
Service.getRaw(this.activeFile.raw_path) if (!this.activeFile.tempFile) {
.then((rawResponse) => { Service.getRaw(this.activeFile.raw_path)
Store.blobRaw = rawResponse.data; .then((rawResponse) => {
Store.activeFile.plain = rawResponse.data; Store.blobRaw = rawResponse.data;
Store.activeFile.plain = rawResponse.data;
const monacoInstance = Helper.monaco.editor.create(this.$el, {
model: null, this.createMonacoInstance();
readOnly: false, })
contextmenu: true, .catch(Helper.loadingError);
scrollBeyondLastLine: false, } else {
}); this.createMonacoInstance();
}
},
Helper.monacoInstance = monacoInstance; methods: {
createMonacoInstance() {
const monacoInstance = Helper.monaco.editor.create(this.$el, {
model: null,
readOnly: false,
contextmenu: true,
scrollBeyondLastLine: false,
});
this.addMonacoEvents(); Helper.monacoInstance = monacoInstance;
this.setupEditor(); this.addMonacoEvents();
})
.catch(Helper.loadingError);
},
methods: { this.setupEditor();
},
setupEditor() { setupEditor() {
this.showHide(); this.showHide();
......
...@@ -11,7 +11,12 @@ const RepoFileButtons = { ...@@ -11,7 +11,12 @@ const RepoFileButtons = {
mixins: [RepoMixin], mixins: [RepoMixin],
computed: { computed: {
showButtons() {
return this.activeFile.raw_path ||
this.activeFile.blame_path ||
this.activeFile.commits_path ||
this.activeFile.permalink;
},
rawDownloadButtonLabel() { rawDownloadButtonLabel() {
return this.binary ? 'Download' : 'Raw'; return this.binary ? 'Download' : 'Raw';
}, },
...@@ -30,7 +35,10 @@ export default RepoFileButtons; ...@@ -30,7 +35,10 @@ export default RepoFileButtons;
</script> </script>
<template> <template>
<div id="repo-file-buttons"> <div
v-if="showButtons"
id="repo-file-buttons"
>
<a <a
:href="activeFile.raw_path" :href="activeFile.raw_path"
target="_blank" target="_blank"
......
...@@ -18,8 +18,8 @@ const RepoTab = { ...@@ -18,8 +18,8 @@ const RepoTab = {
}, },
changedClass() { changedClass() {
const tabChangedObj = { const tabChangedObj = {
'fa-times close-icon': !this.tab.changed, 'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
'fa-circle unsaved-icon': this.tab.changed, 'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
}; };
return tabChangedObj; return tabChangedObj;
}, },
......
...@@ -157,7 +157,7 @@ const RepoHelper = { ...@@ -157,7 +157,7 @@ const RepoHelper = {
}, },
serializeRepoEntity(type, entity, level = 0) { serializeRepoEntity(type, entity, level = 0) {
const { id, url, name, icon, last_commit, tree_url } = entity; const { id, url, name, icon, last_commit, tree_url, path, tempFile } = entity;
return { return {
id, id,
...@@ -165,7 +165,9 @@ const RepoHelper = { ...@@ -165,7 +165,9 @@ const RepoHelper = {
name, name,
url, url,
tree_url, tree_url,
path,
level, level,
tempFile,
icon: `fa-${icon}`, icon: `fa-${icon}`,
files: [], files: [],
loading: false, loading: false,
......
...@@ -5,6 +5,7 @@ import Service from './services/repo_service'; ...@@ -5,6 +5,7 @@ import Service from './services/repo_service';
import Store from './stores/repo_store'; import Store from './stores/repo_store';
import Repo from './components/repo.vue'; import Repo from './components/repo.vue';
import RepoEditButton from './components/repo_edit_button.vue'; import RepoEditButton from './components/repo_edit_button.vue';
import newDropdown from './components/new_dropdown/index.vue';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
function initDropdowns() { function initDropdowns() {
...@@ -62,9 +63,22 @@ function initRepoEditButton(el) { ...@@ -62,9 +63,22 @@ function initRepoEditButton(el) {
}); });
} }
function initNewDropdown(el) {
return new Vue({
el,
components: {
newDropdown,
},
render(createElement) {
return createElement('new-dropdown');
},
});
}
function initRepoBundle() { function initRepoBundle() {
const repo = document.getElementById('repo'); const repo = document.getElementById('repo');
const editButton = document.querySelector('.editable-mode'); const editButton = document.querySelector('.editable-mode');
const newDropdownHolder = document.querySelector('.js-new-dropdown');
setInitialStore(repo.dataset); setInitialStore(repo.dataset);
addEventsForNonVueEls(); addEventsForNonVueEls();
initDropdowns(); initDropdowns();
...@@ -73,6 +87,7 @@ function initRepoBundle() { ...@@ -73,6 +87,7 @@ function initRepoBundle() {
initRepo(repo); initRepo(repo);
initRepoEditButton(editButton); initRepoEditButton(editButton);
initNewDropdown(newDropdownHolder);
} }
$(initRepoBundle); $(initRepoBundle);
......
...@@ -8,7 +8,7 @@ const RepoMixin = { ...@@ -8,7 +8,7 @@ const RepoMixin = {
changedFiles() { changedFiles() {
const changedFileList = this.openedFiles const changedFileList = this.openedFiles
.filter(file => file.changed); .filter(file => file.changed || file.tempFile);
return changedFileList; return changedFileList;
}, },
}, },
......
...@@ -75,7 +75,7 @@ const RepoStore = { ...@@ -75,7 +75,7 @@ const RepoStore = {
RepoStore.blobRaw = file.base64; RepoStore.blobRaw = file.base64;
} else if (file.newContent || file.plain) { } else if (file.newContent || file.plain) {
RepoStore.blobRaw = file.newContent || file.plain; RepoStore.blobRaw = file.newContent || file.plain;
} else { } else if (!file.tempFile) {
Service.getRaw(file.raw_path) Service.getRaw(file.raw_path)
.then((rawResponse) => { .then((rawResponse) => {
RepoStore.blobRaw = rawResponse.data; RepoStore.blobRaw = rawResponse.data;
...@@ -120,6 +120,11 @@ const RepoStore = { ...@@ -120,6 +120,11 @@ const RepoStore = {
return openedFile.path !== file.path; return openedFile.path !== file.path;
}); });
// remove the file from the sidebar if it is a tempFile
if (file.tempFile) {
RepoStore.files = RepoStore.files.filter(f => !(f.tempFile && f.path === file.path));
}
// now activate the right tab based on what you closed. // now activate the right tab based on what you closed.
if (RepoStore.openedFiles.length === 0) { if (RepoStore.openedFiles.length === 0) {
RepoStore.activeFile = {}; RepoStore.activeFile = {};
......
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
}, },
text: { text: {
type: String, type: String,
required: true, required: false,
}, },
kind: { kind: {
type: String, type: String,
...@@ -82,14 +82,15 @@ export default { ...@@ -82,14 +82,15 @@ export default {
type="button" type="button"
class="btn" class="btn"
:class="btnCancelKindClass" :class="btnCancelKindClass"
@click="emitSubmit(false)"> @click="close">
{{closeButtonLabel}} {{ closeButtonLabel }}
</button> </button>
<button type="button" <button
type="button"
class="btn" class="btn"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit(true)"> @click="emitSubmit(true)">
{{primaryButtonLabel}} {{ primaryButtonLabel }}
</button> </button>
</div> </div>
</div> </div>
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path = render 'shared/ref_switcher', destination: 'tree', path: @path
- unless show_new_repo? - if show_new_repo?
.js-new-dropdown
- else
= render 'projects/tree/old_tree_header' = render 'projects/tree/old_tree_header'
.tree-controls .tree-controls
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment