Merge branch 'ide-row-dropdown-design-update' into 'master'
Improvements to new entry dropdowns in Web IDE Closes #44845 See merge request gitlab-org/gitlab-ce!20526
This commit is contained in:
commit
d2c5ac61cd
22 changed files with 363 additions and 253 deletions
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import NewModal from './new_dropdown/modal.vue';
|
||||
import IdeSidebar from './ide_side_bar.vue';
|
||||
import RepoTabs from './repo_tabs.vue';
|
||||
import IdeStatusBar from './ide_status_bar.vue';
|
||||
|
@ -13,6 +14,7 @@ const originalStopCallback = Mousetrap.stopCallback;
|
|||
|
||||
export default {
|
||||
components: {
|
||||
NewModal,
|
||||
IdeSidebar,
|
||||
RepoTabs,
|
||||
IdeStatusBar,
|
||||
|
@ -137,5 +139,6 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
<ide-status-bar :file="activeFile"/>
|
||||
<new-modal />
|
||||
</article>
|
||||
</template>
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
<script>
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import NewDropdown from './new_dropdown/index.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import IdeTreeList from './ide_tree_list.vue';
|
||||
import Upload from './new_dropdown/upload.vue';
|
||||
import NewEntryButton from './new_dropdown/button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NewDropdown,
|
||||
Icon,
|
||||
Upload,
|
||||
IdeTreeList,
|
||||
NewEntryButton,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['currentBranchId']),
|
||||
|
@ -20,23 +24,42 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateViewer']),
|
||||
...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ide-tree-list
|
||||
header-class="d-flex w-100"
|
||||
viewer-type="editor"
|
||||
>
|
||||
<template
|
||||
slot="header"
|
||||
>
|
||||
{{ __('Edit') }}
|
||||
<new-dropdown
|
||||
:project-id="currentProject.name_with_namespace"
|
||||
:branch="currentBranchId"
|
||||
/>
|
||||
<div class="ml-auto d-flex">
|
||||
<new-entry-button
|
||||
:label="__('New file')"
|
||||
:show-label="false"
|
||||
class="d-flex border-0 p-0 mr-3"
|
||||
icon="doc-new"
|
||||
@click="openNewEntryModal({ type: 'blob' })"
|
||||
/>
|
||||
<upload
|
||||
:show-label="false"
|
||||
class="d-flex mr-3"
|
||||
button-css-classes="border-0 p-0"
|
||||
@create="createTempEntry"
|
||||
/>
|
||||
<new-entry-button
|
||||
:label="__('New directory')"
|
||||
:show-label="false"
|
||||
class="d-flex border-0 p-0"
|
||||
icon="folder-new"
|
||||
@click="openNewEntryModal({ type: 'tree' })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ide-tree-list>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:aria-label="label"
|
||||
type="button"
|
||||
@click.stop.prevent="clicked"
|
||||
>
|
||||
<icon
|
||||
:name="icon"
|
||||
:css-classes="iconClasses"
|
||||
/>
|
||||
<template v-if="showLabel">
|
||||
{{ label }}
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
|
@ -3,12 +3,14 @@ import { mapActions } from 'vuex';
|
|||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import newModal from './modal.vue';
|
||||
import upload from './upload.vue';
|
||||
import ItemButton from './button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
newModal,
|
||||
upload,
|
||||
ItemButton,
|
||||
},
|
||||
props: {
|
||||
branch: {
|
||||
|
@ -20,11 +22,13 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
mouseOver: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openModal: false,
|
||||
modalType: '',
|
||||
dropdownOpen: false,
|
||||
};
|
||||
},
|
||||
|
@ -34,17 +38,18 @@ export default {
|
|||
this.$refs.dropdownMenu.scrollIntoView();
|
||||
});
|
||||
},
|
||||
mouseOver() {
|
||||
if (!this.mouseOver) {
|
||||
this.dropdownOpen = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createTempEntry']),
|
||||
...mapActions(['createTempEntry', 'openNewEntryModal']),
|
||||
createNewItem(type) {
|
||||
this.modalType = type;
|
||||
this.openModal = true;
|
||||
this.openNewEntryModal({ type, path: this.path });
|
||||
this.dropdownOpen = false;
|
||||
},
|
||||
hideModal() {
|
||||
this.openModal = false;
|
||||
},
|
||||
openDropdown() {
|
||||
this.dropdownOpen = !this.dropdownOpen;
|
||||
},
|
||||
|
@ -58,23 +63,19 @@ export default {
|
|||
:class="{
|
||||
show: dropdownOpen,
|
||||
}"
|
||||
class="dropdown"
|
||||
class="dropdown d-flex"
|
||||
>
|
||||
<button
|
||||
:aria-label="__('Create new file or directory')"
|
||||
type="button"
|
||||
class="btn btn-sm btn-default dropdown-toggle add-to-tree"
|
||||
aria-label="Create new file or directory"
|
||||
class="rounded border-0 d-flex ide-entry-dropdown-toggle"
|
||||
@click.stop="openDropdown()"
|
||||
>
|
||||
<icon
|
||||
:size="12"
|
||||
name="plus"
|
||||
css-classes="float-left"
|
||||
name="hamburger"
|
||||
/>
|
||||
<icon
|
||||
:size="12"
|
||||
name="arrow-down"
|
||||
css-classes="float-left"
|
||||
/>
|
||||
</button>
|
||||
<ul
|
||||
|
@ -82,39 +83,30 @@ export default {
|
|||
class="dropdown-menu dropdown-menu-right"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.stop.prevent="createNewItem('blob')"
|
||||
>
|
||||
{{ __('New file') }}
|
||||
</a>
|
||||
<item-button
|
||||
:label="__('New file')"
|
||||
class="d-flex"
|
||||
icon="doc-new"
|
||||
icon-classes="mr-2"
|
||||
@click="createNewItem('blob')"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<upload
|
||||
:branch-id="branch"
|
||||
:path="path"
|
||||
@create="createTempEntry"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.stop.prevent="createNewItem('tree')"
|
||||
>
|
||||
{{ __('New directory') }}
|
||||
</a>
|
||||
<item-button
|
||||
:label="__('New directory')"
|
||||
class="d-flex"
|
||||
icon="folder-new"
|
||||
icon-classes="mr-2"
|
||||
@click="createNewItem('tree')"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<new-modal
|
||||
v-if="openModal"
|
||||
:type="modalType"
|
||||
:branch-id="branch"
|
||||
:path="path"
|
||||
@hide="hideModal"
|
||||
@create="createTempEntry"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,78 +1,70 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DeprecatedModal,
|
||||
},
|
||||
props: {
|
||||
branchId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
GlModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
entryName: this.path !== '' ? `${this.path}/` : '',
|
||||
name: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['newEntryModal']),
|
||||
entryName: {
|
||||
get() {
|
||||
return this.name || (this.newEntryModal.path !== '' ? `${this.newEntryModal.path}/` : '');
|
||||
},
|
||||
set(val) {
|
||||
this.name = val;
|
||||
},
|
||||
},
|
||||
modalTitle() {
|
||||
if (this.type === 'tree') {
|
||||
if (this.newEntryModal.type === 'tree') {
|
||||
return __('Create new directory');
|
||||
}
|
||||
|
||||
return __('Create new file');
|
||||
},
|
||||
buttonLabel() {
|
||||
if (this.type === 'tree') {
|
||||
if (this.newEntryModal.type === 'tree') {
|
||||
return __('Create directory');
|
||||
}
|
||||
|
||||
return __('Create file');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fieldName.focus();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createTempEntry']),
|
||||
createEntryInStore() {
|
||||
this.$emit('create', {
|
||||
branchId: this.branchId,
|
||||
name: this.entryName,
|
||||
type: this.type,
|
||||
this.createTempEntry({
|
||||
name: this.name,
|
||||
type: this.newEntryModal.type,
|
||||
});
|
||||
|
||||
this.hideModal();
|
||||
},
|
||||
hideModal() {
|
||||
this.$emit('hide');
|
||||
focusInput() {
|
||||
setTimeout(() => {
|
||||
this.$refs.fieldName.focus();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<deprecated-modal
|
||||
:title="modalTitle"
|
||||
:primary-button-label="buttonLabel"
|
||||
kind="success"
|
||||
@cancel="hideModal"
|
||||
<gl-modal
|
||||
id="ide-new-entry"
|
||||
:header-title-text="modalTitle"
|
||||
:footer-primary-button-text="buttonLabel"
|
||||
footer-primary-button-variant="success"
|
||||
@submit="createEntryInStore"
|
||||
@open="focusInput"
|
||||
>
|
||||
<form
|
||||
slot="body"
|
||||
<div
|
||||
class="form-group row"
|
||||
@submit.prevent="createEntryInStore"
|
||||
>
|
||||
<label class="label-light col-form-label col-sm-3">
|
||||
{{ __('Name') }}
|
||||
|
@ -85,6 +77,6 @@ export default {
|
|||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</deprecated-modal>
|
||||
</div>
|
||||
</gl-modal>
|
||||
</template>
|
||||
|
|
|
@ -1,71 +1,85 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
branchId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fileUpload.addEventListener('change', this.openFile);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.fileUpload.removeEventListener('change', this.openFile);
|
||||
},
|
||||
methods: {
|
||||
createFile(target, file, isText) {
|
||||
const { name } = file;
|
||||
let { result } = target;
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import ItemButton from './button.vue';
|
||||
|
||||
if (!isText) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
result = result.split('base64,')[1];
|
||||
}
|
||||
|
||||
this.$emit('create', {
|
||||
name: `${(this.path ? `${this.path}/` : '')}${name}`,
|
||||
branchId: this.branchId,
|
||||
type: 'blob',
|
||||
content: result,
|
||||
base64: !isText,
|
||||
});
|
||||
},
|
||||
readFile(file) {
|
||||
const reader = new FileReader();
|
||||
const isText = file.type.match(/text.*/) !== null;
|
||||
|
||||
reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
|
||||
|
||||
if (isText) {
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
openFile() {
|
||||
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
|
||||
},
|
||||
startFileUpload() {
|
||||
this.$refs.fileUpload.click();
|
||||
},
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
ItemButton,
|
||||
},
|
||||
props: {
|
||||
path: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
buttonCssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fileUpload.addEventListener('change', this.openFile);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.fileUpload.removeEventListener('change', this.openFile);
|
||||
},
|
||||
methods: {
|
||||
createFile(target, file, isText) {
|
||||
const { name } = file;
|
||||
let { result } = target;
|
||||
|
||||
if (!isText) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
result = result.split('base64,')[1];
|
||||
}
|
||||
|
||||
this.$emit('create', {
|
||||
name: `${this.path ? `${this.path}/` : ''}${name}`,
|
||||
type: 'blob',
|
||||
content: result,
|
||||
base64: !isText,
|
||||
});
|
||||
},
|
||||
readFile(file) {
|
||||
const reader = new FileReader();
|
||||
const isText = file.type.match(/text.*/) !== null;
|
||||
|
||||
reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
|
||||
|
||||
if (isText) {
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
openFile() {
|
||||
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
|
||||
},
|
||||
startFileUpload() {
|
||||
this.$refs.fileUpload.click();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.stop.prevent="startFileUpload"
|
||||
>
|
||||
{{ __('Upload file') }}
|
||||
</a>
|
||||
<item-button
|
||||
:class="buttonCssClasses"
|
||||
:show-label="showLabel"
|
||||
:icon-classes="showLabel ? 'mr-2' : ''"
|
||||
:label="__('Upload file')"
|
||||
class="d-flex"
|
||||
icon="upload"
|
||||
@click="startFileUpload"
|
||||
/>
|
||||
<input
|
||||
id="file-upload"
|
||||
ref="fileUpload"
|
||||
|
|
|
@ -40,6 +40,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mouseOver: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getChangesInFolder',
|
||||
|
@ -142,6 +147,9 @@ export default {
|
|||
hasUrlAtCurrentRoute() {
|
||||
return this.$router.currentRoute.path === `/project${this.file.url}`;
|
||||
},
|
||||
toggleHover(over) {
|
||||
this.mouseOver = over;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -153,6 +161,8 @@ export default {
|
|||
class="file"
|
||||
role="button"
|
||||
@click="clickFile"
|
||||
@mouseover="toggleHover(true)"
|
||||
@mouseout="toggleHover(false)"
|
||||
>
|
||||
<div
|
||||
class="file-name"
|
||||
|
@ -206,6 +216,7 @@ export default {
|
|||
:project-id="file.projectId"
|
||||
:branch="file.branchId"
|
||||
:path="file.path"
|
||||
:mouse-over="mouseOver"
|
||||
class="float-right prepend-left-8"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -52,7 +52,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
|
|||
|
||||
export const createTempEntry = (
|
||||
{ state, commit, dispatch },
|
||||
{ branchId, name, type, content = '', base64 = false },
|
||||
{ name, type, content = '', base64 = false },
|
||||
) =>
|
||||
new Promise(resolve => {
|
||||
const worker = new FilesDecoratorWorker();
|
||||
|
@ -81,7 +81,7 @@ export const createTempEntry = (
|
|||
commit(types.CREATE_TMP_ENTRY, {
|
||||
data,
|
||||
projectId: state.currentProjectId,
|
||||
branchId,
|
||||
branchId: state.currentBranchId,
|
||||
});
|
||||
|
||||
if (type === 'blob') {
|
||||
|
@ -100,7 +100,7 @@ export const createTempEntry = (
|
|||
worker.postMessage({
|
||||
data: [fullName],
|
||||
projectId: state.currentProjectId,
|
||||
branchId,
|
||||
branchId: state.currentBranchId,
|
||||
type,
|
||||
tempFile: true,
|
||||
base64,
|
||||
|
@ -178,6 +178,13 @@ 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 * from './actions/tree';
|
||||
export * from './actions/file';
|
||||
export * from './actions/project';
|
||||
|
|
|
@ -74,3 +74,5 @@ export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
|
|||
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';
|
||||
|
|
|
@ -166,6 +166,11 @@ export default {
|
|||
[types.SET_ERROR_MESSAGE](state, errorMessage) {
|
||||
Object.assign(state, { errorMessage });
|
||||
},
|
||||
[types.OPEN_NEW_ENTRY_MODAL](state, { type, path }) {
|
||||
Object.assign(state, {
|
||||
newEntryModal: { type, path },
|
||||
});
|
||||
},
|
||||
...projectMutations,
|
||||
...mergeRequestMutation,
|
||||
...fileMutations,
|
||||
|
|
|
@ -26,4 +26,8 @@ export default () => ({
|
|||
rightPane: null,
|
||||
links: {},
|
||||
errorMessage: null,
|
||||
newEntryModal: {
|
||||
type: '',
|
||||
path: '',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -45,6 +45,11 @@ export default {
|
|||
emitSubmit(event) {
|
||||
this.$emit('submit', event);
|
||||
},
|
||||
opened({ propertyName }) {
|
||||
if (propertyName === 'opacity') {
|
||||
this.$emit('open');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -55,6 +60,7 @@ export default {
|
|||
class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
@transitionend="opened"
|
||||
>
|
||||
<div
|
||||
:class="modalSizeClass"
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
padding-bottom: $grid-size;
|
||||
|
||||
.file {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
&.file-active {
|
||||
|
@ -716,32 +717,6 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.ide-new-btn {
|
||||
.btn {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dropdown-toggle svg {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
|
||||
label {
|
||||
font-weight: $gl-font-weight-normal;
|
||||
padding: 5px 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ide {
|
||||
overflow: hidden;
|
||||
|
||||
|
@ -1340,3 +1315,24 @@
|
|||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-entry-dropdown-toggle {
|
||||
padding: $gl-padding-4;
|
||||
background-color: $theme-gray-100;
|
||||
|
||||
&:hover {
|
||||
background-color: $theme-gray-200;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $white-normal;
|
||||
background-color: $blue-500;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle {
|
||||
color: $white-normal;
|
||||
background-color: $blue-500;
|
||||
}
|
||||
|
|
5
changelogs/unreleased/ide-row-dropdown-design-update.yml
Normal file
5
changelogs/unreleased/ide-row-dropdown-design-update.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Updated design of new entry dropdown in Web IDE
|
||||
merge_request: 20526
|
||||
author:
|
||||
type: changed
|
|
@ -1720,6 +1720,9 @@ msgstr ""
|
|||
msgid "Create new file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new file or directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new label"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@ describe 'Multi-file editor new directory', :js do
|
|||
end
|
||||
|
||||
it 'creates directory in current directory' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New directory')
|
||||
all('.ide-tree-header button').last.click
|
||||
|
||||
page.within('.modal') do
|
||||
find('.form-control').set('folder name')
|
||||
|
@ -32,9 +30,7 @@ describe 'Multi-file editor new directory', :js do
|
|||
click_button('Create directory')
|
||||
end
|
||||
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New file')
|
||||
first('.ide-tree-header button').click
|
||||
|
||||
page.within('.modal-dialog') do
|
||||
find('.form-control').set('file name')
|
||||
|
|
|
@ -22,9 +22,7 @@ describe 'Multi-file editor new file', :js do
|
|||
end
|
||||
|
||||
it 'creates file in current directory' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New file')
|
||||
first('.ide-tree-header button').click
|
||||
|
||||
page.within('.modal') do
|
||||
find('.form-control').set('file name')
|
||||
|
|
|
@ -24,14 +24,10 @@ describe 'Multi-file editor upload file', :js do
|
|||
end
|
||||
|
||||
it 'uploads text file' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
# make the field visible so capybara can use it
|
||||
execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
|
||||
attach_file('file-upload', txt_file)
|
||||
|
||||
find('.add-to-tree').click
|
||||
|
||||
expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
|
||||
expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
|
||||
end
|
||||
|
|
49
spec/javascripts/ide/components/new_dropdown/button_spec.js
Normal file
49
spec/javascripts/ide/components/new_dropdown/button_spec.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import Button from '~/ide/components/new_dropdown/button.vue';
|
||||
|
||||
describe('IDE new entry dropdown button component', () => {
|
||||
let Component;
|
||||
let vm;
|
||||
|
||||
beforeAll(() => {
|
||||
Component = Vue.extend(Button);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
label: 'Testing',
|
||||
icon: 'doc-new',
|
||||
});
|
||||
|
||||
spyOn(vm, '$emit');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('renders button with label', () => {
|
||||
expect(vm.$el.textContent).toContain('Testing');
|
||||
});
|
||||
|
||||
it('renders icon', () => {
|
||||
expect(vm.$el.querySelector('.ic-doc-new')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('emits click event', () => {
|
||||
vm.$el.click();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('click');
|
||||
});
|
||||
|
||||
it('hides label if showLabel is false', done => {
|
||||
vm.showLabel = false;
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.$el.textContent).not.toContain('Testing');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,6 +13,7 @@ describe('new dropdown component', () => {
|
|||
vm = createComponentWithStore(component, store, {
|
||||
branch: 'master',
|
||||
path: '',
|
||||
mouseOver: false,
|
||||
});
|
||||
|
||||
vm.$store.state.currentProjectId = 'abcproject';
|
||||
|
@ -21,6 +22,8 @@ describe('new dropdown component', () => {
|
|||
tree: [],
|
||||
};
|
||||
|
||||
spyOn(vm, 'openNewEntryModal');
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
|
@ -31,50 +34,23 @@ describe('new dropdown component', () => {
|
|||
});
|
||||
|
||||
it('renders new file, upload and new directory links', () => {
|
||||
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
|
||||
expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
|
||||
expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
|
||||
const buttons = vm.$el.querySelectorAll('.dropdown-menu button');
|
||||
expect(buttons[0].textContent.trim()).toBe('New file');
|
||||
expect(buttons[1].textContent.trim()).toBe('Upload file');
|
||||
expect(buttons[2].textContent.trim()).toBe('New directory');
|
||||
});
|
||||
|
||||
describe('createNewItem', () => {
|
||||
it('sets modalType to blob when new file is clicked', () => {
|
||||
vm.$el.querySelectorAll('a')[0].click();
|
||||
vm.$el.querySelectorAll('.dropdown-menu button')[0].click();
|
||||
|
||||
expect(vm.modalType).toBe('blob');
|
||||
expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'blob', path: '' });
|
||||
});
|
||||
|
||||
it('sets modalType to tree when new directory is clicked', () => {
|
||||
vm.$el.querySelectorAll('a')[2].click();
|
||||
vm.$el.querySelectorAll('.dropdown-menu button')[2].click();
|
||||
|
||||
expect(vm.modalType).toBe('tree');
|
||||
});
|
||||
|
||||
it('opens modal when link is clicked', done => {
|
||||
vm.$el.querySelectorAll('a')[0].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.modal')).not.toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideModal', () => {
|
||||
beforeAll(done => {
|
||||
vm.openModal = true;
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('closes modal after toggling', done => {
|
||||
vm.hideModal();
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vm.$el.querySelector('.modal')).toBeNull();
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'tree', path: '' });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import { createStore } from '~/ide/stores';
|
||||
import modal from '~/ide/components/new_dropdown/modal.vue';
|
||||
import createComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
|
||||
describe('new file modal component', () => {
|
||||
const Component = Vue.extend(modal);
|
||||
|
@ -13,13 +14,15 @@ describe('new file modal component', () => {
|
|||
['tree', 'blob'].forEach(type => {
|
||||
describe(type, () => {
|
||||
beforeEach(() => {
|
||||
vm = createComponent(Component, {
|
||||
const store = createStore();
|
||||
store.state.newEntryModal = {
|
||||
type,
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
});
|
||||
};
|
||||
|
||||
vm.entryName = 'testing';
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
|
||||
vm.name = 'testing';
|
||||
});
|
||||
|
||||
it(`sets modal title as ${type}`, () => {
|
||||
|
@ -40,12 +43,11 @@ describe('new file modal component', () => {
|
|||
|
||||
describe('createEntryInStore', () => {
|
||||
it('$emits create', () => {
|
||||
spyOn(vm, '$emit');
|
||||
spyOn(vm, 'createTempEntry');
|
||||
|
||||
vm.createEntryInStore();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('create', {
|
||||
branchId: 'master',
|
||||
expect(vm.createTempEntry).toHaveBeenCalledWith({
|
||||
name: 'testing',
|
||||
type,
|
||||
});
|
||||
|
@ -53,22 +55,4 @@ describe('new file modal component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('focuses field on mount', () => {
|
||||
document.body.innerHTML += '<div class="js-test"></div>';
|
||||
|
||||
vm = createComponent(
|
||||
Component,
|
||||
{
|
||||
type: 'tree',
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
},
|
||||
'.js-test',
|
||||
);
|
||||
|
||||
expect(document.activeElement).toBe(vm.$refs.fieldName);
|
||||
|
||||
vm.$el.remove();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,6 @@ describe('new dropdown upload', () => {
|
|||
const Component = Vue.extend(upload);
|
||||
|
||||
vm = createComponent(Component, {
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
});
|
||||
|
||||
|
@ -65,7 +64,6 @@ describe('new dropdown upload', () => {
|
|||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('create', {
|
||||
name: file.name,
|
||||
branchId: 'master',
|
||||
type: 'blob',
|
||||
content: target.result,
|
||||
base64: false,
|
||||
|
@ -77,7 +75,6 @@ describe('new dropdown upload', () => {
|
|||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('create', {
|
||||
name: file.name,
|
||||
branchId: 'master',
|
||||
type: 'blob',
|
||||
content: binaryTarget.result.split('base64,')[1],
|
||||
base64: true,
|
||||
|
|
Loading…
Reference in a new issue