Commit 820cbe59 authored by Natalia Tepluhina's avatar Natalia Tepluhina Committed by Phil Hughes

Implemented move files in IDE

- fixed rename behavior;
- added move behavior
parent a38a8ee7
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue';
import upload from './upload.vue'; import upload from './upload.vue';
import ItemButton from './button.vue'; import ItemButton from './button.vue';
import { modalTypes } from '../../constants'; import { modalTypes } from '../../constants';
...@@ -9,7 +8,6 @@ import { modalTypes } from '../../constants'; ...@@ -9,7 +8,6 @@ import { modalTypes } from '../../constants';
export default { export default {
components: { components: {
icon, icon,
newModal,
upload, upload,
ItemButton, ItemButton,
}, },
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { __ } from '~/locale'; import flash from '~/flash';
import { __, sprintf, s__ } from '~/locale';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import GlModal from '~/vue_shared/components/gl_modal.vue'; import GlModal from '~/vue_shared/components/gl_modal.vue';
import { modalTypes } from '../../constants'; import { modalTypes } from '../../constants';
...@@ -15,15 +16,17 @@ export default { ...@@ -15,15 +16,17 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['entryModal']), ...mapState(['entries', 'entryModal']),
...mapGetters('fileTemplates', ['templateTypes']), ...mapGetters('fileTemplates', ['templateTypes']),
entryName: { entryName: {
get() { get() {
const entryPath = this.entryModal.entry.path;
if (this.entryModal.type === modalTypes.rename) { if (this.entryModal.type === modalTypes.rename) {
return this.name || this.entryModal.entry.name; return this.name || entryPath;
} }
return this.name || (this.entryModal.path !== '' ? `${this.entryModal.path}/` : ''); return this.name || (entryPath ? `${entryPath}/` : '');
}, },
set(val) { set(val) {
this.name = val; this.name = val;
...@@ -62,10 +65,40 @@ export default { ...@@ -62,10 +65,40 @@ export default {
...mapActions(['createTempEntry', 'renameEntry']), ...mapActions(['createTempEntry', 'renameEntry']),
submitForm() { submitForm() {
if (this.entryModal.type === modalTypes.rename) { if (this.entryModal.type === modalTypes.rename) {
if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
flash(
sprintf(s__('The name %{entryName} is already taken in this directory.'), {
entryName: this.entryName,
}),
'alert',
document,
null,
false,
true,
);
} else {
let parentPath = this.entryName.split('/');
const entryName = parentPath.pop();
parentPath = parentPath.join('/');
const createPromise =
parentPath && !this.entries[parentPath]
? this.createTempEntry({ name: parentPath, type: 'tree' })
: Promise.resolve();
createPromise
.then(() =>
this.renameEntry({ this.renameEntry({
path: this.entryModal.entry.path, path: this.entryModal.entry.path,
name: this.entryName, name: entryName,
}); entryPath: null,
parentPath,
}),
)
.catch(() =>
flash(__('Error creating a new path'), 'alert', document, null, false, true),
);
}
} else { } else {
this.createTempEntry({ this.createTempEntry({
name: this.name, name: this.name,
...@@ -82,7 +115,14 @@ export default { ...@@ -82,7 +115,14 @@ export default {
$('#ide-new-entry').modal('toggle'); $('#ide-new-entry').modal('toggle');
}, },
focusInput() { focusInput() {
const name = this.entries[this.entryName] ? this.entries[this.entryName].name : null;
const inputValue = this.$refs.fieldName.value;
this.$refs.fieldName.focus(); this.$refs.fieldName.focus();
if (name) {
this.$refs.fieldName.setSelectionRange(inputValue.indexOf(name), inputValue.length);
}
}, },
closedModal() { closedModal() {
this.name = ''; this.name = '';
......
...@@ -215,15 +215,27 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => { ...@@ -215,15 +215,27 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES); export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath = null }) => { export const renameEntry = (
{ dispatch, commit, state },
{ path, name, entryPath = null, parentPath },
) => {
const entry = state.entries[entryPath || path]; const entry = state.entries[entryPath || path];
commit(types.RENAME_ENTRY, { path, name, entryPath }); commit(types.RENAME_ENTRY, { path, name, entryPath, parentPath });
if (entry.type === 'tree') { if (entry.type === 'tree') {
state.entries[entryPath || path].tree.forEach(f => const slashedParentPath = parentPath ? `${parentPath}/` : '';
dispatch('renameEntry', { path, name, entryPath: f.path }), const targetEntry = entryPath ? entryPath.split('/').pop() : name;
); const newParentPath = `${slashedParentPath}${targetEntry}`;
state.entries[entryPath || path].tree.forEach(f => {
dispatch('renameEntry', {
path,
name,
entryPath: f.path,
parentPath: newParentPath,
});
});
} }
if (!entryPath && !entry.tempFile) { if (!entryPath && !entry.tempFile) {
......
...@@ -206,19 +206,17 @@ export default { ...@@ -206,19 +206,17 @@ export default {
} }
} }
}, },
[types.RENAME_ENTRY](state, { path, name, entryPath = null }) { [types.RENAME_ENTRY](state, { path, name, entryPath = null, parentPath }) {
const oldEntry = state.entries[entryPath || path]; const oldEntry = state.entries[entryPath || path];
const nameRegex = const slashedParentPath = parentPath ? `${parentPath}/` : '';
!entryPath && oldEntry.type === 'blob' const newPath = entryPath
? new RegExp(`${oldEntry.name}$`) ? `${slashedParentPath}${oldEntry.name}`
: new RegExp(`^${path}`); : `${slashedParentPath}${name}`;
const newPath = oldEntry.path.replace(nameRegex, name);
const parentPath = oldEntry.parentPath ? oldEntry.parentPath.replace(nameRegex, name) : '';
state.entries[newPath] = { state.entries[newPath] = {
...oldEntry, ...oldEntry,
id: newPath, id: newPath,
key: `${name}-${oldEntry.type}-${oldEntry.id}`, key: `${newPath}-${oldEntry.type}-${oldEntry.id}`,
path: newPath, path: newPath,
name: entryPath ? oldEntry.name : name, name: entryPath ? oldEntry.name : name,
tempFile: true, tempFile: true,
...@@ -228,6 +226,7 @@ export default { ...@@ -228,6 +226,7 @@ export default {
parentPath, parentPath,
raw: '', raw: '',
}; };
oldEntry.moved = true; oldEntry.moved = true;
oldEntry.movedPath = newPath; oldEntry.movedPath = newPath;
...@@ -256,6 +255,7 @@ export default { ...@@ -256,6 +255,7 @@ export default {
Vue.delete(state.entries, oldEntry.path); Vue.delete(state.entries, oldEntry.path);
} }
}, },
...projectMutations, ...projectMutations,
...mergeRequestMutation, ...mergeRequestMutation,
...fileMutations, ...fileMutations,
......
---
title: Resolve Move files in the Web IDE
merge_request: 25431
author:
type: added
...@@ -3811,6 +3811,9 @@ msgstr "" ...@@ -3811,6 +3811,9 @@ msgstr ""
msgid "Error Tracking" msgid "Error Tracking"
msgstr "" msgstr ""
msgid "Error creating a new path"
msgstr ""
msgid "Error creating epic" msgid "Error creating epic"
msgstr "" msgstr ""
...@@ -9694,6 +9697,9 @@ msgstr "" ...@@ -9694,6 +9697,9 @@ msgstr ""
msgid "The maximum file size allowed is 200KB." msgid "The maximum file size allowed is 200KB."
msgstr "" msgstr ""
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest." msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "" msgstr ""
......
...@@ -18,6 +18,9 @@ describe('new file modal component', () => { ...@@ -18,6 +18,9 @@ describe('new file modal component', () => {
store.state.entryModal = { store.state.entryModal = {
type, type,
path: '', path: '',
entry: {
path: '',
},
}; };
vm = createComponentWithStore(Component, store).$mount(); vm = createComponentWithStore(Component, store).$mount();
...@@ -74,6 +77,7 @@ describe('new file modal component', () => { ...@@ -74,6 +77,7 @@ describe('new file modal component', () => {
entry: { entry: {
name: 'test', name: 'test',
type: 'blob', type: 'blob',
path: 'test-path',
}, },
}; };
...@@ -97,7 +101,7 @@ describe('new file modal component', () => { ...@@ -97,7 +101,7 @@ describe('new file modal component', () => {
describe('entryName', () => { describe('entryName', () => {
it('returns entries name', () => { it('returns entries name', () => {
expect(vm.entryName).toBe('test'); expect(vm.entryName).toBe('test-path');
}); });
it('updated name', () => { it('updated name', () => {
...@@ -107,4 +111,53 @@ describe('new file modal component', () => { ...@@ -107,4 +111,53 @@ describe('new file modal component', () => {
}); });
}); });
}); });
describe('submitForm', () => {
it('throws an error when target entry exists', () => {
const store = createStore();
store.state.entryModal = {
type: 'rename',
path: 'test-path/test',
entry: {
name: 'test',
type: 'blob',
path: 'test-path/test',
},
};
store.state.entries = {
'test-path/test': {
name: 'test',
deleted: false,
},
};
vm = createComponentWithStore(Component, store).$mount();
const flashSpy = spyOnDependency(modal, 'flash');
vm.submitForm();
expect(flashSpy).toHaveBeenCalled();
});
it('calls createTempEntry when target path does not exist', () => {
const store = createStore();
store.state.entryModal = {
type: 'rename',
path: 'test-path/test',
entry: {
name: 'test',
type: 'blob',
path: 'test-path1/test',
},
};
vm = createComponentWithStore(Component, store).$mount();
spyOn(vm, 'createTempEntry').and.callFake(() => Promise.resolve());
vm.submitForm();
expect(vm.createTempEntry).toHaveBeenCalledWith({
name: 'test-path1',
type: 'tree',
});
});
});
}); });
...@@ -499,12 +499,12 @@ describe('Multi-file store actions', () => { ...@@ -499,12 +499,12 @@ describe('Multi-file store actions', () => {
testAction( testAction(
renameEntry, renameEntry,
{ path: 'test', name: 'new-name' }, { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
store.state, store.state,
[ [
{ {
type: types.RENAME_ENTRY, type: types.RENAME_ENTRY,
payload: { path: 'test', name: 'new-name', entryPath: null }, payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
}, },
], ],
[{ type: 'deleteEntry', payload: 'test' }], [{ type: 'deleteEntry', payload: 'test' }],
...@@ -527,17 +527,33 @@ describe('Multi-file store actions', () => { ...@@ -527,17 +527,33 @@ describe('Multi-file store actions', () => {
testAction( testAction(
renameEntry, renameEntry,
{ path: 'test', name: 'new-name' }, { path: 'test', name: 'new-name', parentPath: 'parent-path' },
store.state, store.state,
[ [
{ {
type: types.RENAME_ENTRY, type: types.RENAME_ENTRY,
payload: { path: 'test', name: 'new-name', entryPath: null }, payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
}, },
], ],
[ [
{ type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-1' } }, {
{ type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-2' } }, type: 'renameEntry',
payload: {
path: 'test',
name: 'new-name',
entryPath: 'tree-1',
parentPath: 'parent-path/new-name',
},
},
{
type: 'renameEntry',
payload: {
path: 'test',
name: 'new-name',
entryPath: 'tree-2',
parentPath: 'parent-path/new-name',
},
},
{ type: 'deleteEntry', payload: 'test' }, { type: 'deleteEntry', payload: 'test' },
], ],
done, done,
......
...@@ -298,7 +298,12 @@ describe('Multi-file store mutations', () => { ...@@ -298,7 +298,12 @@ describe('Multi-file store mutations', () => {
}); });
it('creates new renamed entry', () => { it('creates new renamed entry', () => {
mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); mutations.RENAME_ENTRY(localState, {
path: 'oldPath',
name: 'newPath',
entryPath: null,
parentPath: '',
});
expect(localState.entries.newPath).toEqual({ expect(localState.entries.newPath).toEqual({
...localState.entries.oldPath, ...localState.entries.oldPath,
...@@ -335,7 +340,12 @@ describe('Multi-file store mutations', () => { ...@@ -335,7 +340,12 @@ describe('Multi-file store mutations', () => {
...file(), ...file(),
}; };
mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); mutations.RENAME_ENTRY(localState, {
path: 'oldPath',
name: 'newPath',
entryPath: null,
parentPath: 'parentPath',
});
expect(localState.entries.parentPath.tree.length).toBe(1); expect(localState.entries.parentPath.tree.length).toBe(1);
}); });
......
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