Commit 5037f32f authored by Himanshu Kapoor's avatar Himanshu Kapoor

Migrate deprecated modals in Web IDE to GlModal

Migrate deprecated modals (new file modal and discard all changes modal)
in the Web IDE to GlModal from @gitlab/ui
parent cb140072
<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