diff options
Diffstat (limited to 'app/assets/javascripts/design_management')
25 files changed, 174 insertions, 297 deletions
diff --git a/app/assets/javascripts/design_management/components/design_destroyer.vue b/app/assets/javascripts/design_management/components/design_destroyer.vue index 7ae569216f0..5d32bfd4a73 100644 --- a/app/assets/javascripts/design_management/components/design_destroyer.vue +++ b/app/assets/javascripts/design_management/components/design_destroyer.vue @@ -1,6 +1,6 @@ <script> import { ApolloMutation } from 'vue-apollo'; -import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql'; import { updateStoreAfterDesignsDelete } from '../utils/cache_update'; 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 845f1aec8cf..6aab4bf423e 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 @@ -210,7 +210,7 @@ export default { :class="{ 'gl-bg-blue-50': isDiscussionActive }" @error="$emit('update-note-error', $event)" > - <template v-if="discussion.resolvable" #resolveDiscussion> + <template v-if="discussion.resolvable" #resolve-discussion> <button v-gl-tooltip :class="{ 'is-active': discussion.resolved }" @@ -224,7 +224,7 @@ export default { <gl-loading-icon v-else inline /> </button> </template> - <template v-if="discussion.resolved" #resolvedStatus> + <template v-if="discussion.resolved" #resolved-status> <p class="gl-text-gray-500 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message"> {{ __('Resolved by') }} <gl-link @@ -277,7 +277,7 @@ export default { @submit-form="mutate" @cancel-form="hideForm" > - <template v-if="discussion.resolvable" #resolveCheckbox> + <template v-if="discussion.resolvable" #resolve-checkbox> <label data-testid="resolve-checkbox"> <input v-model="shouldChangeResolvedStatus" type="checkbox" /> {{ resolveCheckboxText }} 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 7f4b3b31024..421a4dc274a 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 @@ -108,7 +108,7 @@ export default { </span> </div> <div class="gl-display-flex gl-align-items-baseline"> - <slot name="resolveDiscussion"></slot> + <slot name="resolve-discussion"></slot> <button v-if="isEditButtonVisible" v-gl-tooltip @@ -127,7 +127,7 @@ export default { class="note-text js-note-text md" data-qa-selector="note_content" ></div> - <slot name="resolvedStatus"></slot> + <slot name="resolved-status"></slot> </template> <apollo-mutation v-else diff --git a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue index 3754e1dbbc1..7aaac58a1ce 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue @@ -110,7 +110,7 @@ export default { </textarea> </template> </markdown-field> - <slot name="resolveCheckbox"></slot> + <slot name="resolve-checkbox"></slot> <div class="note-form-actions gl-display-flex gl-justify-content-space-between"> <gl-button ref="submitButton" diff --git a/app/assets/javascripts/design_management/components/design_overlay.vue b/app/assets/javascripts/design_management/components/design_overlay.vue index 88f3ce0b8ea..3c2ce693bc0 100644 --- a/app/assets/javascripts/design_management/components/design_overlay.vue +++ b/app/assets/javascripts/design_management/components/design_overlay.vue @@ -112,9 +112,9 @@ export default { }, canMoveNote(note) { const { userPermissions } = note; - const { adminNote } = userPermissions || {}; + const { repositionNote } = userPermissions || {}; - return Boolean(adminNote); + return Boolean(repositionNote); }, isPositionInOverlay(position) { const { top, left } = this.getNoteRelativePosition(position); diff --git a/app/assets/javascripts/design_management/components/design_scaler.vue b/app/assets/javascripts/design_management/components/design_scaler.vue index 8d26f84641e..85c6bd4d79e 100644 --- a/app/assets/javascripts/design_management/components/design_scaler.vue +++ b/app/assets/javascripts/design_management/components/design_scaler.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlButtonGroup, GlButton } from '@gitlab/ui'; const SCALE_STEP_SIZE = 0.2; const DEFAULT_SCALE = 1; @@ -8,7 +8,8 @@ const MAX_SCALE = 2; export default { components: { - GlIcon, + GlButtonGroup, + GlButton, }, data() { return { @@ -49,17 +50,9 @@ export default { </script> <template> - <div class="design-scaler btn-group" role="group"> - <button class="btn" :disabled="disableDecrease" @click="decrementScale"> - <span class="gl-display-flex gl-justify-content-center gl-align-items-center gl-icon s16"> - – - </span> - </button> - <button class="btn" :disabled="disableReset" @click="resetScale"> - <gl-icon name="redo" /> - </button> - <button class="btn" :disabled="disableIncrease" @click="incrementScale"> - <gl-icon name="plus" /> - </button> - </div> + <gl-button-group class="gl-z-index-1"> + <gl-button icon="dash" :disabled="disableDecrease" @click="decrementScale" /> + <gl-button icon="redo" :disabled="disableReset" @click="resetScale" /> + <gl-button icon="plus" :disabled="disableIncrease" @click="incrementScale" /> + </gl-button-group> </template> diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue index fb8e74c8c4c..41dcec38abe 100644 --- a/app/assets/javascripts/design_management/components/design_sidebar.vue +++ b/app/assets/javascripts/design_management/components/design_sidebar.vue @@ -207,6 +207,6 @@ export default { /> </gl-collapse> </template> - <slot name="replyForm"></slot> + <slot name="reply-form"></slot> </div> </template> diff --git a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue index 2719d701c12..4edc2e410c7 100644 --- a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue +++ b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue @@ -1,7 +1,7 @@ <script> /* global Mousetrap */ import 'mousetrap'; -import { GlButton, GlButtonGroup } from '@gitlab/ui'; +import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; import allDesignsMixin from '../../mixins/all_designs'; import { DESIGN_ROUTE_NAME } from '../../router/constants'; @@ -11,6 +11,9 @@ export default { GlButton, GlButtonGroup, }, + directives: { + GlTooltip: GlTooltipDirective, + }, mixins: [allDesignsMixin], props: { id: { @@ -68,6 +71,7 @@ export default { {{ paginationText }} <gl-button-group class="gl-mx-5"> <gl-button + v-gl-tooltip.bottom :disabled="!previousDesign" :title="s__('DesignManagement|Go to previous design')" icon="angle-left" @@ -75,6 +79,7 @@ export default { @click="navigateToDesign(previousDesign)" /> <gl-button + v-gl-tooltip.bottom :disabled="!nextDesign" :title="s__('DesignManagement|Go to next design')" icon="angle-right" diff --git a/app/assets/javascripts/design_management/components/toolbar/index.vue b/app/assets/javascripts/design_management/components/toolbar/index.vue index 8d25d467d59..4caee863df8 100644 --- a/app/assets/javascripts/design_management/components/toolbar/index.vue +++ b/app/assets/javascripts/design_management/components/toolbar/index.vue @@ -1,10 +1,10 @@ <script> -import { GlButton, GlIcon } from '@gitlab/ui'; +import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql'; import { __, sprintf } from '~/locale'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import DesignNavigation from './design_navigation.vue'; import DeleteButton from '../delete_button.vue'; -import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql'; import { DESIGNS_ROUTE_NAME } from '../../router/constants'; export default { @@ -14,6 +14,9 @@ export default { DesignNavigation, DeleteButton, }, + directives: { + GlTooltip: GlTooltipDirective, + }, mixins: [timeagoMixin], props: { id: { @@ -112,14 +115,21 @@ export default { </div> </div> <design-navigation :id="id" class="gl-ml-auto gl-flex-shrink-0" /> - <gl-button :href="image" icon="download" /> + <gl-button + v-gl-tooltip.bottom + :href="image" + icon="download" + :title="s__('DesignManagement|Download design')" + /> <delete-button v-if="isLatestVersion && canDeleteDesign" + v-gl-tooltip.bottom class="gl-ml-3" :is-deleting="isDeleting" button-variant="warning" button-icon="archive" button-category="secondary" + :title="s__('DesignManagement|Archive design')" @deleteSelectedDesigns="$emit('delete')" /> </header> diff --git a/app/assets/javascripts/design_management/components/upload/button.vue b/app/assets/javascripts/design_management/components/upload/button.vue index c76041c74a8..d7b287f663b 100644 --- a/app/assets/javascripts/design_management/components/upload/button.vue +++ b/app/assets/javascripts/design_management/components/upload/button.vue @@ -1,11 +1,10 @@ <script> -import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants'; export default { components: { GlButton, - GlLoadingIcon, }, directives: { GlTooltip: GlTooltipDirective, @@ -38,12 +37,12 @@ export default { ) " :disabled="isSaving" + :loading="isSaving" variant="default" size="small" @click="openFileUpload" > {{ s__('DesignManagement|Upload designs') }} - <gl-loading-icon v-if="isSaving" inline class="ml-1" /> </gl-button> <input diff --git a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management/components/upload/design_dropzone.vue deleted file mode 100644 index 6694b0dab8d..00000000000 --- a/app/assets/javascripts/design_management/components/upload/design_dropzone.vue +++ /dev/null @@ -1,147 +0,0 @@ -<script> -import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; -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'; -import { VALID_DATA_TRANSFER_TYPE, VALID_DESIGN_FILE_MIMETYPE } from '../../constants'; - -export default { - components: { - GlIcon, - GlLink, - GlSprintf, - }, - props: { - hasDesigns: { - type: Boolean, - required: true, - }, - isDraggingDesign: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { - dragCounter: 0, - isDragDataValid: false, - }; - }, - computed: { - 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) { - return files.every(isValidDesignFile); - }, - isValidDragDataType({ dataTransfer }) { - return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE)); - }, - ondrop({ dataTransfer = {} }) { - this.dragCounter = 0; - // User already had feedback when dropzone was active, so bail here - if (!this.isDragDataValid) { - return; - } - - const { files } = dataTransfer; - if (!this.isValidUpload(Array.from(files))) { - createFlash(UPLOAD_DESIGN_INVALID_FILETYPE_ERROR); - return; - } - - this.$emit('change', files); - }, - ondragenter(e) { - this.dragCounter += 1; - this.isDragDataValid = this.isValidDragDataType(e); - }, - ondragleave() { - this.dragCounter -= 1; - }, - openFileUpload() { - this.$refs.fileUpload.click(); - }, - onDesignInputChange(e) { - this.$emit('change', e.target.files); - }, - }, - uploadDesignMutation, - VALID_DESIGN_FILE_MIMETYPE, -}; -</script> - -<template> - <div - class="gl-w-full gl-relative" - @dragstart.prevent.stop - @dragend.prevent.stop - @dragover.prevent.stop - @dragenter.prevent.stop="ondragenter" - @dragleave.prevent.stop="ondragleave" - @drop.prevent.stop="ondrop" - > - <slot> - <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-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')"> - <template #link="{ content }"> - <gl-link @click.stop="openFileUpload"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </p> - </div> - </button> - - <input - ref="fileUpload" - type="file" - name="design_file" - :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype" - class="hide" - multiple - @change="onDesignInputChange" - /> - </slot> - <transition name="design-dropzone-fade"> - <div - v-show="dragging && !isDraggingDesign" - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" - > - <div v-show="!isDragDataValid" class="mw-50 gl-text-center"> - <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.', - ) - }}</span> - </div> - <div v-show="isDragDataValid" class="mw-50 gl-text-center"> - <h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Incoming!') }}</h3> - <span>{{ __('Drop your designs to start your upload.') }}</span> - </div> - </div> - </transition> - </div> -</template> diff --git a/app/assets/javascripts/design_management/constants.js b/app/assets/javascripts/design_management/constants.js index 63a92ef5ec0..92928ca429f 100644 --- a/app/assets/javascripts/design_management/constants.js +++ b/app/assets/javascripts/design_management/constants.js @@ -5,9 +5,6 @@ export const VALID_DESIGN_FILE_MIMETYPE = { regex: /image\/.+/, }; -// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types -export const VALID_DATA_TRANSFER_TYPE = 'Files'; - export const ACTIVE_DISCUSSION_SOURCE_TYPES = { pin: 'pin', discussion: 'discussion', diff --git a/app/assets/javascripts/design_management/graphql/fragments/note_permissions.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/note_permissions.fragment.graphql index c243e39f3d3..e599ab19c2d 100644 --- a/app/assets/javascripts/design_management/graphql/fragments/note_permissions.fragment.graphql +++ b/app/assets/javascripts/design_management/graphql/fragments/note_permissions.fragment.graphql @@ -1,3 +1,4 @@ fragment DesignNotePermissions on NotePermissions { adminNote + repositionNote } diff --git a/app/assets/javascripts/design_management/graphql/mutations/reposition_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/reposition_image_diff_note.mutation.graphql new file mode 100644 index 00000000000..78fbcf1c3c7 --- /dev/null +++ b/app/assets/javascripts/design_management/graphql/mutations/reposition_image_diff_note.mutation.graphql @@ -0,0 +1,10 @@ +#import "../fragments/design_note.fragment.graphql" + +mutation repositionImageDiffNote($input: RepositionImageDiffNoteInput!) { + repositionImageDiffNote(input: $input) { + errors + note { + ...DesignNote + } + } +} diff --git a/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql deleted file mode 100644 index 5562ca9d89f..00000000000 --- a/app/assets/javascripts/design_management/graphql/mutations/update_image_diff_note.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/design_note.fragment.graphql" - -mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) { - updateImageDiffNote(input: $input) { - errors - note { - ...DesignNote - } - } -} diff --git a/app/assets/javascripts/design_management/graphql/queries/design_permissions.query.graphql b/app/assets/javascripts/design_management/graphql/queries/design_permissions.query.graphql deleted file mode 100644 index a87b256dc95..00000000000 --- a/app/assets/javascripts/design_management/graphql/queries/design_permissions.query.graphql +++ /dev/null @@ -1,10 +0,0 @@ -query permissions($fullPath: ID!, $iid: String!) { - project(fullPath: $fullPath) { - id - issue(iid: $iid) { - userPermissions { - createDesign - } - } - } -} 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 96869a404b1..99a61191c6e 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 @@ -1,7 +1,12 @@ #import "../fragments/design.fragment.graphql" #import "~/graphql_shared/fragments/author.fragment.graphql" -query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) { +query getDesign( + $fullPath: ID! + $iid: String! + $atVersion: DesignManagementVersionID + $filenames: [String!] +) { project(fullPath: $fullPath) { id issue(iid: $iid) { 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 deleted file mode 100644 index efa61edf51a..00000000000 --- a/app/assets/javascripts/design_management/graphql/queries/get_design_list.query.graphql +++ /dev/null @@ -1,23 +0,0 @@ -#import "../fragments/design_list.fragment.graphql" -#import "../fragments/version.fragment.graphql" - -query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) { - project(fullPath: $fullPath) { - id - issue(iid: $iid) { - designCollection { - copyState - designs(atVersion: $atVersion) { - nodes { - ...DesignListItem - } - } - versions { - nodes { - ...VersionListItem - } - } - } - } - } -} diff --git a/app/assets/javascripts/design_management/mixins/all_designs.js b/app/assets/javascripts/design_management/mixins/all_designs.js index 62bcf216add..466f61e21fa 100644 --- a/app/assets/javascripts/design_management/mixins/all_designs.js +++ b/app/assets/javascripts/design_management/mixins/all_designs.js @@ -1,7 +1,7 @@ import { propertyOf } from 'lodash'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; +import createFlash, { FLASH_TYPES } from '~/flash'; import { s__ } from '~/locale'; -import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; import allVersionsMixin from './all_versions'; import { DESIGNS_ROUTE_NAME } from '../router/constants'; @@ -36,20 +36,20 @@ export default { }, result() { if (this.$route.query.version && !this.hasValidVersion) { - createFlash( - s__( + createFlash({ + message: s__( 'DesignManagement|Requested design version does not exist. Showing latest version instead', ), - ); + }); this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } }); } if (this.designCollection.copyState === 'ERROR') { - createFlash( - s__( + createFlash({ + message: s__( 'DesignManagement|There was an error moving your designs. Please upload your designs below.', ), - 'warning', - ); + type: FLASH_TYPES.WARNING, + }); } }, }, diff --git a/app/assets/javascripts/design_management/mixins/all_versions.js b/app/assets/javascripts/design_management/mixins/all_versions.js index 7a094f23378..07cd0fc92bd 100644 --- a/app/assets/javascripts/design_management/mixins/all_versions.js +++ b/app/assets/javascripts/design_management/mixins/all_versions.js @@ -1,4 +1,4 @@ -import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; import { findVersionId } from '../utils/design_management_utils'; export default { diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index 6a96b06dcd8..e07279ba39d 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 { deprecatedCreateFlash as createFlash } from '~/flash'; +import createFlash from '~/flash'; import { fetchPolicies } from '~/lib/graphql'; import allVersionsMixin from '../../mixins/all_versions'; import Toolbar from '../../components/toolbar/index.vue'; @@ -13,18 +13,19 @@ 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 createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql'; -import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql'; +import repositionImageDiffNoteMutation from '../../graphql/mutations/reposition_image_diff_note.mutation.graphql'; import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql'; import { extractDiscussions, extractDesign, - updateImageDiffNoteOptimisticResponse, + repositionImageDiffNoteOptimisticResponse, toDiffNoteGid, extractDesignNoteId, + getPageLayoutElement, } from '../../utils/design_management_utils'; import { updateStoreAfterAddImageDiffNote, - updateStoreAfterUpdateImageDiffNote, + updateStoreAfterRepositionImageDiffNote, } from '../../utils/cache_update'; import { ADD_DISCUSSION_COMMENT_ERROR, @@ -38,7 +39,7 @@ import { } from '../../utils/error_messages'; import { trackDesignDetailView } from '../../utils/tracking'; import { DESIGNS_ROUTE_NAME } from '../../router/constants'; -import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants'; +import { ACTIVE_DISCUSSION_SOURCE_TYPES, DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../../constants'; const DEFAULT_SCALE = 1; @@ -181,12 +182,12 @@ export default { updateImageDiffNoteInStore( store, { - data: { updateImageDiffNote }, + data: { repositionImageDiffNote }, }, ) { - return updateStoreAfterUpdateImageDiffNote( + return updateStoreAfterRepositionImageDiffNote( store, - updateImageDiffNote, + repositionImageDiffNote, getDesignQuery, this.designVariables, ); @@ -198,7 +199,7 @@ export default { ); const mutationPayload = { - optimisticResponse: updateImageDiffNoteOptimisticResponse(note, { + optimisticResponse: repositionImageDiffNoteOptimisticResponse(note, { position, }), variables: { @@ -207,7 +208,7 @@ export default { position, }, }, - mutation: updateImageDiffNoteMutation, + mutation: repositionImageDiffNoteMutation, update: this.updateImageDiffNoteInStore, }; @@ -229,7 +230,7 @@ export default { onQueryError(message) { // because we redirect user to /designs (the issue page), // we want to create these flashes on the issue page - createFlash(message); + createFlash({ message }); this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME }); }, onError(message, e) { @@ -300,6 +301,22 @@ export default { this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded; }, }, + beforeRouteEnter(to, from, next) { + const pageEl = getPageLayoutElement(); + if (pageEl) { + pageEl.classList.add(...DESIGN_DETAIL_LAYOUT_CLASSLIST); + } + + next(); + }, + beforeRouteLeave(to, from, next) { + const pageEl = getPageLayoutElement(); + if (pageEl) { + pageEl.classList.remove(...DESIGN_DETAIL_LAYOUT_CLASSLIST); + } + + next(); + }, createImageDiffNoteMutation, DESIGNS_ROUTE_NAME, }; @@ -366,7 +383,7 @@ export default { @toggleResolvedComments="toggleResolvedComments" @todoError="onTodoError" > - <template #replyForm> + <template #reply-form> <apollo-mutation v-if="isAnnotating" #default="{ mutate, loading }" diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue index 6e71dca41e9..ea404692840 100644 --- a/app/assets/javascripts/design_management/pages/index.vue +++ b/app/assets/javascripts/design_management/pages/index.vue @@ -1,25 +1,26 @@ <script> -import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import VueDraggable from 'vuedraggable'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; -import { s__, sprintf } from '~/locale'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; +import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql'; +import createFlash, { FLASH_TYPES } from '~/flash'; +import { __, s__, sprintf } from '~/locale'; import { getFilename } from '~/lib/utils/file_upload'; import UploadButton from '../components/upload/button.vue'; import DeleteButton from '../components/delete_button.vue'; import Design from '../components/list/item.vue'; 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 DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_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'; import { UPLOAD_DESIGN_ERROR, EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE, EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE, MOVE_DESIGN_ERROR, + UPLOAD_DESIGN_INVALID_FILETYPE_ERROR, designUploadSkippedWarning, designDeletionError, } from '../utils/error_messages'; @@ -34,6 +35,7 @@ import { } from '../utils/design_management_utils'; import { trackDesignCreate, trackDesignUpdate } from '../utils/tracking'; import { DESIGNS_ROUTE_NAME } from '../router/constants'; +import { VALID_DESIGN_FILE_MIMETYPE } from '../constants'; const MAXIMUM_FILE_UPLOAD_LIMIT = 10; @@ -42,6 +44,8 @@ export default { GlLoadingIcon, GlAlert, GlButton, + GlSprintf, + GlLink, UploadButton, Design, DesignDestroyer, @@ -50,6 +54,11 @@ export default { DesignDropzone, VueDraggable, }, + dropzoneProps: { + dropToStartMessage: __('Drop your designs to start your upload.'), + isFileValid: isValidDesignFile, + validFileMimetypes: [VALID_DESIGN_FILE_MIMETYPE.mimetype], + }, mixins: [allDesignsMixin], apollo: { permissions: { @@ -139,8 +148,8 @@ export default { if (!this.canCreateDesign) return false; if (files.length > MAXIMUM_FILE_UPLOAD_LIMIT) { - createFlash( - sprintf( + createFlash({ + message: sprintf( s__( 'DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again.', ), @@ -148,7 +157,7 @@ export default { upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT, }, ), - ); + }); return false; } @@ -191,7 +200,7 @@ export default { const skippedFiles = res?.data?.designManagementUpload?.skippedDesigns || []; const skippedWarningMessage = designUploadSkippedWarning(this.filesToBeSaved, skippedFiles); if (skippedWarningMessage) { - createFlash(skippedWarningMessage, 'warning'); + createFlash({ message: skippedWarningMessage, types: FLASH_TYPES.WARNING }); } // if this upload resulted in a new version being created, redirect user to the latest version @@ -214,7 +223,7 @@ export default { }, onUploadDesignError() { this.resetFilesToBeSaved(); - createFlash(UPLOAD_DESIGN_ERROR); + createFlash({ message: UPLOAD_DESIGN_ERROR }); }, changeSelectedDesigns(filename) { if (this.isDesignSelected(filename)) { @@ -245,18 +254,21 @@ export default { }, onDesignDeleteError() { const errorMessage = designDeletionError({ singular: this.selectedDesigns.length === 1 }); - createFlash(errorMessage); + createFlash({ message: errorMessage }); + }, + onDesignDropzoneError() { + createFlash({ message: UPLOAD_DESIGN_INVALID_FILETYPE_ERROR }); }, onExistingDesignDropzoneChange(files, existingDesignFilename) { const filesArr = Array.from(files); if (filesArr.length > 1) { - createFlash(EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE); + createFlash({ message: EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE }); return; } if (!filesArr.some(({ name }) => existingDesignFilename === name)) { - createFlash(EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE); + createFlash({ message: EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE }); return; } @@ -307,7 +319,7 @@ export default { optimisticResponse: moveDesignOptimisticResponse(this.reorderedDesigns), }) .catch(() => { - createFlash(MOVE_DESIGN_ERROR); + createFlash({ message: MOVE_DESIGN_ERROR }); }) .finally(() => { this.isReorderingInProgress = false; @@ -325,6 +337,9 @@ export default { animation: 200, ghostClass: 'gl-visibility-hidden', }, + i18n: { + dropzoneDescriptionText: __('Drop or %{linkStart}upload%{linkEnd} designs to attach'), + }, }; </script> @@ -335,7 +350,11 @@ export default { @mouseenter="toggleOnPasteListener" @mouseleave="toggleOffPasteListener" > - <header v-if="showToolbar" class="row-content-block gl-border-t-0 gl-p-3 gl-display-flex"> + <header + v-if="showToolbar" + class="row-content-block gl-border-t-0 gl-p-3 gl-display-flex" + data-testid="design-toolbar-wrapper" + > <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> @@ -371,7 +390,12 @@ export default { {{ s__('DesignManagement|Archive selected') }} </delete-button> </design-destroyer> - <upload-button v-if="canCreateDesign" :is-saving="isSaving" @upload="onUploadDesign" /> + <upload-button + v-if="canCreateDesign" + :is-saving="isSaving" + data-testid="design-upload-button" + @upload="onUploadDesign" + /> </div> </div> </header> @@ -414,15 +438,26 @@ export default { 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" + :display-as-card="hasDesigns" + :enable-drag-behavior="isDraggingDesign" + v-bind="$options.dropzoneProps" @change="onExistingDesignDropzoneChange($event, design.filename)" + @error="onDesignDropzoneError" > <design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)" class="gl-bg-white" /> + <template #upload-text="{ openFileUpload }"> + <gl-sprintf :message="$options.i18n.dropzoneDescriptionText"> + <template #link="{ content }"> + <gl-link @click.stop="openFileUpload"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </template> </design-dropzone> <input @@ -438,12 +473,24 @@ export default { <template #header> <li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper"> <design-dropzone - :is-dragging-design="isDraggingDesign" + :enable-drag-behavior="isDraggingDesign" :class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }" - :has-designs="hasDesigns" + :display-as-card="hasDesigns" + v-bind="$options.dropzoneProps" data-qa-selector="design_dropzone_content" @change="onUploadDesign" - /> + @error="onDesignDropzoneError" + > + <template #upload-text="{ openFileUpload }"> + <gl-sprintf :message="$options.i18n.dropzoneDescriptionText"> + <template #link="{ content }"> + <gl-link @click.stop="openFileUpload"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </template> + </design-dropzone> </li> </template> </vue-draggable> diff --git a/app/assets/javascripts/design_management/router/index.js b/app/assets/javascripts/design_management/router/index.js index cbeb2f7ce42..12692612bbc 100644 --- a/app/assets/javascripts/design_management/router/index.js +++ b/app/assets/javascripts/design_management/router/index.js @@ -1,9 +1,6 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import routes from './routes'; -import { DESIGN_ROUTE_NAME } from './constants'; -import { getPageLayoutElement } from '~/design_management/utils/design_management_utils'; -import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../constants'; Vue.use(VueRouter); @@ -13,20 +10,6 @@ export default function createRouter(base) { mode: 'history', routes, }); - const pageEl = getPageLayoutElement(); - - router.beforeEach(({ name }, _, next) => { - // apply a fullscreen layout style in Design View (a.k.a design detail) - if (pageEl) { - if (name === DESIGN_ROUTE_NAME) { - pageEl.classList.add(...DESIGN_DETAIL_LAYOUT_CLASSLIST); - } else { - pageEl.classList.remove(...DESIGN_DETAIL_LAYOUT_CLASSLIST); - } - } - - next(); - }); return router; } diff --git a/app/assets/javascripts/design_management/utils/cache_update.js b/app/assets/javascripts/design_management/utils/cache_update.js index fc0530ff977..5bd0288d037 100644 --- a/app/assets/javascripts/design_management/utils/cache_update.js +++ b/app/assets/javascripts/design_management/utils/cache_update.js @@ -2,7 +2,7 @@ import { differenceBy } from 'lodash'; import produce from 'immer'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; +import createFlash from '~/flash'; import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils'; import { ADD_IMAGE_DIFF_NOTE_ERROR, @@ -101,7 +101,7 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) = }); }; -const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => { +const updateImageDiffNoteInStore = (store, repositionImageDiffNote, query, variables) => { const sourceData = store.readQuery({ query, variables, @@ -111,12 +111,12 @@ const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables const design = extractDesign(draftData); const discussion = extractCurrentDiscussion( design.discussions, - updateImageDiffNote.note.discussion.id, + repositionImageDiffNote.note.discussion.id, ); discussion.notes = { ...discussion.notes, - nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)], + nodes: [repositionImageDiffNote.note, ...discussion.notes.nodes.slice(1)], }; }); @@ -237,7 +237,7 @@ export const deletePendingTodoFromStore = (store, todoMarkDone, query, queryVari }; const onError = (data, message) => { - createFlash(message); + createFlash({ message }); throw new Error(data.errors); }; @@ -268,7 +268,7 @@ export const updateStoreAfterAddImageDiffNote = (store, data, query, queryVariab } }; -export const updateStoreAfterUpdateImageDiffNote = (store, data, query, queryVariables) => { +export const updateStoreAfterRepositionImageDiffNote = (store, data, query, queryVariables) => { if (hasErrors(data)) { onError(data, UPDATE_IMAGE_DIFF_NOTE_ERROR); } else { @@ -286,7 +286,7 @@ export const updateStoreAfterUploadDesign = (store, data, query) => { export const updateDesignsOnStoreAfterReorder = (store, data, query) => { if (hasErrors(data)) { - createFlash(data.errors[0]); + createFlash({ message: 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 687e793d3df..a905230811c 100644 --- a/app/assets/javascripts/design_management/utils/design_management_utils.js +++ b/app/assets/javascripts/design_management/utils/design_management_utils.js @@ -107,12 +107,12 @@ export const designUploadOptimisticResponse = files => { * @param {Object} note * @param {Object} position */ -export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({ +export const repositionImageDiffNoteOptimisticResponse = (note, { position }) => ({ // 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', - updateImageDiffNote: { - __typename: 'UpdateImageDiffNotePayload', + repositionImageDiffNote: { + __typename: 'RepositionImageDiffNotePayload', note: { ...note, position: { |