Commit 82493cba authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '216256-webide-glmodal' into 'master'

WebIDE: Migrate deprecated modals to use GlModal

See merge request gitlab-org/gitlab!30918
parents 150bb361 5037f32f
<script>
import $ from 'jquery';
import { mapActions } from 'vuex';
import { __, sprintf } from '~/locale';
import { GlModal } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import ListItem from './list_item.vue';
......@@ -11,7 +10,7 @@ export default {
components: {
Icon,
ListItem,
GlModal: DeprecatedModal2,
GlModal,
},
directives: {
tooltip,
......@@ -58,7 +57,7 @@ export default {
methods: {
...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
openDiscardModal() {
$('#discard-all-changes').modal('show');
this.$refs.discardAllModal.show();
},
unstageAndDiscardAllChanges() {
this.unstageAllChanges();
......@@ -114,11 +113,12 @@ export default {
</p>
<gl-modal
v-if="!stagedList"
id="discard-all-changes"
:footer-primary-button-text="__('Discard all changes')"
:header-title-text="__('Discard all changes?')"
footer-primary-button-variant="danger"
@submit="unstageAndDiscardAllChanges"
ref="discardAllModal"
ok-variant="danger"
modal-id="discard-all-changes"
:ok-title="__('Discard all changes')"
:title="__('Discard all changes?')"
@ok="unstageAndDiscardAllChanges"
>
{{ $options.discardModalText }}
</gl-modal>
......
......@@ -3,6 +3,7 @@ import Vue from 'vue';
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlDeprecatedButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { modalTypes } from '../constants';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import NewModal from './new_dropdown/modal.vue';
import IdeSidebar from './ide_side_bar.vue';
......@@ -67,7 +68,7 @@ export default {
document.querySelector('.navbar-gitlab').classList.add(`theme-${this.themeName}`);
},
methods: {
...mapActions(['toggleFileFinder', 'openNewEntryModal']),
...mapActions(['toggleFileFinder']),
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
......@@ -81,6 +82,9 @@ export default {
openFile(file) {
this.$router.push(`/project${file.url}`);
},
createNewFile() {
this.$refs.newModal.open(modalTypes.blob);
},
},
};
</script>
......@@ -137,7 +141,7 @@ export default {
variant="success"
:title="__('New file')"
:aria-label="__('New file')"
@click="openNewEntryModal({ type: 'blob' })"
@click="createNewFile()"
>
{{ __('New file') }}
</gl-deprecated-button>
......@@ -159,6 +163,6 @@ export default {
<component :is="rightPaneComponent" v-if="currentProjectId" />
</div>
<ide-status-bar />
<new-modal />
<new-modal ref="newModal" />
</article>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { modalTypes } from '../constants';
import IdeTreeList from './ide_tree_list.vue';
import Upload from './new_dropdown/upload.vue';
import NewEntryButton from './new_dropdown/button.vue';
import NewModal from './new_dropdown/modal.vue';
export default {
components: {
Upload,
IdeTreeList,
NewEntryButton,
NewModal,
},
computed: {
...mapState(['currentBranchId']),
......@@ -26,7 +29,13 @@ export default {
}
},
methods: {
...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry', 'resetOpenFiles']),
...mapActions(['updateViewer', 'createTempEntry', 'resetOpenFiles']),
createNewFile() {
this.$refs.newModal.open(modalTypes.blob);
},
createNewFolder() {
this.$refs.newModal.open(modalTypes.tree);
},
},
};
</script>
......@@ -41,7 +50,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
@click="openNewEntryModal({ type: 'blob' })"
@click="createNewFile()"
/>
<upload
:show-label="false"
......@@ -54,9 +63,10 @@ export default {
:show-label="false"
class="d-flex border-0 p-0"
icon="folder-new"
@click="openNewEntryModal({ type: 'tree' })"
@click="createNewFolder()"
/>
</div>
<new-modal ref="newModal" />
</template>
</ide-tree-list>
</template>
......@@ -4,12 +4,14 @@ import icon from '~/vue_shared/components/icon.vue';
import upload from './upload.vue';
import ItemButton from './button.vue';
import { modalTypes } from '../../constants';
import NewModal from '../new_dropdown/modal.vue';
export default {
components: {
icon,
upload,
ItemButton,
NewModal,
},
props: {
type: {
......@@ -37,9 +39,9 @@ export default {
},
},
methods: {
...mapActions(['createTempEntry', 'openNewEntryModal', 'deleteEntry']),
...mapActions(['createTempEntry', 'deleteEntry']),
createNewItem(type) {
this.openNewEntryModal({ type, path: this.path });
this.$refs.newModal.open(type, this.path);
this.$emit('toggle', false);
},
openDropdown() {
......@@ -109,5 +111,6 @@ export default {
</li>
</ul>
</div>
<new-modal ref="newModal" />
</div>
</template>
<script>
import $ from 'jquery';
import { mapActions, mapState, mapGetters } from 'vuex';
import flash from '~/flash';
import { __, sprintf, s__ } from '~/locale';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import { GlModal } from '@gitlab/ui';
import { modalTypes } from '../../constants';
export default {
components: {
GlModal: DeprecatedModal2,
GlModal,
},
data() {
return {
name: '',
type: modalTypes.blob,
path: '',
};
},
computed: {
...mapState(['entries', 'entryModal']),
...mapState(['entries']),
...mapGetters('fileTemplates', ['templateTypes']),
entryName: {
get() {
const entryPath = this.entryModal.entry.path;
if (this.entryModal.type === modalTypes.rename) {
return this.name || entryPath;
if (this.type === modalTypes.rename) {
return this.name || this.path;
}
return this.name || (entryPath ? `${entryPath}/` : '');
return this.name || (this.path ? `${this.path}/` : '');
},
set(val) {
this.name = val.trim();
},
},
modalTitle() {
if (this.entryModal.type === modalTypes.tree) {
const entry = this.entries[this.path];
if (this.type === modalTypes.tree) {
return __('Create new directory');
} else if (this.entryModal.type === modalTypes.rename) {
return this.entryModal.entry.type === modalTypes.tree
? __('Rename folder')
: __('Rename file');
} else if (this.type === modalTypes.rename) {
return entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
}
return __('Create new file');
},
buttonLabel() {
if (this.entryModal.type === modalTypes.tree) {
const entry = this.entries[this.path];
if (this.type === modalTypes.tree) {
return __('Create directory');
} else if (this.entryModal.type === modalTypes.rename) {
return this.entryModal.entry.type === modalTypes.tree
? __('Rename folder')
: __('Rename file');
} else if (this.type === modalTypes.rename) {
return entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
}
return __('Create file');
},
isCreatingNewFile() {
return this.entryModal.type === 'blob';
return this.type === modalTypes.blob;
},
placeholder() {
return this.isCreatingNewFile ? 'dir/file_name' : 'dir/';
......@@ -64,7 +63,7 @@ export default {
methods: {
...mapActions(['createTempEntry', 'renameEntry']),
submitForm() {
if (this.entryModal.type === modalTypes.rename) {
if (this.type === modalTypes.rename) {
if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
flash(
sprintf(s__('The name "%{name}" is already taken in this directory.'), {
......@@ -82,7 +81,7 @@ export default {
parentPath = parentPath.join('/');
this.renameEntry({
path: this.entryModal.entry.path,
path: this.path,
name: entryName,
parentPath,
});
......@@ -90,17 +89,17 @@ export default {
} else {
this.createTempEntry({
name: this.name,
type: this.entryModal.type,
type: this.type,
});
}
},
createFromTemplate(template) {
this.createTempEntry({
name: template.name,
type: this.entryModal.type,
type: this.type,
});
$('#ide-new-entry').modal('toggle');
this.$refs.modal.toggle();
},
focusInput() {
const name = this.entries[this.entryName] ? this.entries[this.entryName].name : null;
......@@ -112,8 +111,23 @@ export default {
this.$refs.fieldName.setSelectionRange(inputValue.indexOf(name), inputValue.length);
}
},
closedModal() {
resetData() {
this.name = '';
this.path = '';
this.type = modalTypes.blob;
},
open(type = modalTypes.blob, path = '') {
this.type = type;
this.path = path;
this.$refs.modal.show();
// wait for modal to show first
this.$nextTick(() => {
this.focusInput();
});
},
close() {
this.$refs.modal.hide();
},
},
};
......@@ -121,15 +135,15 @@ export default {
<template>
<gl-modal
id="ide-new-entry"
class="qa-new-file-modal"
:header-title-text="modalTitle"
:footer-primary-button-text="buttonLabel"
footer-primary-button-variant="success"
modal-size="lg"
@submit="submitForm"
@open="focusInput"
@closed="closedModal"
ref="modal"
modal-id="ide-new-entry"
modal-class="qa-new-file-modal"
:title="modalTitle"
:ok-title="buttonLabel"
ok-variant="success"
size="lg"
@ok="submitForm"
@hide="resetData"
>
<div class="form-group row">
<label class="label-bold col-form-label col-sm-2"> {{ __('Name') }} </label>
......
......@@ -78,6 +78,7 @@ export const commitItemIconMap = {
export const modalTypes = {
rename: 'rename',
tree: 'tree',
blob: 'blob',
};
export const commitActionTypes = {
......
import $ from 'jquery';
import Vue from 'vue';
import { escape } from 'lodash';
import { __, sprintf } from '~/locale';
......@@ -176,13 +175,6 @@ export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links);
export const setErrorMessage = ({ commit }, errorMessage) =>
commit(types.SET_ERROR_MESSAGE, errorMessage);
export const openNewEntryModal = ({ commit }, { type, path = '' }) => {
commit(types.OPEN_NEW_ENTRY_MODAL, { type, path });
// open the modal manually so we don't mess around with dropdown/rows
$('#ide-new-entry').modal('show');
};
export const deleteEntry = ({ commit, dispatch, state }, path) => {
const entry = state.entries[path];
const { prevPath, prevName, prevParentPath } = entry;
......
......@@ -73,7 +73,6 @@ export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL';
export const DELETE_ENTRY = 'DELETE_ENTRY';
export const RENAME_ENTRY = 'RENAME_ENTRY';
export const REVERT_RENAME_ENTRY = 'REVERT_RENAME_ENTRY';
......
......@@ -192,15 +192,6 @@ export default {
[types.SET_ERROR_MESSAGE](state, errorMessage) {
Object.assign(state, { errorMessage });
},
[types.OPEN_NEW_ENTRY_MODAL](state, { type, path }) {
Object.assign(state, {
entryModal: {
type,
path,
entry: { ...state.entries[path] },
},
});
},
[types.DELETE_ENTRY](state, path) {
const entry = state.entries[path];
const { tempFile = false } = entry;
......
......@@ -40,7 +40,7 @@
h1,
h2,
h3,
h4:not(.modal-title),
h4,
h5,
h6,
code,
......@@ -80,10 +80,6 @@
background-color: $dropdown-hover-background;
}
.modal-body {
color: $gl-text-color;
}
.dropdown-menu-toggle svg,
.dropdown-menu-toggle svg:hover,
.ide-tree-header:not(.ide-pipeline-header) svg,
......
......@@ -23,9 +23,9 @@ describe('new dropdown component', () => {
tree: [],
};
jest.spyOn(vm, 'openNewEntryModal').mockImplementation(() => {});
vm.$mount();
jest.spyOn(vm.$refs.newModal, 'open').mockImplementation(() => {});
});
afterEach(() => {
......@@ -43,16 +43,16 @@ describe('new dropdown component', () => {
});
describe('createNewItem', () => {
it('sets modalType to blob when new file is clicked', () => {
it('opens modal for a blob when new file is clicked', () => {
vm.$el.querySelectorAll('.dropdown-menu button')[0].click();
expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'blob', path: '' });
expect(vm.$refs.newModal.open).toHaveBeenCalledWith('blob', '');
});
it('sets modalType to tree when new directory is clicked', () => {
it('opens modal for a tree when new directory is clicked', () => {
vm.$el.querySelectorAll('.dropdown-menu button')[2].click();
expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'tree', path: '' });
expect(vm.$refs.newModal.open).toHaveBeenCalledWith('tree', '');
});
});
......
......@@ -14,55 +14,48 @@ describe('new file modal component', () => {
vm.$destroy();
});
describe.each(['tree', 'blob'])('%s', type => {
beforeEach(() => {
describe.each`
entryType | modalTitle | btnTitle | showsFileTemplates
${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false}
${'blob'} | ${'Create new file'} | ${'Create file'} | ${true}
`('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => {
beforeEach(done => {
const store = createStore();
store.state.entryModal = {
type,
path: '',
entry: {
path: '',
},
};
vm = createComponentWithStore(Component, store).$mount();
vm.open(entryType);
vm.name = 'testing';
});
it(`sets modal title as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file';
vm.$nextTick(done);
});
expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
afterEach(() => {
vm.close();
});
it(`sets button label as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file';
it(`sets modal title as ${entryType}`, () => {
expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
});
expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
it(`sets button label as ${entryType}`, () => {
expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle);
});
it(`sets form label as ${type}`, () => {
expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name');
it(`sets form label as ${entryType}`, () => {
expect(document.querySelector('.label-bold').textContent.trim()).toBe('Name');
});
it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => {
const templateFilesEl = vm.$el.querySelector('.file-templates');
if (type === 'tree') {
expect(templateFilesEl).toBeNull();
} else {
expect(templateFilesEl instanceof Element).toBeTruthy();
}
it(`shows file templates: ${showsFileTemplates}`, () => {
const templateFilesEl = document.querySelector('.file-templates');
expect(Boolean(templateFilesEl)).toBe(showsFileTemplates);
});
});
describe('rename entry', () => {
beforeEach(() => {
const store = createStore();
store.state.entryModal = {
type: 'rename',
path: '',
entry: {
store.state.entries = {
'test-path': {
name: 'test',
type: 'blob',
path: 'test-path',
......@@ -72,23 +65,29 @@ describe('new file modal component', () => {
vm = createComponentWithStore(Component, store).$mount();
});
['tree', 'blob'].forEach(type => {
it(`renders title and button for renaming ${type}`, done => {
const text = type === 'tree' ? 'folder' : 'file';
vm.$store.state.entryModal.entry.type = type;
it.each`
entryType | modalTitle | btnTitle
${'tree'} | ${'Rename folder'} | ${'Rename folder'}
${'blob'} | ${'Rename file'} | ${'Rename file'}
`(
'renders title and button for renaming $entryType',
({ entryType, modalTitle, btnTitle }, done) => {
vm.$store.state.entries['test-path'].type = entryType;
vm.open('rename', 'test-path');
vm.$nextTick(() => {
expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Rename ${text}`);
expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Rename ${text}`);
expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle);
done();
});
});
});
},
);
describe('entryName', () => {
it('returns entries name', () => {
vm.open('rename', 'test-path');
expect(vm.entryName).toBe('test-path');
});
......@@ -115,15 +114,6 @@ 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',
......@@ -132,6 +122,7 @@ describe('new file modal component', () => {
};
vm = createComponentWithStore(Component, store).$mount();
vm.open('rename', 'test-path/test');
expect(createFlash).not.toHaveBeenCalled();
......
......@@ -339,23 +339,6 @@ describe('Multi-file store mutations', () => {
});
});
describe('OPEN_NEW_ENTRY_MODAL', () => {
it('sets entryModal', () => {
localState.entries.testPath = file();
mutations.OPEN_NEW_ENTRY_MODAL(localState, {
type: 'test',
path: 'testPath',
});
expect(localState.entryModal).toEqual({
type: 'test',
path: 'testPath',
entry: localState.entries.testPath,
});
});
});
describe('RENAME_ENTRY', () => {
beforeEach(() => {
localState.trees = {
......
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