Commit acc4599b authored by Natalia Tepluhina's avatar Natalia Tepluhina Committed by Nicolò Maria Mezzopera

Renamed design management app

Renamed design_management_new to design_management

Renamed design_management to design_management_legacy
parent 490145d4
<script>
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { s__ } from '~/locale';
export default {
name: 'DeleteButton',
components: {
GlDeprecatedButton,
GlButton,
GlModal,
},
directives: {
......@@ -25,7 +26,12 @@ export default {
buttonVariant: {
type: String,
required: false,
default: '',
default: 'info',
},
buttonSize: {
type: String,
required: false,
default: 'medium',
},
hasSelectedDesigns: {
type: Boolean,
......@@ -38,27 +44,38 @@ export default {
modalId: uniqueId('design-deletion-confirmation-'),
};
},
modal: {
title: s__('DesignManagement|Delete designs confirmation'),
actionPrimary: {
text: s__('Delete'),
attributes: { variant: 'danger' },
},
actionCancel: {
text: s__('Cancel'),
},
},
};
</script>
<template>
<div>
<div class="gl-display-flex gl-align-items-center gl-h-full">
<gl-modal
:modal-id="modalId"
:title="s__('DesignManagement|Delete designs confirmation')"
:ok-title="s__('DesignManagement|Delete')"
ok-variant="danger"
:title="$options.modal.title"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
@ok="$emit('deleteSelectedDesigns')"
>
<p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p>
</gl-modal>
<gl-deprecated-button
<gl-button
v-gl-modal-directive="modalId"
:variant="buttonVariant"
:disabled="isDeleting || !hasSelectedDesigns"
:size="buttonSize"
:class="buttonClass"
:disabled="isDeleting || !hasSelectedDesigns"
>
<slot></slot>
</gl-deprecated-button>
</gl-button>
</div>
</template>
......@@ -13,13 +13,14 @@ export default {
type: Array,
required: true,
},
},
inject: {
projectPath: {
type: String,
required: true,
default: '',
},
iid: {
type: String,
required: true,
from: 'issueIid',
defaut: '',
},
},
computed: {
......
......@@ -60,7 +60,7 @@ export default {
},
mounted() {
if (this.isNoteLinked) {
this.$refs.anchor.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
}
},
methods: {
......@@ -80,7 +80,7 @@ export default {
</script>
<template>
<timeline-entry-item :id="`note_${noteAnchorId}`" ref="anchor" class="design-note note-form">
<timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form">
<user-avatar-link
:link-href="author.webUrl"
:img-src="author.avatarUrl"
......
......@@ -127,7 +127,7 @@ export default {
params: { id: filename },
query: $route.query,
}"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
>
<div class="card-body p-0 d-flex-center overflow-hidden position-relative">
<div v-if="icon.name" class="design-event position-absolute">
......
......@@ -6,7 +6,6 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import Pagination from './pagination.vue';
import DeleteButton from '../delete_button.vue';
import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
export default {
......@@ -55,19 +54,17 @@ export default {
permissions: {
createDesign: false,
},
projectPath: '',
issueIid: null,
};
},
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
inject: {
projectPath: {
default: '',
},
issueIid: {
default: '',
},
},
apollo: {
permissions: {
query: permissionsQuery,
variables() {
......@@ -102,6 +99,7 @@ export default {
query: $route.query,
}"
:aria-label="s__('DesignManagement|Go back to designs')"
data-testid="close-design"
class="mr-3 text-plain d-flex justify-content-center align-items-center"
>
<icon :size="18" name="close" />
......
<script>
import { GlDeprecatedButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
export default {
components: {
GlDeprecatedButton,
GlButton,
GlLoadingIcon,
},
directives: {
......@@ -30,7 +30,7 @@ export default {
<template>
<div>
<gl-deprecated-button
<gl-button
v-gl-tooltip.hover
:title="
s__(
......@@ -38,12 +38,13 @@ export default {
)
"
:disabled="isSaving"
variant="success"
variant="default"
size="small"
@click="openFileUpload"
>
{{ s__('DesignManagement|Upload designs') }}
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
</gl-deprecated-button>
</gl-button>
<input
ref="fileUpload"
......
......@@ -12,6 +12,12 @@ export default {
GlLink,
GlSprintf,
},
props: {
hasDesigns: {
type: Boolean,
required: true,
},
},
data() {
return {
dragCounter: 0,
......@@ -22,6 +28,12 @@ export default {
dragging() {
return this.dragCounter !== 0;
},
iconStyles() {
return {
size: this.hasDesigns ? 24 : 16,
class: this.hasDesigns ? 'gl-mb-2' : 'gl-mr-3 gl-text-gray-700',
};
},
},
methods: {
isValidUpload(files) {
......@@ -76,25 +88,21 @@ export default {
>
<slot>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
@click="openFileUpload"
>
<div class="d-flex-center flex-column text-center">
<gl-icon name="doc-new" :size="48" class="mb-4" />
<p>
<gl-sprintf
:message="
__(
'%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}.',
)
"
>
<template #lineOne="{ content }"
><span class="d-block">{{ content }}</span>
</template>
<div
:class="{ 'gl-flex-direction-column': hasDesigns }"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center"
data-testid="dropzone-area"
>
<gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" />
<p class="gl-mb-0">
<gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} designs to attach')">
<template #link="{ content }">
<gl-link class="h-100 w-100" @click.stop="openFileUpload">{{ content }}</gl-link>
<gl-link @click.stop="openFileUpload">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
......@@ -117,7 +125,7 @@ export default {
class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
>
<div v-show="!isDragDataValid" class="mw-50 text-center">
<h3>{{ __('Oh no!') }}</h3>
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Oh no!') }}</h3>
<span>{{
__(
'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.',
......@@ -125,7 +133,7 @@ export default {
}}</span>
</div>
<div v-show="isDragDataValid" class="mw-50 text-center">
<h3>{{ __('Incoming!') }}</h3>
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Incoming!') }}</h3>
<span>{{ __('Drop your designs to start your upload.') }}</span>
</div>
</div>
......
<script>
import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
import { findVersionId } from '../../utils/design_management_utils';
export default {
components: {
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlNewDropdown,
GlNewDropdownItem,
},
mixins: [allVersionsMixin],
computed: {
......@@ -50,8 +50,8 @@ export default {
</script>
<template>
<gl-deprecated-dropdown :text="dropdownText" variant="link" class="design-version-dropdown">
<gl-deprecated-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown">
<gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<router-link
class="d-flex js-version-link"
:to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
......@@ -71,6 +71,6 @@ export default {
class="fa fa-check pull-right"
></i>
</router-link>
</gl-deprecated-dropdown-item>
</gl-deprecated-dropdown>
</gl-new-dropdown-item>
</gl-new-dropdown>
</template>
// This application is being moved, please do not touch this files
// Please see https://gitlab.com/gitlab-org/gitlab/-/issues/14744#note_364468096 for details
import $ from 'jquery';
import Vue from 'vue';
import createRouter from './router';
import App from './components/app.vue';
import apolloProvider from './graphql';
import getDesignListQuery from './graphql/queries/get_design_list.query.graphql';
import { DESIGNS_ROUTE_NAME, ROOT_ROUTE_NAME } from './router/constants';
export default () => {
const el = document.querySelector('.js-design-management');
const badge = document.querySelector('.js-designs-count');
const el = document.querySelector('.js-design-management-new');
const { issueIid, projectPath, issuePath } = el.dataset;
const router = createRouter(issuePath);
$('.js-issue-tabs').on('shown.bs.tab', ({ target: { id } }) => {
if (id === 'designs' && router.currentRoute.name === ROOT_ROUTE_NAME) {
router.push({ name: DESIGNS_ROUTE_NAME });
} else if (id === 'discussion') {
router.push({ name: ROOT_ROUTE_NAME });
}
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {
projectPath,
issueIid,
activeDiscussion: {
__typename: 'ActiveDiscussion',
id: null,
......@@ -35,25 +18,14 @@ export default () => {
},
});
apolloProvider.clients.defaultClient
.watchQuery({
query: getDesignListQuery,
variables: {
fullPath: projectPath,
iid: issueIid,
atVersion: null,
},
})
.subscribe(({ data }) => {
if (badge) {
badge.textContent = data.project.issue.designCollection.designs.edges.length;
}
});
return new Vue({
el,
router,
apolloProvider,
provide: {
projectPath,
issueIid,
},
render(createElement) {
return createElement(App);
},
......
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import appDataQuery from '../graphql/queries/app_data.query.graphql';
import { findVersionId } from '../utils/design_management_utils';
export default {
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
},
allVersions: {
query: getDesignListQuery,
variables() {
......@@ -24,6 +15,14 @@ export default {
update: data => data.project.issue.designCollection.versions.edges,
},
},
inject: {
projectPath: {
default: '',
},
issueIid: {
default: '',
},
},
computed: {
hasValidVersion() {
return (
......@@ -55,8 +54,6 @@ export default {
data() {
return {
allVersions: [],
projectPath: '',
issueIid: null,
};
},
};
......@@ -12,7 +12,6 @@ import DesignPresentation from '../../components/design_presentation.vue';
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
import DesignSidebar from '../../components/design_sidebar.vue';
import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
......@@ -62,22 +61,12 @@ export default {
design: {},
comment: '',
annotationCoordinates: null,
projectPath: '',
errorMessage: '',
issueIid: '',
scale: 1,
resolvedDiscussionsExpanded: false,
};
},
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
},
design: {
query: getDesignQuery,
// We want to see cached design version if we have one, and fetch newer version on the background to update discussions
......
<script>
import { GlLoadingIcon, GlDeprecatedButton, GlAlert } from '@gitlab/ui';
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import UploadButton from '../components/upload/button.vue';
......@@ -33,7 +33,7 @@ export default {
components: {
GlLoadingIcon,
GlAlert,
GlDeprecatedButton,
GlButton,
UploadButton,
Design,
DesignDestroyer,
......@@ -96,9 +96,20 @@ export default {
? s__('DesignManagement|Deselect all')
: s__('DesignManagement|Select all');
},
isDesignListEmpty() {
return !this.isSaving && !this.hasDesigns;
},
designDropzoneWrapperClass() {
return this.isDesignListEmpty
? 'col-12'
: 'gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3';
},
},
mounted() {
this.toggleOnPasteListener(this.$route.name);
if (this.$route.path === '/designs') {
this.$el.scrollIntoView();
}
},
methods: {
resetFilesToBeSaved() {
......@@ -238,51 +249,54 @@ export default {
this.onUploadDesign([newFile]);
}
},
toggleOnPasteListener(route) {
if (route === DESIGNS_ROUTE_NAME) {
document.addEventListener('paste', this.onDesignPaste);
} else {
document.removeEventListener('paste', this.onDesignPaste);
}
toggleOnPasteListener() {
document.addEventListener('paste', this.onDesignPaste);
},
toggleOffPasteListener() {
document.removeEventListener('paste', this.onDesignPaste);
},
},
beforeRouteUpdate(to, from, next) {
this.toggleOnPasteListener(to.name);
this.selectedDesigns = [];
next();
},
beforeRouteLeave(to, from, next) {
this.toggleOnPasteListener(to.name);
next();
},
};
</script>
<template>
<div>
<div
data-testid="designs-root"
class="gl-mt-5"
@mouseenter="toggleOnPasteListener"
@mouseleave="toggleOffPasteListener"
>
<header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
<div class="d-flex justify-content-between align-items-center w-100">
<design-version-dropdown />
<div :class="['qa-selector-toolbar', { 'd-flex': hasDesigns, 'd-none': !hasDesigns }]">
<gl-deprecated-button
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full">
<div>
<span class="gl-font-weight-bold gl-mr-3">{{ s__('DesignManagement|Designs') }}</span>
<design-version-dropdown />
</div>
<div v-show="hasDesigns" class="qa-selector-toolbar gl-display-flex gl-align-items-center">
<gl-button
v-if="isLatestVersion"
variant="link"
class="mr-2 js-select-all"
size="small"
class="gl-mr-2 js-select-all"
@click="toggleDesignsSelection"
>{{ selectAllButtonText }}</gl-deprecated-button
>
>{{ selectAllButtonText }}
</gl-button>
<design-destroyer
#default="{ mutate, loading }"
:filenames="selectedDesigns"
:project-path="projectPath"
:iid="issueIid"
@done="onDesignDelete"
@error="onDesignDeleteError"
>
<delete-button
v-if="isLatestVersion"
:is-deleting="loading"
button-class="btn-danger btn-inverted mr-2"
button-variant="danger"
button-class="gl-mr-4"
button-size="small"
:has-selected-designs="hasSelectedDesigns"
@deleteSelectedDesigns="mutate()"
>
......@@ -300,11 +314,17 @@ export default {
{{ __('An error occurred while loading designs. Please try again.') }}
</gl-alert>
<ol v-else class="list-unstyled row">
<li class="col-md-6 col-lg-4 mb-3">
<design-dropzone class="design-list-item" @change="onUploadDesign" />
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
<design-dropzone
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
:has-designs="hasDesigns"
@change="onUploadDesign"
/>
</li>
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3">
<design-dropzone @change="onExistingDesignDropzoneChange($event, design.filename)"
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3">
<design-dropzone
:has-designs="hasDesigns"
@change="onExistingDesignDropzoneChange($event, design.filename)"
><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)"
/></design-dropzone>
......
export const ROOT_ROUTE_NAME = 'root';
export const DESIGNS_ROUTE_NAME = 'designs';
export const DESIGN_ROUTE_NAME = 'design';
import $ from 'jquery';
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
......@@ -16,9 +15,7 @@ export default function createRouter(base) {
});
const pageEl = getPageLayoutElement();
router.beforeEach(({ meta: { el }, name }, _, next) => {
$(`#${el}`).tab('show');
router.beforeEach(({ name }, _, next) => {
// apply a fullscreen layout style in Design View (a.k.a design detail)
if (pageEl) {
if (name === DESIGN_ROUTE_NAME) {
......
import Home from '../pages/index.vue';
import DesignDetail from '../pages/design/index.vue';
import { ROOT_ROUTE_NAME, DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
export default [
{
name: ROOT_ROUTE_NAME,
name: DESIGNS_ROUTE_NAME,
path: '/',
component: Home,
meta: {
el: 'discussion',
},
alias: '/designs',
},
{
name: DESIGNS_ROUTE_NAME,
path: '/designs',
component: Home,
meta: {
el: 'designs',
},
children: [
name: DESIGN_ROUTE_NAME,
path: '/designs/:id',
component: DesignDetail,
beforeEnter(
{
name: DESIGN_ROUTE_NAME,
path: ':id',
component: DesignDetail,
meta: {
el: 'designs',
},
beforeEnter(
{
params: { id },
},
from,
next,
) {
if (typeof id === 'string') {
next();
}
},
props: ({ params: { id } }) => ({ id }),
params: { id },
},
],
_,
next,
) {
if (typeof id === 'string') {
next();
}
},
props: ({ params: { id } }) => ({ id }),
},
];
<script>
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { s__ } from '~/locale';
export default {
name: 'DeleteButton',
components: {
GlButton,
GlDeprecatedButton,
GlModal,
},
directives: {
......@@ -26,12 +25,7 @@ export default {
buttonVariant: {
type: String,
required: false,
default: 'info',
},
buttonSize: {
type: String,
required: false,
default: 'medium',
default: '',
},
hasSelectedDesigns: {
type: Boolean,
......@@ -44,38 +38,27 @@ export default {
modalId: uniqueId('design-deletion-confirmation-'),
};
},
modal: {
title: s__('DesignManagement|Delete designs confirmation'),
actionPrimary: {
text: s__('Delete'),
attributes: { variant: 'danger' },
},
actionCancel: {
text: s__('Cancel'),
},
},
};
</script>
<template>
<div class="gl-display-flex gl-align-items-center gl-h-full">
<div>
<gl-modal
:modal-id="modalId"
:title="$options.modal.title"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
:title="s__('DesignManagement|Delete designs confirmation')"
:ok-title="s__('DesignManagement|Delete')"
ok-variant="danger"
@ok="$emit('deleteSelectedDesigns')"
>
<p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p>
</gl-modal>
<gl-button
<gl-deprecated-button
v-gl-modal-directive="modalId"
:variant="buttonVariant"
:size="buttonSize"
:class="buttonClass"
:disabled="isDeleting || !hasSelectedDesigns"
:class="buttonClass"
>
<slot></slot>
</gl-button>
</gl-deprecated-button>
</div>
</template>
......@@ -13,14 +13,13 @@ export default {
type: Array,
required: true,
},
},
inject: {
projectPath: {
default: '',
type: String,
required: true,
},
iid: {
from: 'issueIid',
defaut: '',
type: String,
required: true,
},
},
computed: {
......
......@@ -60,7 +60,7 @@ export default {
},
mounted() {
if (this.isNoteLinked) {
this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
this.$refs.anchor.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' });
}
},
methods: {
......@@ -80,7 +80,7 @@ export default {
</script>
<template>
<timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form">
<timeline-entry-item :id="`note_${noteAnchorId}`" ref="anchor" class="design-note note-form">
<user-avatar-link
:link-href="author.webUrl"
:img-src="author.avatarUrl"
......
......@@ -127,7 +127,7 @@ export default {
params: { id: filename },
query: $route.query,
}"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
>
<div class="card-body p-0 d-flex-center overflow-hidden position-relative">
<div v-if="icon.name" class="design-event position-absolute">
......
......@@ -6,6 +6,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import Pagination from './pagination.vue';
import DeleteButton from '../delete_button.vue';
import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
export default {
......@@ -54,17 +55,19 @@ export default {
permissions: {
createDesign: false,
},
projectPath: '',
issueIid: null,
};
},
inject: {
projectPath: {
default: '',
},
issueIid: {
default: '',
},
},
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
},
permissions: {
query: permissionsQuery,
variables() {
......@@ -99,7 +102,6 @@ export default {
query: $route.query,
}"
:aria-label="s__('DesignManagement|Go back to designs')"
data-testid="close-design"
class="mr-3 text-plain d-flex justify-content-center align-items-center"
>
<icon :size="18" name="close" />
......
<script>
import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlDeprecatedButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
export default {
components: {
GlButton,
GlDeprecatedButton,
GlLoadingIcon,
},
directives: {
......@@ -30,7 +30,7 @@ export default {
<template>
<div>
<gl-button
<gl-deprecated-button
v-gl-tooltip.hover
:title="
s__(
......@@ -38,13 +38,12 @@ export default {
)
"
:disabled="isSaving"
variant="default"
size="small"
variant="success"
@click="openFileUpload"
>
{{ s__('DesignManagement|Upload designs') }}
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
</gl-button>
</gl-deprecated-button>
<input
ref="fileUpload"
......
......@@ -12,12 +12,6 @@ export default {
GlLink,
GlSprintf,
},
props: {
hasDesigns: {
type: Boolean,
required: true,
},
},
data() {
return {
dragCounter: 0,
......@@ -28,12 +22,6 @@ export default {
dragging() {
return this.dragCounter !== 0;
},
iconStyles() {
return {
size: this.hasDesigns ? 24 : 16,
class: this.hasDesigns ? 'gl-mb-2' : 'gl-mr-3 gl-text-gray-700',
};
},
},
methods: {
isValidUpload(files) {
......@@ -88,21 +76,25 @@ export default {
>
<slot>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
@click="openFileUpload"
>
<div
:class="{ 'gl-flex-direction-column': hasDesigns }"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center"
data-testid="dropzone-area"
>
<gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" />
<p class="gl-mb-0">
<gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} designs to attach')">
<div class="d-flex-center flex-column text-center">
<gl-icon name="doc-new" :size="48" class="mb-4" />
<p>
<gl-sprintf
:message="
__(
'%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}.',
)
"
>
<template #lineOne="{ content }"
><span class="d-block">{{ content }}</span>
</template>
<template #link="{ content }">
<gl-link @click.stop="openFileUpload">
{{ content }}
</gl-link>
<gl-link class="h-100 w-100" @click.stop="openFileUpload">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
......@@ -125,7 +117,7 @@ export default {
class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
>
<div v-show="!isDragDataValid" class="mw-50 text-center">
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Oh no!') }}</h3>
<h3>{{ __('Oh no!') }}</h3>
<span>{{
__(
'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.',
......@@ -133,7 +125,7 @@ export default {
}}</span>
</div>
<div v-show="isDragDataValid" class="mw-50 text-center">
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Incoming!') }}</h3>
<h3>{{ __('Incoming!') }}</h3>
<span>{{ __('Drop your designs to start your upload.') }}</span>
</div>
</div>
......
<script>
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
import { findVersionId } from '../../utils/design_management_utils';
export default {
components: {
GlNewDropdown,
GlNewDropdownItem,
GlDropdown,
GlDropdownItem,
},
mixins: [allVersionsMixin],
computed: {
......@@ -50,8 +50,8 @@ export default {
</script>
<template>
<gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown">
<gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<gl-dropdown :text="dropdownText" variant="link" class="design-version-dropdown">
<gl-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<router-link
class="d-flex js-version-link"
:to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
......@@ -71,6 +71,6 @@ export default {
class="fa fa-check pull-right"
></i>
</router-link>
</gl-new-dropdown-item>
</gl-new-dropdown>
</gl-dropdown-item>
</gl-dropdown>
</template>
// This application is being moved, please do not touch this files
// Please see https://gitlab.com/gitlab-org/gitlab/-/issues/14744#note_364468096 for details
import $ from 'jquery';
import Vue from 'vue';
import createRouter from './router';
import App from './components/app.vue';
import apolloProvider from './graphql';
import getDesignListQuery from './graphql/queries/get_design_list.query.graphql';
import { DESIGNS_ROUTE_NAME, ROOT_ROUTE_NAME } from './router/constants';
export default () => {
const el = document.querySelector('.js-design-management-new');
const el = document.querySelector('.js-design-management');
const badge = document.querySelector('.js-designs-count');
const { issueIid, projectPath, issuePath } = el.dataset;
const router = createRouter(issuePath);
$('.js-issue-tabs').on('shown.bs.tab', ({ target: { id } }) => {
if (id === 'designs' && router.currentRoute.name === ROOT_ROUTE_NAME) {
router.push({ name: DESIGNS_ROUTE_NAME });
} else if (id === 'discussion') {
router.push({ name: ROOT_ROUTE_NAME });
}
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {
projectPath,
issueIid,
activeDiscussion: {
__typename: 'ActiveDiscussion',
id: null,
......@@ -18,14 +35,25 @@ export default () => {
},
});
apolloProvider.clients.defaultClient
.watchQuery({
query: getDesignListQuery,
variables: {
fullPath: projectPath,
iid: issueIid,
atVersion: null,
},
})
.subscribe(({ data }) => {
if (badge) {
badge.textContent = data.project.issue.designCollection.designs.edges.length;
}
});
return new Vue({
el,
router,
apolloProvider,
provide: {
projectPath,
issueIid,
},
render(createElement) {
return createElement(App);
},
......
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import appDataQuery from '../graphql/queries/app_data.query.graphql';
import { findVersionId } from '../utils/design_management_utils';
export default {
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
},
allVersions: {
query: getDesignListQuery,
variables() {
......@@ -15,14 +24,6 @@ export default {
update: data => data.project.issue.designCollection.versions.edges,
},
},
inject: {
projectPath: {
default: '',
},
issueIid: {
default: '',
},
},
computed: {
hasValidVersion() {
return (
......@@ -54,6 +55,8 @@ export default {
data() {
return {
allVersions: [],
projectPath: '',
issueIid: null,
};
},
};
......@@ -12,6 +12,7 @@ import DesignPresentation from '../../components/design_presentation.vue';
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
import DesignSidebar from '../../components/design_sidebar.vue';
import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
......@@ -61,12 +62,22 @@ export default {
design: {},
comment: '',
annotationCoordinates: null,
projectPath: '',
errorMessage: '',
issueIid: '',
scale: 1,
resolvedDiscussionsExpanded: false,
};
},
apollo: {
appData: {
query: appDataQuery,
manual: true,
result({ data: { projectPath, issueIid } }) {
this.projectPath = projectPath;
this.issueIid = issueIid;
},
},
design: {
query: getDesignQuery,
// We want to see cached design version if we have one, and fetch newer version on the background to update discussions
......
<script>
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
import { GlLoadingIcon, GlDeprecatedButton, GlAlert } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import UploadButton from '../components/upload/button.vue';
......@@ -33,7 +33,7 @@ export default {
components: {
GlLoadingIcon,
GlAlert,
GlButton,
GlDeprecatedButton,
UploadButton,
Design,
DesignDestroyer,
......@@ -96,20 +96,9 @@ export default {
? s__('DesignManagement|Deselect all')
: s__('DesignManagement|Select all');
},
isDesignListEmpty() {
return !this.isSaving && !this.hasDesigns;
},
designDropzoneWrapperClass() {
return this.isDesignListEmpty
? 'col-12'
: 'gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3';
},
},
mounted() {
this.toggleOnPasteListener(this.$route.name);
if (this.$route.path === '/designs') {
this.$el.scrollIntoView();
}
},
methods: {
resetFilesToBeSaved() {
......@@ -249,54 +238,51 @@ export default {
this.onUploadDesign([newFile]);
}
},
toggleOnPasteListener() {
document.addEventListener('paste', this.onDesignPaste);
},
toggleOffPasteListener() {
document.removeEventListener('paste', this.onDesignPaste);
toggleOnPasteListener(route) {
if (route === DESIGNS_ROUTE_NAME) {
document.addEventListener('paste', this.onDesignPaste);
} else {
document.removeEventListener('paste', this.onDesignPaste);
}
},
},
beforeRouteUpdate(to, from, next) {
this.toggleOnPasteListener(to.name);
this.selectedDesigns = [];
next();
},
beforeRouteLeave(to, from, next) {
this.toggleOnPasteListener(to.name);
next();
},
};
</script>
<template>
<div
data-testid="designs-root"
class="gl-mt-5"
@mouseenter="toggleOnPasteListener"
@mouseleave="toggleOffPasteListener"
>
<div>
<header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full">
<div>
<span class="gl-font-weight-bold gl-mr-3">{{ s__('DesignManagement|Designs') }}</span>
<design-version-dropdown />
</div>
<div v-show="hasDesigns" class="qa-selector-toolbar gl-display-flex gl-align-items-center">
<gl-button
<div class="d-flex justify-content-between align-items-center w-100">
<design-version-dropdown />
<div :class="['qa-selector-toolbar', { 'd-flex': hasDesigns, 'd-none': !hasDesigns }]">
<gl-deprecated-button
v-if="isLatestVersion"
variant="link"
size="small"
class="gl-mr-2 js-select-all"
class="mr-2 js-select-all"
@click="toggleDesignsSelection"
>{{ selectAllButtonText }}
</gl-button>
>{{ selectAllButtonText }}</gl-deprecated-button
>
<design-destroyer
#default="{ mutate, loading }"
:filenames="selectedDesigns"
:project-path="projectPath"
:iid="issueIid"
@done="onDesignDelete"
@error="onDesignDeleteError"
>
<delete-button
v-if="isLatestVersion"
:is-deleting="loading"
button-variant="danger"
button-class="gl-mr-4"
button-size="small"
button-class="btn-danger btn-inverted mr-2"
:has-selected-designs="hasSelectedDesigns"
@deleteSelectedDesigns="mutate()"
>
......@@ -314,17 +300,11 @@ export default {
{{ __('An error occurred while loading designs. Please try again.') }}
</gl-alert>
<ol v-else class="list-unstyled row">
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
<design-dropzone
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
:has-designs="hasDesigns"
@change="onUploadDesign"
/>
<li class="col-md-6 col-lg-4 mb-3">
<design-dropzone class="design-list-item" @change="onUploadDesign" />
</li>
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3">
<design-dropzone
:has-designs="hasDesigns"
@change="onExistingDesignDropzoneChange($event, design.filename)"
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3">
<design-dropzone @change="onExistingDesignDropzoneChange($event, design.filename)"
><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)"
/></design-dropzone>
......
export const ROOT_ROUTE_NAME = 'root';
export const DESIGNS_ROUTE_NAME = 'designs';
export const DESIGN_ROUTE_NAME = 'design';
import $ from 'jquery';
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
import { DESIGN_ROUTE_NAME } from './constants';
import { getPageLayoutElement } from '~/design_management_new/utils/design_management_utils';
import { getPageLayoutElement } from '~/design_management_legacy/utils/design_management_utils';
import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../constants';
Vue.use(VueRouter);
......@@ -15,7 +16,9 @@ export default function createRouter(base) {
});
const pageEl = getPageLayoutElement();
router.beforeEach(({ name }, _, next) => {
router.beforeEach(({ meta: { el }, name }, _, next) => {
$(`#${el}`).tab('show');
// apply a fullscreen layout style in Design View (a.k.a design detail)
if (pageEl) {
if (name === DESIGN_ROUTE_NAME) {
......
import Home from '../pages/index.vue';
import DesignDetail from '../pages/design/index.vue';
import { ROOT_ROUTE_NAME, DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
export default [
{
name: ROOT_ROUTE_NAME,
path: '/',
component: Home,
meta: {
el: 'discussion',
},
},
{
name: DESIGNS_ROUTE_NAME,
path: '/designs',
component: Home,
meta: {
el: 'designs',
},
children: [
{
name: DESIGN_ROUTE_NAME,
path: ':id',
component: DesignDetail,
meta: {
el: 'designs',
},
beforeEnter(
{
params: { id },
},
from,
next,
) {
if (typeof id === 'string') {
next();
}
},
props: ({ params: { id } }) => ({ id }),
},
],
},
];
import Home from '../pages/index.vue';
import DesignDetail from '../pages/design/index.vue';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants';
export default [
{
name: DESIGNS_ROUTE_NAME,
path: '/',
component: Home,
alias: '/designs',
},
{
name: DESIGN_ROUTE_NAME,
path: '/designs/:id',
component: DesignDetail,
beforeEnter(
{
params: { id },
},
_,
next,
) {
if (typeof id === 'string') {
next();
}
},
props: ({ params: { id } }) => ({ id }),
},
];
......@@ -16,13 +16,13 @@ export default function() {
initSentryErrorStackTraceApp();
initRelatedMergeRequestsApp();
import(/* webpackChunkName: 'design_management' */ '~/design_management')
// This will be removed when we remove the `design_management_moved` feature flag
// See https://gitlab.com/gitlab-org/gitlab/-/issues/223197
import(/* webpackChunkName: 'design_management' */ '~/design_management_legacy')
.then(module => module.default())
.catch(() => {});
// This will be removed when we remove the `design_management_moved` feature flag
// See https://gitlab.com/gitlab-org/gitlab/-/issues/223197
import(/* webpackChunkName: 'design_management' */ '~/design_management_new')
import(/* webpackChunkName: 'design_management' */ '~/design_management')
.then(module => module.default())
.catch(() => {});
......
import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import BatchDeleteButton from '~/design_management/components/delete_button.vue';
describe('Batch delete button component', () => {
let wrapper;
const findButton = () => wrapper.find(GlDeprecatedButton);
const findButton = () => wrapper.find(GlButton);
const findModal = () => wrapper.find(GlModal);
function createComponent(isDeleting = false) {
......
......@@ -61,6 +61,10 @@ describe('Design discussions component', () => {
...data,
};
},
provide: {
projectPath: 'project-path',
issueIid: '1',
},
mocks: {
$apollo,
$route: {
......
......@@ -10,7 +10,7 @@ exports[`Design management list item component when item appears in view after i
exports[`Design management list item component with no notes renders item with correct status icon for creation event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -76,7 +76,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with correct status icon for deletion event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -142,7 +142,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with correct status icon for modification event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -208,7 +208,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with no status icon for none event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -261,7 +261,7 @@ exports[`Design management list item component with no notes renders item with n
exports[`Design management list item component with no notes renders loading spinner when isUploading is true 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -319,7 +319,7 @@ exports[`Design management list item component with no notes renders loading spi
exports[`Design management list item component with notes renders item with multiple comments 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......@@ -389,7 +389,7 @@ exports[`Design management list item component with notes renders item with mult
exports[`Design management list item component with notes renders item with single comment 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
......
......@@ -7,6 +7,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
<a
aria-label="Go back to designs"
class="mr-3 text-plain d-flex justify-content-center align-items-center"
data-testid="close-design"
>
<icon-stub
name="close"
......@@ -49,6 +50,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
<delete-button-stub
buttonclass=""
buttonsize="medium"
buttonvariant="danger"
hasselecteddesigns="true"
>
......
......@@ -4,16 +4,18 @@ exports[`Design management upload button component renders inverted upload desig
<div
isinverted="true"
>
<gl-deprecated-button-stub
size="md"
<gl-button-stub
category="tertiary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
variant="default"
>
Upload designs
<!---->
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"
......@@ -27,11 +29,13 @@ exports[`Design management upload button component renders inverted upload desig
exports[`Design management upload button component renders loading icon 1`] = `
<div>
<gl-deprecated-button-stub
<gl-button-stub
category="tertiary"
disabled="true"
size="md"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
variant="default"
>
Upload designs
......@@ -43,7 +47,7 @@ exports[`Design management upload button component renders loading icon 1`] = `
label="Loading"
size="sm"
/>
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"
......@@ -57,16 +61,18 @@ exports[`Design management upload button component renders loading icon 1`] = `
exports[`Design management upload button component renders upload design button 1`] = `
<div>
<gl-deprecated-button-stub
size="md"
<gl-button-stub
category="tertiary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
variant="default"
>
Upload designs
<!---->
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
<gl-deprecated-dropdown-stub
<gl-new-dropdown-stub
category="tertiary"
class="design-version-dropdown"
headertext=""
issueiid=""
projectpath=""
size="small"
text="Showing Latest Version"
variant="link"
variant="default"
>
<gl-deprecated-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
......@@ -31,8 +40,14 @@ exports[`Design management design version dropdown component renders design vers
class="fa fa-check pull-right"
/>
</router-link-stub>
</gl-deprecated-dropdown-item-stub>
<gl-deprecated-dropdown-item-stub>
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
......@@ -51,19 +66,28 @@ exports[`Design management design version dropdown component renders design vers
<!---->
</router-link-stub>
</gl-deprecated-dropdown-item-stub>
</gl-deprecated-dropdown-stub>
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
`;
exports[`Design management design version dropdown component renders design version list 1`] = `
<gl-deprecated-dropdown-stub
<gl-new-dropdown-stub
category="tertiary"
class="design-version-dropdown"
headertext=""
issueiid=""
projectpath=""
size="small"
text="Showing Latest Version"
variant="link"
variant="default"
>
<gl-deprecated-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
......@@ -86,8 +110,14 @@ exports[`Design management design version dropdown component renders design vers
class="fa fa-check pull-right"
/>
</router-link-stub>
</gl-deprecated-dropdown-item-stub>
<gl-deprecated-dropdown-item-stub>
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
......@@ -106,6 +136,6 @@ exports[`Design management design version dropdown component renders design vers
<!---->
</router-link-stub>
</gl-deprecated-dropdown-item-stub>
</gl-deprecated-dropdown-stub>
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
`;
import { shallowMount } from '@vue/test-utils';
import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue';
import createFlash from '~/flash';
import { GlIcon } from '@gitlab/ui';
jest.mock('~/flash');
......@@ -12,10 +13,16 @@ describe('Design management dropzone component', () => {
};
const findDropzoneCard = () => wrapper.find('.design-dropzone-card');
const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
const findIcon = () => wrapper.find(GlIcon);
function createComponent({ slots = {}, data = {} } = {}) {
function createComponent({ slots = {}, data = {}, props = {} } = {}) {
wrapper = shallowMount(DesignDropzone, {
slots,
propsData: {
hasDesigns: true,
...props,
},
data() {
return data;
},
......@@ -129,4 +136,18 @@ describe('Design management dropzone component', () => {
});
});
});
it('applies correct classes when there are no designs or no design saving loader', () => {
createComponent({ props: { hasDesigns: false } });
expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column');
expect(findIcon().classes()).toEqual(['gl-mr-3', 'gl-text-gray-700']);
expect(findIcon().props('size')).toBe(16);
});
it('applies correct classes when there are designs or design saving loader', () => {
createComponent({ props: { hasDesigns: true } });
expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column');
expect(findIcon().classes()).toEqual(['gl-mb-2']);
expect(findIcon().props('size')).toBe(24);
});
});
import { shallowMount } from '@vue/test-utils';
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
import mockAllVersions from './mock_data/all_versions';
const LATEST_VERSION_ID = 3;
......@@ -75,9 +75,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
'Showing Latest Version',
);
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
......@@ -85,9 +83,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ maxVersions: 1 });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
'Showing Latest Version',
);
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
......@@ -95,7 +91,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(`Showing Version #1`);
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe(`Showing Version #1`);
});
});
......@@ -103,9 +99,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDeprecatedDropdown).attributes('text')).toBe(
'Showing Latest Version',
);
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
......@@ -113,9 +107,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlDeprecatedDropdownItem)).toHaveLength(
wrapper.vm.allVersions.length,
);
expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
});
});
});
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
<div>
<div
class="gl-mt-5"
data-testid="designs-root"
>
<!---->
<div
......@@ -11,17 +14,21 @@ exports[`Design management index page designs does not render toolbar when there
class="list-unstyled row"
>
<li
class="col-md-6 col-lg-4 mb-3"
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class="design-list-item design-list-item-new"
hasdesigns="true"
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-1-name"
......@@ -34,9 +41,11 @@ exports[`Design management index page designs does not render toolbar when there
<!---->
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-2-name"
......@@ -49,9 +58,11 @@ exports[`Design management index page designs does not render toolbar when there
<!---->
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-3-name"
......@@ -73,30 +84,45 @@ exports[`Design management index page designs does not render toolbar when there
`;
exports[`Design management index page designs renders designs list and header with upload button 1`] = `
<div>
<div
class="gl-mt-5"
data-testid="designs-root"
>
<header
class="row-content-block border-top-0 p-2 d-flex"
>
<div
class="d-flex justify-content-between align-items-center w-100"
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"
>
<design-version-dropdown-stub />
<div>
<span
class="gl-font-weight-bold gl-mr-3"
>
Designs
</span>
<design-version-dropdown-stub />
</div>
<div
class="qa-selector-toolbar d-flex"
class="qa-selector-toolbar gl-display-flex gl-align-items-center"
>
<gl-deprecated-button-stub
class="mr-2 js-select-all"
size="md"
<gl-button-stub
category="tertiary"
class="gl-mr-2 js-select-all"
icon=""
size="small"
variant="link"
>
Select all
</gl-deprecated-button-stub>
</gl-button-stub>
<div>
<delete-button-stub
buttonclass="btn-danger btn-inverted mr-2"
buttonvariant=""
buttonclass="gl-mr-4"
buttonsize="small"
buttonvariant="danger"
>
Delete selected
......@@ -117,17 +143,21 @@ exports[`Design management index page designs renders designs list and header wi
class="list-unstyled row"
>
<li
class="col-md-6 col-lg-4 mb-3"
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class="design-list-item design-list-item-new"
hasdesigns="true"
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-1-name"
......@@ -143,9 +173,11 @@ exports[`Design management index page designs renders designs list and header wi
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-2-name"
......@@ -161,9 +193,11 @@ exports[`Design management index page designs renders designs list and header wi
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-3-name"
......@@ -188,7 +222,10 @@ exports[`Design management index page designs renders designs list and header wi
`;
exports[`Design management index page designs renders error 1`] = `
<div>
<div
class="gl-mt-5"
data-testid="designs-root"
>
<!---->
<div
......@@ -216,7 +253,10 @@ exports[`Design management index page designs renders error 1`] = `
`;
exports[`Design management index page designs renders loading icon 1`] = `
<div>
<div
class="gl-mt-5"
data-testid="designs-root"
>
<!---->
<div
......@@ -235,8 +275,11 @@ exports[`Design management index page designs renders loading icon 1`] = `
</div>
`;
exports[`Design management index page when has no designs renders empty text 1`] = `
<div>
exports[`Design management index page when has no designs renders design dropzone 1`] = `
<div
class="gl-mt-5"
data-testid="designs-root"
>
<!---->
<div
......@@ -246,10 +289,11 @@ exports[`Design management index page when has no designs renders empty text 1`]
class="list-unstyled row"
>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-12"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class=""
/>
</li>
......
......@@ -10,7 +10,7 @@ exports[`Design management design index page renders design index 1`] = `
<design-destroyer-stub
filenames="test.jpg"
iid="1"
projectpath=""
project-path="project-path"
/>
<!---->
......@@ -60,7 +60,7 @@ exports[`Design management design index page renders design index 1`] = `
designid="test"
discussion="[object Object]"
discussionwithopenform=""
markdownpreviewpath="//preview_markdown?target_type=Issue"
markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
noteableid="design-id"
/>
......@@ -108,7 +108,7 @@ exports[`Design management design index page renders design index 1`] = `
designid="test"
discussion="[object Object]"
discussionwithopenform=""
markdownpreviewpath="//preview_markdown?target_type=Issue"
markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
noteableid="design-id"
/>
</gl-collapse-stub>
......@@ -140,7 +140,7 @@ exports[`Design management design index page with error GlAlert is rendered in c
<design-destroyer-stub
filenames="test.jpg"
iid="1"
projectpath=""
project-path="project-path"
/>
<div
......
......@@ -95,9 +95,12 @@ describe('Design management design index page', () => {
DesignSidebar,
DesignReplyForm,
},
provide: {
issueIid: '1',
projectPath: 'project-path',
},
data() {
return {
issueIid: '1',
activeDiscussion: {
id: null,
source: null,
......@@ -149,7 +152,7 @@ describe('Design management design index page', () => {
expect(findSidebar().props()).toEqual({
design,
markdownPreviewPath: '//preview_markdown?target_type=Issue',
markdownPreviewPath: '/project-path/preview_markdown?target_type=Issue',
resolvedDiscussionsExpanded: false,
});
});
......
......@@ -25,6 +25,9 @@ const mockPageEl = {
};
jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageEl);
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
const localVue = createLocalVue();
const router = createRouter();
localVue.use(VueRouter);
......@@ -68,6 +71,8 @@ describe('Design management index page', () => {
const findToolbar = () => wrapper.find('.qa-selector-toolbar');
const findDeleteButton = () => wrapper.find(DeleteButton);
const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
const dropzoneClasses = () => findDropzone().classes();
const findDropzoneWrapper = () => wrapper.find('[data-testid="design-dropzone-wrapper"]');
const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
function createComponent({
......@@ -92,19 +97,23 @@ describe('Design management index page', () => {
};
wrapper = shallowMount(Index, {
data() {
return {
designs,
allVersions,
permissions: {
createDesign,
},
};
},
mocks: { $apollo },
localVue,
router,
stubs: { DesignDestroyer, ApolloMutation, ...stubs },
attachToDocument: true,
});
wrapper.setData({
designs,
allVersions,
issueIid: '1',
permissions: {
createDesign,
provide: {
projectPath: 'project-path',
issueIid: '1',
},
});
}
......@@ -117,9 +126,7 @@ describe('Design management index page', () => {
it('renders loading icon', () => {
createComponent({ loading: true });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.element).toMatchSnapshot();
});
expect(wrapper.element).toMatchSnapshot();
});
it('renders error', () => {
......@@ -135,25 +142,35 @@ describe('Design management index page', () => {
it('renders a toolbar with buttons when there are designs', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
return wrapper.vm.$nextTick().then(() => {
expect(findToolbar().exists()).toBe(true);
});
expect(findToolbar().exists()).toBe(true);
});
it('renders designs list and header with upload button', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.element).toMatchSnapshot();
});
expect(wrapper.element).toMatchSnapshot();
});
it('does not render toolbar when there is no permission', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.element).toMatchSnapshot();
});
expect(wrapper.element).toMatchSnapshot();
});
it('has correct classes applied to design dropzone', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
expect(dropzoneClasses()).toContain('design-list-item');
expect(dropzoneClasses()).toContain('design-list-item-new');
});
it('has correct classes applied to dropzone wrapper', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
expect(findDropzoneWrapper().classes()).toEqual([
'gl-flex-direction-column',
'col-md-6',
'col-lg-3',
'gl-mb-3',
]);
});
});
......@@ -162,11 +179,20 @@ describe('Design management index page', () => {
createComponent();
});
it('renders empty text', () =>
it('renders design dropzone', () =>
wrapper.vm.$nextTick().then(() => {
expect(wrapper.element).toMatchSnapshot();
}));
it('has correct classes applied to design dropzone', () => {
expect(dropzoneClasses()).not.toContain('design-list-item');
expect(dropzoneClasses()).not.toContain('design-list-item-new');
});
it('has correct classes applied to dropzone wrapper', () => {
expect(findDropzoneWrapper().classes()).toEqual(['col-12']);
});
it('does not render a toolbar with buttons', () =>
wrapper.vm.$nextTick().then(() => {
expect(findToolbar().exists()).toBe(false);
......@@ -185,7 +211,7 @@ describe('Design management index page', () => {
mutation: uploadDesignQuery,
variables: {
files: [{ name: 'test' }],
projectPath: '',
projectPath: 'project-path',
iid: '1',
},
optimisticResponse: {
......@@ -231,12 +257,18 @@ describe('Design management index page', () => {
},
};
return wrapper.vm.$nextTick().then(() => {
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
expect(wrapper.vm.isSaving).toBeTruthy();
});
return wrapper.vm
.$nextTick()
.then(() => {
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
expect(wrapper.vm.isSaving).toBeTruthy();
})
.then(() => {
expect(dropzoneClasses()).toContain('design-list-item');
expect(dropzoneClasses()).toContain('design-list-item-new');
});
});
it('sets isSaving', () => {
......@@ -384,8 +416,7 @@ describe('Design management index page', () => {
it('renders toolbar buttons', () => {
expect(findToolbar().exists()).toBe(true);
expect(findToolbar().classes()).toContain('d-flex');
expect(findToolbar().classes()).not.toContain('d-none');
expect(findToolbar().isVisible()).toBe(true);
});
it('adds two designs to selected designs when their checkboxes are checked', () => {
......@@ -442,9 +473,9 @@ describe('Design management index page', () => {
});
});
it('on latest version when has no designs does not render toolbar buttons', () => {
it('on latest version when has no designs toolbar buttons are invisible', () => {
createComponent({ designs: [], allVersions: [mockVersion] });
expect(findToolbar().exists()).toBe(false);
expect(findToolbar().isVisible()).toBe(false);
});
describe('on non-latest version', () => {
......@@ -535,9 +566,18 @@ describe('Design management index page', () => {
it('ensures fullscreen layout is not applied', () => {
createComponent(true);
wrapper.vm.$router.push('/designs');
wrapper.vm.$router.push('/');
expect(mockPageEl.classList.remove).toHaveBeenCalledTimes(1);
expect(mockPageEl.classList.remove).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
});
it('should trigger a scrollIntoView method if designs route is detected', () => {
router.replace({
path: '/designs',
});
createComponent(true);
expect(scrollIntoViewMock).toHaveBeenCalled();
});
});
});
......@@ -5,11 +5,7 @@ import App from '~/design_management/components/app.vue';
import Designs from '~/design_management/pages/index.vue';
import DesignDetail from '~/design_management/pages/design/index.vue';
import createRouter from '~/design_management/router';
import {
ROOT_ROUTE_NAME,
DESIGNS_ROUTE_NAME,
DESIGN_ROUTE_NAME,
} from '~/design_management/router/constants';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
import '~/commons/bootstrap';
function factory(routeArg) {
......@@ -49,7 +45,7 @@ describe('Design management router', () => {
window.location.hash = '';
});
describe.each([['/'], [{ name: ROOT_ROUTE_NAME }]])('root route', routeArg => {
describe.each([['/'], [{ name: DESIGNS_ROUTE_NAME }]])('root route', routeArg => {
it('pushes home component', () => {
const wrapper = factory(routeArg);
......@@ -57,14 +53,6 @@ describe('Design management router', () => {
});
});
describe.each([['/designs'], [{ name: DESIGNS_ROUTE_NAME }]])('designs route', routeArg => {
it('pushes designs root component', () => {
const wrapper = factory(routeArg);
expect(wrapper.find(Designs).exists()).toBe(true);
});
});
describe.each([['/designs/1'], [{ name: DESIGN_ROUTE_NAME, params: { id: '1' } }]])(
'designs detail route',
routeArg => {
......
import { shallowMount } from '@vue/test-utils';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import BatchDeleteButton from '~/design_management_new/components/delete_button.vue';
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import BatchDeleteButton from '~/design_management_legacy/components/delete_button.vue';
describe('Batch delete button component', () => {
let wrapper;
const findButton = () => wrapper.find(GlButton);
const findButton = () => wrapper.find(GlDeprecatedButton);
const findModal = () => wrapper.find(GlModal);
function createComponent(isDeleting = false) {
......
import { shallowMount } from '@vue/test-utils';
import DesignNotePin from '~/design_management_new/components/design_note_pin.vue';
import DesignNotePin from '~/design_management_legacy/components/design_note_pin.vue';
describe('Design note pin component', () => {
let wrapper;
......
import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import notes from '../../mock_data/notes';
import DesignDiscussion from '~/design_management_new/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
import createNoteMutation from '~/design_management_new/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import DesignDiscussion from '~/design_management_legacy/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management_legacy/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
import createNoteMutation from '~/design_management_legacy/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management_legacy/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
import ToggleRepliesWidget from '~/design_management_legacy/components/design_notes/toggle_replies_widget.vue';
const discussion = {
id: '0',
......@@ -61,10 +61,6 @@ describe('Design discussions component', () => {
...data,
};
},
provide: {
projectPath: 'project-path',
issueIid: '1',
},
mocks: {
$apollo,
$route: {
......
import { shallowMount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
import DesignNote from '~/design_management_legacy/components/design_notes/design_note.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
const scrollIntoViewMock = jest.fn();
const note = {
......
import { mount } from '@vue/test-utils';
import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
import DesignReplyForm from '~/design_management_legacy/components/design_notes/design_reply_form.vue';
const showModal = jest.fn();
......
import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
import ToggleRepliesWidget from '~/design_management_legacy/components/design_notes/toggle_replies_widget.vue';
import notes from '../../mock_data/notes';
describe('Toggle replies widget component', () => {
......
import { mount } from '@vue/test-utils';
import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
import updateActiveDiscussion from '~/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql';
import DesignOverlay from '~/design_management_legacy/components/design_overlay.vue';
import updateActiveDiscussion from '~/design_management_legacy/graphql/mutations/update_active_discussion.mutation.graphql';
import notes from '../mock_data/notes';
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management_new/constants';
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management_legacy/constants';
const mutate = jest.fn(() => Promise.resolve());
......
import { shallowMount } from '@vue/test-utils';
import DesignPresentation from '~/design_management_new/components/design_presentation.vue';
import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
import DesignPresentation from '~/design_management_legacy/components/design_presentation.vue';
import DesignOverlay from '~/design_management_legacy/components/design_overlay.vue';
const mockOverlayData = {
overlayDimensions: {
......
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