summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/design_management
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/design_management')
-rw-r--r--app/assets/javascripts/design_management/components/delete_button.vue65
-rw-r--r--app/assets/javascripts/design_management/components/design_destroyer.vue9
-rw-r--r--app/assets/javascripts/design_management/components/design_note_pin.vue8
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue4
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue4
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/toggle_replies_widget.vue6
-rw-r--r--app/assets/javascripts/design_management/components/design_sidebar.vue10
-rw-r--r--app/assets/javascripts/design_management/components/list/item.vue6
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/design_navigation.vue (renamed from app/assets/javascripts/design_management/components/toolbar/pagination.vue)27
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/index.vue78
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/pagination_button.vue48
-rw-r--r--app/assets/javascripts/design_management/components/upload/button.vue11
-rw-r--r--app/assets/javascripts/design_management/components/upload/design_dropzone.vue53
-rw-r--r--app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue65
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql6
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/move_design.mutation.graphql18
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql8
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql4
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql22
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql12
-rw-r--r--app/assets/javascripts/design_management/index.js38
-rw-r--r--app/assets/javascripts/design_management/mixins/all_designs.js15
-rw-r--r--app/assets/javascripts/design_management/mixins/all_versions.js25
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue13
-rw-r--r--app/assets/javascripts/design_management/pages/index.vue164
-rw-r--r--app/assets/javascripts/design_management/router/constants.js1
-rw-r--r--app/assets/javascripts/design_management/router/index.js5
-rw-r--r--app/assets/javascripts/design_management/router/routes.js47
-rw-r--r--app/assets/javascripts/design_management/utils/cache_update.js90
-rw-r--r--app/assets/javascripts/design_management/utils/design_management_utils.js53
-rw-r--r--app/assets/javascripts/design_management/utils/error_messages.js6
31 files changed, 496 insertions, 425 deletions
diff --git a/app/assets/javascripts/design_management/components/delete_button.vue b/app/assets/javascripts/design_management/components/delete_button.vue
index 1fd902c9ed7..37686dd5a46 100644
--- a/app/assets/javascripts/design_management/components/delete_button.vue
+++ b/app/assets/javascripts/design_management/components/delete_button.vue
@@ -1,11 +1,12 @@
<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,40 +26,78 @@ export default {
buttonVariant: {
type: String,
required: false,
- default: '',
+ default: 'info',
+ },
+ buttonCategory: {
+ type: String,
+ required: false,
+ default: 'primary',
+ },
+ buttonIcon: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ buttonSize: {
+ type: String,
+ required: false,
+ default: 'medium',
},
hasSelectedDesigns: {
type: Boolean,
required: false,
default: true,
},
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
modalId: uniqueId('design-deletion-confirmation-'),
};
},
+ modal: {
+ title: s__('DesignManagement|Are you sure you want to archive the selected designs?'),
+ actionPrimary: {
+ text: s__('DesignManagement|Archive designs'),
+ attributes: { variant: 'warning' },
+ },
+ actionCancel: {
+ text: __('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>
+ <p>
+ {{
+ s__(
+ 'DesignManagement|Archived designs will still be available in previous versions of the design collection.',
+ )
+ }}
+ </p>
</gl-modal>
- <gl-deprecated-button
+ <gl-button
v-gl-modal-directive="modalId"
:variant="buttonVariant"
- :disabled="isDeleting || !hasSelectedDesigns"
+ :category="buttonCategory"
+ :size="buttonSize"
:class="buttonClass"
- >
- <slot></slot>
- </gl-deprecated-button>
+ :loading="loading"
+ :icon="buttonIcon"
+ :disabled="isDeleting || !hasSelectedDesigns"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/design_management/components/design_destroyer.vue b/app/assets/javascripts/design_management/components/design_destroyer.vue
index 62460ca551c..7ae569216f0 100644
--- a/app/assets/javascripts/design_management/components/design_destroyer.vue
+++ b/app/assets/javascripts/design_management/components/design_destroyer.vue
@@ -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: {
diff --git a/app/assets/javascripts/design_management/components/design_note_pin.vue b/app/assets/javascripts/design_management/components/design_note_pin.vue
index 0811397fbad..2b5e62c2870 100644
--- a/app/assets/javascripts/design_management/components/design_note_pin.vue
+++ b/app/assets/javascripts/design_management/components/design_note_pin.vue
@@ -1,11 +1,11 @@
<script>
+import { GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'DesignNotePin',
components: {
- Icon,
+ GlIcon,
},
props: {
position: {
@@ -47,13 +47,13 @@ export default {
'btn-transparent comment-indicator': isNewNote,
'js-image-badge badge badge-pill': !isNewNote,
}"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0"
type="button"
@mousedown="$emit('mousedown', $event)"
@mouseup="$emit('mouseup', $event)"
@click="$emit('click', $event)"
>
- <icon v-if="isNewNote" name="image-comment-dark" />
+ <gl-icon v-if="isNewNote" name="image-comment-dark" :size="24" />
<template v-else>
{{ label }}
</template>
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
index 4aaf43e3a5b..6a20517eed7 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
@@ -230,10 +230,10 @@ export default {
</button>
</template>
<template v-if="discussion.resolved" #resolvedStatus>
- <p class="gl-text-gray-700 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
+ <p class="gl-text-gray-500 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
{{ __('Resolved by') }}
<gl-link
- class="gl-text-gray-700 gl-text-decoration-none gl-font-sm link-inherit-color"
+ class="gl-text-gray-500 gl-text-decoration-none gl-font-sm link-inherit-color"
:href="discussion.resolvedBy.webUrl"
target="_blank"
>{{ discussion.resolvedBy.name }}</gl-link
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index b1f3a43a66d..172e61920ef 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -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"
diff --git a/app/assets/javascripts/design_management/components/design_notes/toggle_replies_widget.vue b/app/assets/javascripts/design_management/components/design_notes/toggle_replies_widget.vue
index 46c73e3eea8..2e366282de3 100644
--- a/app/assets/javascripts/design_management/components/design_notes/toggle_replies_widget.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/toggle_replies_widget.vue
@@ -52,18 +52,18 @@ export default {
{{ toggleText }}
</gl-button>
<template v-if="collapsed">
- <span class="gl-text-gray-700">{{ __('Last reply by') }}</span>
+ <span class="gl-text-gray-500">{{ __('Last reply by') }}</span>
<gl-link
:href="lastReply.author.webUrl"
target="_blank"
- class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-ml-2 gl-mr-2"
+ class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-ml-2 gl-mr-2"
>
{{ lastReply.author.name }}
</gl-link>
<time-ago-tooltip
:time="lastReply.createdAt"
tooltip-placement="bottom"
- class="gl-text-gray-700"
+ class="gl-text-gray-500"
/>
</template>
</li>
diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue
index 333ad2557e8..e5a3590877e 100644
--- a/app/assets/javascripts/design_management/components/design_sidebar.vue
+++ b/app/assets/javascripts/design_management/components/design_sidebar.vue
@@ -1,8 +1,8 @@
<script>
-import { s__ } from '~/locale';
import Cookies from 'js-cookie';
-import { parseBoolean } from '~/lib/utils/common_utils';
import { GlCollapse, GlButton, GlPopover } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { parseBoolean } from '~/lib/utils/common_utils';
import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
import { extractDiscussions, extractParticipants } from '../utils/design_management_utils';
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
@@ -48,7 +48,7 @@ export default {
};
},
discussionParticipants() {
- return extractParticipants(this.issue.participants);
+ return extractParticipants(this.issue.participants.nodes);
},
resolvedDiscussions() {
return this.discussions.filter(discussion => discussion.resolved);
@@ -94,7 +94,7 @@ export default {
{{ issue.title }}
</h2>
<a
- class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ class="gl-text-gray-400 gl-text-decoration-none gl-mb-6 gl-display-block"
:href="issue.webUrl"
>{{ issue.webPath }}</a
>
@@ -132,7 +132,7 @@ export default {
data-testid="resolved-comments"
:icon="resolvedCommentsToggleIcon"
variant="link"
- class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-mb-4"
+ class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
@click="$emit('toggleResolvedComments')"
>{{ $options.resolveCommentsToggleText }} ({{ resolvedDiscussions.length }})
</gl-button>
diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue
index eaa641d85d6..292b6e09055 100644
--- a/app/assets/javascripts/design_management/components/list/item.vue
+++ b/app/assets/javascripts/design_management/components/list/item.vue
@@ -74,7 +74,7 @@ export default {
deletion: {
name: 'file-deletion-solid',
classes: 'text-danger-500',
- tooltip: __('Deleted in this version'),
+ tooltip: __('Archived in this version'),
},
};
@@ -127,10 +127,10 @@ 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">
+ <div v-if="icon.name" data-testid="designEvent" class="design-event position-absolute">
<span :title="icon.tooltip" :aria-label="icon.tooltip">
<icon :name="icon.name" :size="18" :class="icon.classes" />
</span>
diff --git a/app/assets/javascripts/design_management/components/toolbar/pagination.vue b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
index bf62a8f66a6..afca8ed2c6f 100644
--- a/app/assets/javascripts/design_management/components/toolbar/pagination.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
@@ -1,14 +1,15 @@
<script>
/* global Mousetrap */
import 'mousetrap';
+import { GlButton, GlButtonGroup } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
-import PaginationButton from './pagination_button.vue';
import allDesignsMixin from '../../mixins/all_designs';
import { DESIGN_ROUTE_NAME } from '../../router/constants';
export default {
components: {
- PaginationButton,
+ GlButton,
+ GlButtonGroup,
},
mixins: [allDesignsMixin],
props: {
@@ -31,12 +32,12 @@ export default {
});
},
previousDesign() {
- if (!this.designsCount) return null;
+ if (this.currentIndex === 0) return null;
return this.designs[this.currentIndex - 1];
},
nextDesign() {
- if (!this.designsCount) return null;
+ if (this.currentIndex + 1 === this.designsCount) return null;
return this.designs[this.currentIndex + 1];
},
@@ -65,19 +66,21 @@ export default {
<template>
<div v-if="designsCount" class="d-flex align-items-center">
{{ paginationText }}
- <div class="btn-group ml-3 mr-3">
- <pagination-button
- :design="previousDesign"
+ <gl-button-group class="ml-3 mr-3">
+ <gl-button
+ :disabled="!previousDesign"
:title="s__('DesignManagement|Go to previous design')"
- icon-name="angle-left"
+ icon="angle-left"
class="js-previous-design"
+ @click="navigateToDesign(previousDesign)"
/>
- <pagination-button
- :design="nextDesign"
+ <gl-button
+ :disabled="!nextDesign"
:title="s__('DesignManagement|Go to next design')"
- icon-name="angle-right"
+ icon="angle-right"
class="js-next-design"
+ @click="navigateToDesign(nextDesign)"
/>
- </div>
+ </gl-button-group>
</div>
</template>
diff --git a/app/assets/javascripts/design_management/components/toolbar/index.vue b/app/assets/javascripts/design_management/components/toolbar/index.vue
index b998dfc47b8..a1cb57123ab 100644
--- a/app/assets/javascripts/design_management/components/toolbar/index.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/index.vue
@@ -1,20 +1,18 @@
<script>
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton, GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import Pagination from './pagination.vue';
+import DesignNavigation from './design_navigation.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 {
components: {
- Icon,
- Pagination,
+ GlButton,
+ GlIcon,
+ DesignNavigation,
DeleteButton,
- GlDeprecatedButton,
},
mixins: [timeagoMixin],
props: {
@@ -55,19 +53,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() {
@@ -95,32 +91,36 @@ export default {
</script>
<template>
- <header class="d-flex p-2 bg-white align-items-center js-design-header">
- <router-link
- :to="{
- name: $options.DESIGNS_ROUTE_NAME,
- query: $route.query,
- }"
- :aria-label="s__('DesignManagement|Go back to designs')"
- class="mr-3 text-plain d-flex justify-content-center align-items-center"
- >
- <icon :size="18" name="close" />
- </router-link>
- <div class="overflow-hidden d-flex align-items-center">
- <h2 class="m-0 str-truncated-100 gl-font-base">{{ filename }}</h2>
- <small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small>
+ <header
+ class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-bg-white gl-py-4 gl-pl-4 js-design-header"
+ >
+ <div class="gl-display-flex gl-align-items-center">
+ <router-link
+ :to="{
+ name: $options.DESIGNS_ROUTE_NAME,
+ query: $route.query,
+ }"
+ :aria-label="s__('DesignManagement|Go back to designs')"
+ data-testid="close-design"
+ class="gl-mr-5 gl-display-flex gl-align-items-center gl-justify-content-center text-plain"
+ >
+ <gl-icon name="close" />
+ </router-link>
+ <div class="overflow-hidden d-flex align-items-center">
+ <h2 class="m-0 str-truncated-100 gl-font-base">{{ filename }}</h2>
+ <small v-if="updatedAt" class="text-secondary">{{ updatedText }}</small>
+ </div>
</div>
- <pagination :id="id" class="ml-auto flex-shrink-0" />
- <gl-deprecated-button :href="image" class="mr-2">
- <icon :size="18" name="download" />
- </gl-deprecated-button>
+ <design-navigation :id="id" class="ml-auto flex-shrink-0" />
+ <gl-button :href="image" icon="download" />
<delete-button
v-if="isLatestVersion && canDeleteDesign"
+ class="gl-ml-3"
:is-deleting="isDeleting"
- button-variant="danger"
+ button-variant="warning"
+ button-icon="archive"
+ button-category="secondary"
@deleteSelectedDesigns="$emit('delete')"
- >
- <icon :size="18" name="remove" />
- </delete-button>
+ />
</header>
</template>
diff --git a/app/assets/javascripts/design_management/components/toolbar/pagination_button.vue b/app/assets/javascripts/design_management/components/toolbar/pagination_button.vue
deleted file mode 100644
index f00ecefca01..00000000000
--- a/app/assets/javascripts/design_management/components/toolbar/pagination_button.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<script>
-import Icon from '~/vue_shared/components/icon.vue';
-import { DESIGN_ROUTE_NAME } from '../../router/constants';
-
-export default {
- components: {
- Icon,
- },
- props: {
- design: {
- type: Object,
- required: false,
- default: null,
- },
- title: {
- type: String,
- required: true,
- },
- iconName: {
- type: String,
- required: true,
- },
- },
- computed: {
- designLink() {
- if (!this.design) return {};
-
- return {
- name: DESIGN_ROUTE_NAME,
- params: { id: this.design.filename },
- query: this.$route.query,
- };
- },
- },
-};
-</script>
-
-<template>
- <router-link
- :to="designLink"
- :disabled="!design"
- :class="{ disabled: !design }"
- :aria-label="title"
- class="btn btn-default"
- >
- <icon :name="iconName" />
- </router-link>
-</template>
diff --git a/app/assets/javascripts/design_management/components/upload/button.vue b/app/assets/javascripts/design_management/components/upload/button.vue
index 68555104a3c..c76041c74a8 100644
--- a/app/assets/javascripts/design_management/components/upload/button.vue
+++ b/app/assets/javascripts/design_management/components/upload/button.vue
@@ -1,10 +1,10 @@
<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"
diff --git a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
index 33261134c15..7254b7cd16a 100644
--- a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
+++ b/app/assets/javascripts/design_management/components/upload/design_dropzone.vue
@@ -1,6 +1,6 @@
<script>
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
import { isValidDesignFile } from '../../utils/design_management_utils';
@@ -12,6 +12,17 @@ export default {
GlLink,
GlSprintf,
},
+ props: {
+ hasDesigns: {
+ type: Boolean,
+ required: true,
+ },
+ isDraggingDesign: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
data() {
return {
dragCounter: 0,
@@ -22,6 +33,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-500',
+ };
+ },
},
methods: {
isValidUpload(files) {
@@ -76,25 +93,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>
@@ -113,11 +126,11 @@ export default {
</slot>
<transition name="design-dropzone-fade">
<div
- v-show="dragging"
+ v-show="dragging && !isDraggingDesign"
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 +138,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>
diff --git a/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue b/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
index 993eac6f37f..a03982cb91b 100644
--- a/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
+++ b/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
@@ -1,13 +1,14 @@
<script>
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlNewDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
import { findVersionId } from '../../utils/design_management_utils';
export default {
components: {
- GlDropdown,
- GlDropdownItem,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlSprintf,
},
mixins: [allVersionsMixin],
computed: {
@@ -18,7 +19,7 @@ export default {
if (!this.queryVersion) return 0;
const idx = this.allVersions.findIndex(
- version => this.findVersionId(version.node.id) === this.queryVersion,
+ version => this.findVersionId(version.id) === this.queryVersion,
);
// if the currentVersionId isn't a valid version (i.e. not in allVersions)
@@ -29,48 +30,52 @@ export default {
if (this.queryVersion) return this.queryVersion;
const currentVersion = this.allVersions[this.currentVersionIdx];
- return this.findVersionId(currentVersion.node.id);
+ return this.findVersionId(currentVersion.id);
},
dropdownText() {
if (this.isLatestVersion) {
- return __('Showing Latest Version');
+ return __('Showing latest version');
}
// allVersions is sorted in reverse chronological order (latest first)
const currentVersionNumber = this.allVersions.length - this.currentVersionIdx;
- return sprintf(__('Showing Version #%{versionNumber}'), {
+ return sprintf(__('Showing version #%{versionNumber}'), {
versionNumber: currentVersionNumber,
});
},
},
methods: {
findVersionId,
+ routeToVersion(versionId) {
+ this.$router.push({
+ path: this.$route.path,
+ query: { version: this.findVersionId(versionId) },
+ });
+ },
+ versionText(versionId) {
+ if (this.findVersionId(versionId) === this.latestVersionId) {
+ return __('Version %{versionNumber} (latest)');
+ }
+ return __('Version %{versionNumber}');
+ },
},
};
</script>
<template>
- <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) } }"
- >
- <div class="flex-grow-1 ml-2">
- <div>
- <strong
- >{{ __('Version') }} {{ allVersions.length - index }}
- <span v-if="findVersionId(version.node.id) === latestVersionId"
- >({{ __('latest') }})</span
- >
- </strong>
- </div>
- </div>
- <i
- v-if="findVersionId(version.node.id) === currentVersionId"
- class="fa fa-check pull-right"
- ></i>
- </router-link>
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-new-dropdown :text="dropdownText" size="small">
+ <gl-new-dropdown-item
+ v-for="(version, index) in allVersions"
+ :key="version.id"
+ :is-check-item="true"
+ :is-checked="findVersionId(version.id) === currentVersionId"
+ @click="routeToVersion(version.id)"
+ >
+ <gl-sprintf :message="versionText(version.id)">
+ <template #versionNumber>
+ {{ allVersions.length - index }}
+ </template>
+ </gl-sprintf>
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
</template>
diff --git a/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql
index c8ade328120..0b8400ac040 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/create_image_diff_note.mutation.graphql
@@ -8,10 +8,8 @@ mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
id
replyId
notes {
- edges {
- node {
- ...DesignNote
- }
+ nodes {
+ ...DesignNote
}
}
}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/move_design.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/move_design.mutation.graphql
new file mode 100644
index 00000000000..144b2729999
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/move_design.mutation.graphql
@@ -0,0 +1,18 @@
+#import "../fragments/design_list.fragment.graphql"
+
+mutation DesignManagementMove(
+ $id: DesignManagementDesignID!
+ $previous: DesignManagementDesignID
+ $next: DesignManagementDesignID
+) {
+ designManagementMove(input: { id: $id, previous: $previous, next: $next }) {
+ designCollection {
+ designs {
+ nodes {
+ ...DesignListItem
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
index d694e6558a0..84aeb374351 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
@@ -5,11 +5,9 @@ mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
designs {
...DesignItem
versions {
- edges {
- node {
- id
- sha
- }
+ nodes {
+ id
+ sha
}
}
}
diff --git a/app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql b/app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql
deleted file mode 100644
index e1269761206..00000000000
--- a/app/assets/javascripts/design_management/graphql/queries/app_data.query.graphql
+++ /dev/null
@@ -1,4 +0,0 @@
-query projectFullPath {
- projectPath @client
- issueIid @client
-}
diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
index 07a9af55787..ab987dda525 100644
--- a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
@@ -7,19 +7,15 @@ query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [Stri
issue(iid: $iid) {
designCollection {
designs(atVersion: $atVersion, filenames: $filenames) {
- edges {
- node {
- ...DesignItem
- issue {
- title
- webPath
- webUrl
- participants {
- edges {
- node {
- ...Author
- }
- }
+ nodes {
+ ...DesignItem
+ issue {
+ title
+ webPath
+ webUrl
+ participants {
+ nodes {
+ ...Author
}
}
}
diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
index 121a50555b3..96efa8e8242 100644
--- a/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql
@@ -7,17 +7,13 @@ query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
issue(iid: $iid) {
designCollection {
designs(atVersion: $atVersion) {
- edges {
- node {
- ...DesignListItem
- }
+ nodes {
+ ...DesignListItem
}
}
versions {
- edges {
- node {
- ...VersionListItem
- }
+ nodes {
+ ...VersionListItem
}
}
}
diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js
index 1fc5779515a..20c9cacf83f 100644
--- a/app/assets/javascripts/design_management/index.js
+++ b/app/assets/javascripts/design_management/index.js
@@ -1,32 +1,15 @@
-// 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);
},
diff --git a/app/assets/javascripts/design_management/mixins/all_designs.js b/app/assets/javascripts/design_management/mixins/all_designs.js
index f7d6551c46c..0c2858bb14b 100644
--- a/app/assets/javascripts/design_management/mixins/all_designs.js
+++ b/app/assets/javascripts/design_management/mixins/all_designs.js
@@ -1,8 +1,7 @@
import { propertyOf } from 'lodash';
-import createFlash from '~/flash';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__ } from '~/locale';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
-import { extractNodes } from '../utils/design_management_utils';
import allVersionsMixin from './all_versions';
import { DESIGNS_ROUTE_NAME } from '../router/constants';
@@ -19,9 +18,15 @@ export default {
};
},
update: data => {
- const designEdges = propertyOf(data)(['project', 'issue', 'designCollection', 'designs']);
- if (designEdges) {
- return extractNodes(designEdges);
+ const designNodes = propertyOf(data)([
+ 'project',
+ 'issue',
+ 'designCollection',
+ 'designs',
+ 'nodes',
+ ]);
+ if (designNodes) {
+ return designNodes;
}
return [];
},
diff --git a/app/assets/javascripts/design_management/mixins/all_versions.js b/app/assets/javascripts/design_management/mixins/all_versions.js
index 3966fe71732..7a094f23378 100644
--- a/app/assets/javascripts/design_management/mixins/all_versions.js
+++ b/app/assets/javascripts/design_management/mixins/all_versions.js
@@ -1,17 +1,8 @@
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() {
@@ -21,7 +12,15 @@ export default {
atVersion: null,
};
},
- update: data => data.project.issue.designCollection.versions.edges,
+ update: data => data.project.issue.designCollection.versions.nodes,
+ },
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ issueIid: {
+ default: '',
},
},
computed: {
@@ -29,7 +28,7 @@ export default {
return (
this.$route.query.version &&
this.allVersions &&
- this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version))
+ this.allVersions.some(version => version.id.endsWith(this.$route.query.version))
);
},
designsVersion() {
@@ -39,7 +38,7 @@ export default {
},
latestVersionId() {
const latestVersion = this.allVersions[0];
- return latestVersion && findVersionId(latestVersion.node.id);
+ return latestVersion && findVersionId(latestVersion.id);
},
isLatestVersion() {
if (this.allVersions.length > 0) {
@@ -55,8 +54,6 @@ export default {
data() {
return {
allVersions: [],
- projectPath: '',
- issueIid: null,
};
},
};
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index 9a959222e22..17b72e73127 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -2,7 +2,7 @@
import Mousetrap from 'mousetrap';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { ApolloMutation } from 'vue-apollo';
-import createFlash from '~/flash';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import allVersionsMixin from '../../mixins/all_versions';
import Toolbar from '../../components/toolbar/index.vue';
@@ -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
diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue
index d14a1fc8c1c..cd68e9d6c5b 100644
--- a/app/assets/javascripts/design_management/pages/index.vue
+++ b/app/assets/javascripts/design_management/pages/index.vue
@@ -1,6 +1,7 @@
<script>
-import { GlLoadingIcon, GlDeprecatedButton, GlAlert } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
+import VueDraggable from 'vuedraggable';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__, sprintf } from '~/locale';
import UploadButton from '../components/upload/button.vue';
import DeleteButton from '../components/delete_button.vue';
@@ -9,6 +10,7 @@ import DesignDestroyer from '../components/design_destroyer.vue';
import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
import DesignDropzone from '../components/upload/design_dropzone.vue';
import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
+import moveDesignMutation from '../graphql/mutations/move_design.mutation.graphql';
import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import allDesignsMixin from '../mixins/all_designs';
@@ -16,13 +18,18 @@ import {
UPLOAD_DESIGN_ERROR,
EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
+ MOVE_DESIGN_ERROR,
designUploadSkippedWarning,
designDeletionError,
} from '../utils/error_messages';
-import { updateStoreAfterUploadDesign } from '../utils/cache_update';
+import {
+ updateStoreAfterUploadDesign,
+ updateDesignsOnStoreAfterReorder,
+} from '../utils/cache_update';
import {
designUploadOptimisticResponse,
isValidDesignFile,
+ moveDesignOptimisticResponse,
} from '../utils/design_management_utils';
import { getFilename } from '~/lib/utils/file_upload';
import { DESIGNS_ROUTE_NAME } from '../router/constants';
@@ -33,13 +40,14 @@ export default {
components: {
GlLoadingIcon,
GlAlert,
- GlDeprecatedButton,
+ GlButton,
UploadButton,
Design,
DesignDestroyer,
DesignVersionDropdown,
DeleteButton,
DesignDropzone,
+ VueDraggable,
},
mixins: [allDesignsMixin],
apollo: {
@@ -61,6 +69,8 @@ export default {
},
filesToBeSaved: [],
selectedDesigns: [],
+ isDraggingDesign: false,
+ reorderedDesigns: null,
};
},
computed: {
@@ -96,9 +106,19 @@ 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,56 +258,97 @@ 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);
+ },
+ designMoveVariables(newIndex, element) {
+ const variables = {
+ id: element.id,
+ };
+ if (newIndex > 0) {
+ variables.previous = this.reorderedDesigns[newIndex - 1].id;
+ }
+ if (newIndex < this.reorderedDesigns.length - 1) {
+ variables.next = this.reorderedDesigns[newIndex + 1].id;
}
+ return variables;
+ },
+ reorderDesigns({ moved: { newIndex, element } }) {
+ this.$apollo
+ .mutate({
+ mutation: moveDesignMutation,
+ variables: this.designMoveVariables(newIndex, element),
+ update: (store, { data: { designManagementMove } }) => {
+ return updateDesignsOnStoreAfterReorder(
+ store,
+ designManagementMove,
+ this.projectQueryBody,
+ );
+ },
+ optimisticResponse: moveDesignOptimisticResponse(this.reorderedDesigns),
+ })
+ .catch(() => {
+ createFlash(MOVE_DESIGN_ERROR);
+ });
+ },
+ onDesignMove(designs) {
+ this.reorderedDesigns = designs;
},
},
beforeRouteUpdate(to, from, next) {
- this.toggleOnPasteListener(to.name);
this.selectedDesigns = [];
next();
},
- beforeRouteLeave(to, from, next) {
- this.toggleOnPasteListener(to.name);
- next();
+ dragOptions: {
+ animation: 200,
+ ghostClass: 'gl-visibility-hidden',
},
};
</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-3 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="warning"
+ button-category="secondary"
+ button-class="gl-mr-3"
+ button-size="small"
+ :loading="loading"
:has-selected-designs="hasSelectedDesigns"
@deleteSelectedDesigns="mutate()"
>
- {{ s__('DesignManagement|Delete selected') }}
- <gl-loading-icon v-if="loading" inline class="ml-1" />
+ {{ s__('DesignManagement|Archive selected') }}
</delete-button>
</design-destroyer>
<upload-button v-if="canCreateDesign" :is-saving="isSaving" @upload="onUploadDesign" />
@@ -299,14 +360,35 @@ export default {
<gl-alert v-else-if="error" variant="danger" :dismissible="false">
{{ __('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>
- <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>
+ <vue-draggable
+ v-else
+ :value="designs"
+ :disabled="!isLatestVersion"
+ v-bind="$options.dragOptions"
+ tag="ol"
+ draggable=".js-design-tile"
+ class="list-unstyled row"
+ @start="isDraggingDesign = true"
+ @end="isDraggingDesign = false"
+ @change="reorderDesigns"
+ @input="onDesignMove"
+ >
+ <li
+ v-for="design in designs"
+ :key="design.id"
+ class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
+ >
+ <design-dropzone
+ :has-designs="hasDesigns"
+ :is-dragging-design="isDraggingDesign"
+ @change="onExistingDesignDropzoneChange($event, design.filename)"
+ >
+ <design
+ v-bind="design"
+ :is-uploading="isDesignToBeSaved(design.filename)"
+ class="gl-bg-white"
+ />
+ </design-dropzone>
<input
v-if="canSelectDesign(design.filename)"
@@ -316,7 +398,17 @@ export default {
@change="changeSelectedDesigns(design.filename)"
/>
</li>
- </ol>
+ <template #header>
+ <li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
+ <design-dropzone
+ :is-dragging-design="isDraggingDesign"
+ :class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
+ :has-designs="hasDesigns"
+ @change="onUploadDesign"
+ />
+ </li>
+ </template>
+ </vue-draggable>
</div>
<router-view :key="$route.fullPath" />
</div>
diff --git a/app/assets/javascripts/design_management/router/constants.js b/app/assets/javascripts/design_management/router/constants.js
index abeef520e33..dd2ee8d8689 100644
--- a/app/assets/javascripts/design_management/router/constants.js
+++ b/app/assets/javascripts/design_management/router/constants.js
@@ -1,3 +1,2 @@
-export const ROOT_ROUTE_NAME = 'root';
export const DESIGNS_ROUTE_NAME = 'designs';
export const DESIGN_ROUTE_NAME = 'design';
diff --git a/app/assets/javascripts/design_management/router/index.js b/app/assets/javascripts/design_management/router/index.js
index 7494da002c8..cbeb2f7ce42 100644
--- a/app/assets/javascripts/design_management/router/index.js
+++ b/app/assets/javascripts/design_management/router/index.js
@@ -1,4 +1,3 @@
-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) {
diff --git a/app/assets/javascripts/design_management/router/routes.js b/app/assets/javascripts/design_management/router/routes.js
index 788910e5514..d888b856611 100644
--- a/app/assets/javascripts/design_management/router/routes.js
+++ b/app/assets/javascripts/design_management/router/routes.js
@@ -1,44 +1,29 @@
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 }),
},
];
diff --git a/app/assets/javascripts/design_management/utils/cache_update.js b/app/assets/javascripts/design_management/utils/cache_update.js
index 24b374b79fd..b79df9d01d5 100644
--- a/app/assets/javascripts/design_management/utils/cache_update.js
+++ b/app/assets/javascripts/design_management/utils/cache_update.js
@@ -1,6 +1,7 @@
/* eslint-disable @gitlab/require-i18n-strings */
-import createFlash from '~/flash';
+import { groupBy } from 'lodash';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import { extractCurrentDiscussion, extractDesign } from './design_management_utils';
import {
ADD_IMAGE_DIFF_NOTE_ERROR,
@@ -12,10 +13,10 @@ import {
const deleteDesignsFromStore = (store, query, selectedDesigns) => {
const data = store.readQuery(query);
- const changedDesigns = data.project.issue.designCollection.designs.edges.filter(
- ({ node }) => !selectedDesigns.includes(node.filename),
+ const changedDesigns = data.project.issue.designCollection.designs.nodes.filter(
+ node => !selectedDesigns.includes(node.filename),
);
- data.project.issue.designCollection.designs.edges = [...changedDesigns];
+ data.project.issue.designCollection.designs.nodes = [...changedDesigns];
store.writeQuery({
...query,
@@ -34,11 +35,10 @@ const addNewVersionToStore = (store, query, version) => {
if (!version) return;
const data = store.readQuery(query);
- const newEdge = { node: version, __typename: 'DesignVersionEdge' };
- data.project.issue.designCollection.versions.edges = [
- newEdge,
- ...data.project.issue.designCollection.versions.edges,
+ data.project.issue.designCollection.versions.nodes = [
+ version,
+ ...data.project.issue.designCollection.versions.nodes,
];
store.writeQuery({
@@ -59,18 +59,15 @@ const addDiscussionCommentToStore = (store, createNote, query, queryVariables, d
design.notesCount += 1;
if (
- !design.issue.participants.edges.some(
- participant => participant.node.username === createNote.note.author.username,
+ !design.issue.participants.nodes.some(
+ participant => participant.username === createNote.note.author.username,
)
) {
- design.issue.participants.edges = [
- ...design.issue.participants.edges,
+ design.issue.participants.nodes = [
+ ...design.issue.participants.nodes,
{
- __typename: 'UserEdge',
- node: {
- __typename: 'User',
- ...createNote.note.author,
- },
+ __typename: 'User',
+ ...createNote.note.author,
},
];
}
@@ -108,18 +105,15 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
const notesCount = design.notesCount + 1;
design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
if (
- !design.issue.participants.edges.some(
- participant => participant.node.username === createImageDiffNote.note.author.username,
+ !design.issue.participants.nodes.some(
+ participant => participant.username === createImageDiffNote.note.author.username,
)
) {
- design.issue.participants.edges = [
- ...design.issue.participants.edges,
+ design.issue.participants.nodes = [
+ ...design.issue.participants.nodes,
{
- __typename: 'UserEdge',
- node: {
- __typename: 'User',
- ...createImageDiffNote.note.author,
- },
+ __typename: 'User',
+ ...createImageDiffNote.note.author,
},
];
}
@@ -166,42 +160,37 @@ const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables
const addNewDesignToStore = (store, designManagementUpload, query) => {
const data = store.readQuery(query);
- const newDesigns = data.project.issue.designCollection.designs.edges.reduce((acc, design) => {
- if (!acc.find(d => d.filename === design.node.filename)) {
- acc.push(design.node);
- }
-
- return acc;
- }, designManagementUpload.designs);
+ const currentDesigns = data.project.issue.designCollection.designs.nodes;
+ const existingDesigns = groupBy(currentDesigns, 'filename');
+ const newDesigns = currentDesigns.concat(
+ designManagementUpload.designs.filter(d => !existingDesigns[d.filename]),
+ );
let newVersionNode;
const findNewVersions = designManagementUpload.designs.find(design => design.versions);
if (findNewVersions) {
- const findNewVersionsEdges = findNewVersions.versions.edges;
+ const findNewVersionsNodes = findNewVersions.versions.nodes;
- if (findNewVersionsEdges && findNewVersionsEdges.length) {
- newVersionNode = [findNewVersionsEdges[0]];
+ if (findNewVersionsNodes && findNewVersionsNodes.length) {
+ newVersionNode = [findNewVersionsNodes[0]];
}
}
const newVersions = [
...(newVersionNode || []),
- ...data.project.issue.designCollection.versions.edges,
+ ...data.project.issue.designCollection.versions.nodes,
];
const updatedDesigns = {
__typename: 'DesignCollection',
designs: {
__typename: 'DesignConnection',
- edges: newDesigns.map(design => ({
- __typename: 'DesignEdge',
- node: design,
- })),
+ nodes: newDesigns,
},
versions: {
__typename: 'DesignVersionConnection',
- edges: newVersions,
+ nodes: newVersions,
},
};
@@ -213,6 +202,15 @@ const addNewDesignToStore = (store, designManagementUpload, query) => {
});
};
+const moveDesignInStore = (store, designManagementMove, query) => {
+ const data = store.readQuery(query);
+ data.project.issue.designCollection.designs = designManagementMove.designCollection.designs;
+ store.writeQuery({
+ ...query,
+ data,
+ });
+};
+
const onError = (data, message) => {
createFlash(message);
throw new Error(data.errors);
@@ -274,3 +272,11 @@ export const updateStoreAfterUploadDesign = (store, data, query) => {
addNewDesignToStore(store, data, query);
}
};
+
+export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
+ if (hasErrors(data)) {
+ createFlash(data.errors[0]);
+ } else {
+ moveDesignInStore(store, data, query);
+ }
+};
diff --git a/app/assets/javascripts/design_management/utils/design_management_utils.js b/app/assets/javascripts/design_management/utils/design_management_utils.js
index 22705cf67a1..da8f89ff960 100644
--- a/app/assets/javascripts/design_management/utils/design_management_utils.js
+++ b/app/assets/javascripts/design_management/utils/design_management_utils.js
@@ -5,17 +5,7 @@ export const isValidDesignFile = ({ type }) =>
(type.match(VALID_DESIGN_FILE_MIMETYPE.regex) || []).length > 0;
/**
- * Returns formatted array that doesn't contain
- * `edges`->`node` nesting
- *
- * @param {Array} elements
- */
-
-export const extractNodes = elements => elements.edges.map(({ node }) => node);
-
-/**
- * Returns formatted array of discussions that doesn't contain
- * `edges`->`node` nesting for child notes
+ * Returns formatted array of discussions
*
* @param {Array} discussions
*/
@@ -40,9 +30,9 @@ export const findVersionId = id => (id.match('::Version/(.+$)') || [])[1];
export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
-export const extractDesigns = data => data.project.issue.designCollection.designs.edges;
+export const extractDesigns = data => data.project.issue.designCollection.designs.nodes;
-export const extractDesign = data => (extractDesigns(data) || [])[0]?.node;
+export const extractDesign = data => (extractDesigns(data) || [])[0];
/**
* Generates optimistic response for a design upload mutation
@@ -72,13 +62,10 @@ export const designUploadOptimisticResponse = files => {
},
versions: {
__typename: 'DesignVersionConnection',
- edges: {
- __typename: 'DesignVersionEdge',
- node: {
- __typename: 'DesignVersion',
- id: -uniqueId(),
- sha: -uniqueId(),
- },
+ nodes: {
+ __typename: 'DesignVersion',
+ id: -uniqueId(),
+ sha: -uniqueId(),
},
},
}));
@@ -98,7 +85,8 @@ export const designUploadOptimisticResponse = files => {
/**
* Generates optimistic response for a design upload mutation
- * @param {Array<File>} files
+ * @param {Object} note
+ * @param {Object} position
*/
export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
@@ -117,12 +105,33 @@ export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
},
});
+/**
+ * Generates optimistic response for a design upload mutation
+ * @param {Array} designs
+ */
+export const moveDesignOptimisticResponse = designs => ({
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Mutation',
+ designManagementMove: {
+ __typename: 'DesignManagementMovePayload',
+ designCollection: {
+ __typename: 'DesignCollection',
+ designs: {
+ __typename: 'DesignConnection',
+ nodes: designs,
+ },
+ },
+ errors: [],
+ },
+});
+
const normalizeAuthor = author => ({
...author,
web_url: author.webUrl,
avatar_url: author.avatarUrl,
});
-export const extractParticipants = users => users.edges.map(({ node }) => normalizeAuthor(node));
+export const extractParticipants = users => users.map(node => normalizeAuthor(node));
export const getPageLayoutElement = () => document.querySelector('.layout-page');
diff --git a/app/assets/javascripts/design_management/utils/error_messages.js b/app/assets/javascripts/design_management/utils/error_messages.js
index 7666c726c2f..c815b11737d 100644
--- a/app/assets/javascripts/design_management/utils/error_messages.js
+++ b/app/assets/javascripts/design_management/utils/error_messages.js
@@ -40,6 +40,10 @@ export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __(
'You must upload a file with the same file name when dropping onto an existing design.',
);
+export const MOVE_DESIGN_ERROR = __(
+ 'Something went wrong when reordering designs. Please try again',
+);
+
const MAX_SKIPPED_FILES_LISTINGS = 5;
const oneDesignSkippedMessage = filename =>
@@ -69,7 +73,7 @@ const someDesignsSkippedMessage = skippedFiles => {
export const designDeletionError = ({ singular = true } = {}) => {
const design = singular ? __('a design') : __('designs');
- return sprintf(s__('Could not delete %{design}. Please try again.'), {
+ return sprintf(s__('Could not archive %{design}. Please try again.'), {
design,
});
};