diff options
Diffstat (limited to 'app/assets/javascripts/design_management/components')
9 files changed, 349 insertions, 131 deletions
diff --git a/app/assets/javascripts/design_management/components/delete_button.vue b/app/assets/javascripts/design_management/components/delete_button.vue index 37686dd5a46..970197ef41b 100644 --- a/app/assets/javascripts/design_management/components/delete_button.vue +++ b/app/assets/javascripts/design_management/components/delete_button.vue @@ -98,6 +98,7 @@ export default { :loading="loading" :icon="buttonIcon" :disabled="isDeleting || !hasSelectedDesigns" - /> + ><slot></slot + ></gl-button> </div> </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 6a20517eed7..845f1aec8cf 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 @@ -1,19 +1,20 @@ <script> import { ApolloMutation } from 'vue-apollo'; -import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui'; +import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink, GlBadge } from '@gitlab/ui'; import { s__ } from '~/locale'; +import createFlash from '~/flash'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import allVersionsMixin from '../../mixins/all_versions'; import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql'; import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql'; -import getDesignQuery from '../../graphql/queries/get_design.query.graphql'; import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql'; import DesignNote from './design_note.vue'; import DesignReplyForm from './design_reply_form.vue'; -import { updateStoreAfterAddDiscussionComment } from '../../utils/cache_update'; import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants'; import ToggleRepliesWidget from './toggle_replies_widget.vue'; +import { hasErrors } from '../../utils/cache_update'; +import { ADD_DISCUSSION_COMMENT_ERROR } from '../../utils/error_messages'; export default { components: { @@ -26,6 +27,7 @@ export default { GlLink, ToggleRepliesWidget, TimeAgoTooltip, + GlBadge, }, directives: { GlTooltip: GlTooltipDirective, @@ -62,22 +64,20 @@ export default { activeDiscussion: { query: activeDiscussionQuery, result({ data }) { - const discussionId = data.activeDiscussion.id; if (this.discussion.resolved && !this.resolvedDiscussionsExpanded) { return; } - // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists - // We don't want scrollIntoView to be triggered from the discussion click itself - if ( - discussionId && - data.activeDiscussion.source === ACTIVE_DISCUSSION_SOURCE_TYPES.pin && - discussionId === this.discussion.notes[0].id - ) { - this.$el.scrollIntoView({ - behavior: 'smooth', - inline: 'start', - }); - } + + this.$nextTick(() => { + // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists. + // We don't want scrollIntoView to be triggered from the discussion click itself. + if (this.$el && this.shouldScrollToDiscussion(data.activeDiscussion)) { + this.$el.scrollIntoView({ + behavior: 'smooth', + inline: 'start', + }); + } + }); }, }, }, @@ -107,8 +107,8 @@ export default { atVersion: this.designsVersion, }; }, - isDiscussionHighlighted() { - return this.discussion.notes[0].id === this.activeDiscussion.id; + isDiscussionActive() { + return this.discussion.notes.some(({ id }) => id === this.activeDiscussion.id); }, resolveCheckboxText() { return this.discussion.resolved @@ -138,21 +138,10 @@ export default { }, }, methods: { - addDiscussionComment( - store, - { - data: { createNote }, - }, - ) { - updateStoreAfterAddDiscussionComment( - store, - createNote, - getDesignQuery, - this.designVariables, - this.discussion.id, - ); - }, - onDone() { + onDone({ data: { createNote } }) { + if (hasErrors(createNote)) { + createFlash({ message: ADD_DISCUSSION_COMMENT_ERROR }); + } this.discussionComment = ''; this.hideForm(); if (this.shouldChangeResolvedStatus) { @@ -160,14 +149,14 @@ export default { } }, onCreateNoteError(err) { - this.$emit('createNoteError', err); + this.$emit('create-note-error', err); }, hideForm() { this.isFormRendered = false; this.discussionComment = ''; }, showForm() { - this.$emit('openForm', this.discussion.id); + this.$emit('open-form', this.discussion.id); this.isFormRendered = true; }, toggleResolvedStatus() { @@ -179,16 +168,24 @@ export default { }) .then(({ data }) => { if (data.errors?.length > 0) { - this.$emit('resolveDiscussionError', data.errors[0]); + this.$emit('resolve-discussion-error', data.errors[0]); } }) .catch(err => { - this.$emit('resolveDiscussionError', err); + this.$emit('resolve-discussion-error', err); }) .finally(() => { this.isResolving = false; }); }, + shouldScrollToDiscussion(activeDiscussion) { + const ALLOWED_ACTIVE_DISCUSSION_SOURCES = [ + ACTIVE_DISCUSSION_SOURCE_TYPES.pin, + ACTIVE_DISCUSSION_SOURCE_TYPES.url, + ]; + const { source } = activeDiscussion; + return ALLOWED_ACTIVE_DISCUSSION_SOURCES.includes(source) && this.isDiscussionActive; + }, }, createNoteMutation, }; @@ -196,13 +193,12 @@ export default { <template> <div class="design-discussion-wrapper"> - <div - class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center" + <gl-badge + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-cursor-pointer" :class="{ resolved: discussion.resolved }" - type="button" > {{ discussion.index }} - </div> + </gl-badge> <ul class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none" data-qa-selector="design_discussion_content" @@ -211,8 +207,8 @@ export default { :note="firstNote" :markdown-preview-path="markdownPreviewPath" :is-resolving="isResolving" - :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }" - @error="$emit('updateNoteError', $event)" + :class="{ 'gl-bg-blue-50': isDiscussionActive }" + @error="$emit('update-note-error', $event)" > <template v-if="discussion.resolvable" #resolveDiscussion> <button @@ -220,7 +216,6 @@ export default { :class="{ 'is-active': discussion.resolved }" :title="resolveCheckboxText" :aria-label="resolveCheckboxText" - type="button" class="line-resolve-btn note-action-button gl-mr-3" data-testid="resolve-button" @click.stop="toggleResolvedStatus" @@ -255,8 +250,8 @@ export default { :note="note" :markdown-preview-path="markdownPreviewPath" :is-resolving="isResolving" - :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }" - @error="$emit('updateNoteError', $event)" + :class="{ 'gl-bg-blue-50': isDiscussionActive }" + @error="$emit('update-note-error', $event)" /> <li v-show="isReplyPlaceholderVisible" class="reply-wrapper"> <reply-placeholder @@ -272,7 +267,6 @@ export default { :variables="{ input: mutationPayload, }" - :update="addDiscussionComment" @done="onDone" @error="onCreateNoteError" > @@ -280,8 +274,8 @@ export default { v-model="discussionComment" :is-saving="loading" :markdown-preview-path="markdownPreviewPath" - @submitForm="mutate" - @cancelForm="hideForm" + @submit-form="mutate" + @cancel-form="hideForm" > <template v-if="discussion.resolvable" #resolveCheckbox> <label data-testid="resolve-checkbox"> 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 172e61920ef..7f4b3b31024 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 @@ -1,12 +1,12 @@ <script> import { ApolloMutation } from 'vue-apollo'; -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { GlTooltipDirective, GlIcon, GlLink, GlSafeHtmlDirective } from '@gitlab/ui'; import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import DesignReplyForm from './design_reply_form.vue'; -import { findNoteId } from '../../utils/design_management_utils'; +import { findNoteId, extractDesignNoteId } from '../../utils/design_management_utils'; import { hasErrors } from '../../utils/cache_update'; export default { @@ -17,9 +17,11 @@ export default { DesignReplyForm, ApolloMutation, GlIcon, + GlLink, }, directives: { GlTooltip: GlTooltipDirective, + SafeHtml: GlSafeHtmlDirective, }, props: { note: { @@ -46,7 +48,7 @@ export default { return findNoteId(this.note.id); }, isNoteLinked() { - return this.$route.hash === `#note_${this.noteAnchorId}`; + return extractDesignNoteId(this.$route.hash) === this.noteAnchorId; }, mutationPayload() { return { @@ -58,11 +60,6 @@ export default { return !this.isEditing && this.note.userPermissions.adminNote; }, }, - mounted() { - if (this.isNoteLinked) { - this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' }); - } - }, methods: { hideForm() { this.isEditing = false; @@ -87,30 +84,30 @@ export default { :img-alt="author.username" :img-size="40" /> - <div class="d-flex justify-content-between"> + <div class="gl-display-flex gl-justify-content-space-between"> <div> - <a + <gl-link v-once :href="author.webUrl" class="js-user-link" :data-user-id="author.id" :data-username="author.username" > - <span class="note-header-author-name bold">{{ author.name }}</span> - <span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span> + <span class="note-header-author-name gl-font-weight-bold">{{ author.name }}</span> + <span v-if="author.status_tooltip_html" v-safe-html="author.status_tooltip_html"></span> <span class="note-headline-light">@{{ author.username }}</span> - </a> + </gl-link> <span class="note-headline-light note-headline-meta"> <span class="system-note-message"> <slot></slot> </span> - <template v-if="note.createdAt"> - <span class="system-note-separator"></span> - <a class="note-timestamp system-note-separator" :href="`#note_${noteAnchorId}`"> - <time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" /> - </a> - </template> + <gl-link + class="note-timestamp system-note-separator gl-display-block gl-mb-2" + :href="`#note_${noteAnchorId}`" + > + <time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" /> + </gl-link> </span> </div> - <div class="gl-display-flex"> + <div class="gl-display-flex gl-align-items-baseline"> <slot name="resolveDiscussion"></slot> <button v-if="isEditButtonVisible" @@ -126,9 +123,9 @@ export default { </div> <template v-if="!isEditing"> <div + v-safe-html="note.bodyHtml" class="note-text js-note-text md" data-qa-selector="note_content" - v-html="note.bodyHtml" ></div> <slot name="resolvedStatus"></slot> </template> @@ -147,9 +144,9 @@ export default { :is-saving="loading" :markdown-preview-path="markdownPreviewPath" :is-new-comment="false" - class="mt-5" - @submitForm="mutate" - @cancelForm="hideForm" + class="gl-mt-5" + @submit-form="mutate" + @cancel-form="hideForm" /> </apollo-mutation> </timeline-entry-item> 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 969034909f2..3754e1dbbc1 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 @@ -1,5 +1,5 @@ <script> -import { GlDeprecatedButton, GlModal } from '@gitlab/ui'; +import { GlButton, GlModal } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { s__ } from '~/locale'; @@ -7,7 +7,7 @@ export default { name: 'DesignReplyForm', components: { MarkdownField, - GlDeprecatedButton, + GlButton, GlModal, }, props: { @@ -66,13 +66,13 @@ export default { }, methods: { submitForm() { - if (this.hasValue) this.$emit('submitForm'); + if (this.hasValue) this.$emit('submit-form'); }, cancelComment() { if (this.hasValue && this.formText !== this.value) { this.$refs.cancelCommentModal.show(); } else { - this.$emit('cancelForm'); + this.$emit('cancel-form'); } }, focusInput() { @@ -112,20 +112,21 @@ export default { </markdown-field> <slot name="resolveCheckbox"></slot> <div class="note-form-actions gl-display-flex gl-justify-content-space-between"> - <gl-deprecated-button + <gl-button ref="submitButton" :disabled="!hasValue || isSaving" + category="primary" variant="success" type="submit" data-track-event="click_button" data-qa-selector="save_comment_button" - @click="$emit('submitForm')" + @click="$emit('submit-form')" > {{ buttonText }} - </gl-deprecated-button> - <gl-deprecated-button ref="cancelButton" @click="cancelComment">{{ + </gl-button> + <gl-button ref="cancelButton" variant="default" category="primary" @click="cancelComment">{{ __('Cancel') - }}</gl-deprecated-button> + }}</gl-button> </div> <gl-modal ref="cancelCommentModal" @@ -134,7 +135,7 @@ export default { :ok-title="modalSettings.okTitle" :cancel-title="modalSettings.cancelTitle" modal-id="cancel-comment-modal" - @ok="$emit('cancelForm')" + @ok="$emit('cancel-form')" >{{ modalSettings.content }} </gl-modal> </form> diff --git a/app/assets/javascripts/design_management/components/design_overlay.vue b/app/assets/javascripts/design_management/components/design_overlay.vue index 926e7c74802..5c4a3ab5f94 100644 --- a/app/assets/javascripts/design_management/components/design_overlay.vue +++ b/app/assets/javascripts/design_management/components/design_overlay.vue @@ -1,4 +1,5 @@ <script> +import { __ } from '~/locale'; import activeDiscussionQuery from '../graphql/queries/active_discussion.query.graphql'; import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql'; import DesignNotePin from './design_note_pin.vue'; @@ -236,18 +237,26 @@ export default { }); }, isNoteInactive(note) { - return this.activeDiscussion.id && this.activeDiscussion.id !== note.id; + const discussionNotes = note.discussion.notes.nodes || []; + + return ( + this.activeDiscussion.id && + !discussionNotes.some(({ id }) => id === this.activeDiscussion.id) + ); }, designPinClass(note) { return { inactive: this.isNoteInactive(note), resolved: note.resolved }; }, }, + i18n: { + newCommentButtonLabel: __('Add comment to design'), + }, }; </script> <template> <div - class="position-absolute image-diff-overlay frame" + class="gl-absolute gl-top-0 gl-left-0 frame" :style="overlayStyle" @mousemove="onOverlayMousemove" @mouseleave="onNoteMouseup" @@ -255,26 +264,28 @@ export default { <button v-show="!disableCommenting" type="button" - class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button" + role="button" + :aria-label="$options.i18n.newCommentButtonLabel" + class="gl-absolute gl-w-full gl-h-full gl-p-0 gl-top-0 gl-left-0 gl-outline-0! btn-transparent design-detail-overlay-add-comment" data-qa-selector="design_image_button" @mouseup="onAddCommentMouseup" ></button> - <template v-for="note in notes"> - <design-note-pin - v-if="resolvedDiscussionsExpanded || !note.resolved" - :key="note.id" - :label="note.index" - :repositioning="isMovingNote(note.id)" - :position=" - isMovingNote(note.id) && movingNoteNewPosition - ? getNotePositionStyle(movingNoteNewPosition) - : getNotePositionStyle(note.position) - " - :class="designPinClass(note)" - @mousedown.stop="onNoteMousedown($event, note)" - @mouseup.stop="onNoteMouseup(note)" - /> - </template> + + <design-note-pin + v-for="note in notes" + v-if="resolvedDiscussionsExpanded || !note.resolved" + :key="note.id" + :label="note.index" + :repositioning="isMovingNote(note.id)" + :position=" + isMovingNote(note.id) && movingNoteNewPosition + ? getNotePositionStyle(movingNoteNewPosition) + : getNotePositionStyle(note.position) + " + :class="designPinClass(note)" + @mousedown.stop="onNoteMousedown($event, note)" + @mouseup.stop="onNoteMouseup(note)" + /> <design-note-pin v-if="currentCommentForm" diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue index e5a3590877e..df425e3b96d 100644 --- a/app/assets/javascripts/design_management/components/design_sidebar.vue +++ b/app/assets/javascripts/design_management/components/design_sidebar.vue @@ -8,6 +8,8 @@ import { extractDiscussions, extractParticipants } from '../utils/design_managem import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants'; import DesignDiscussion from './design_notes/design_discussion.vue'; import Participants from '~/sidebar/components/participants/participants.vue'; +import DesignTodoButton from './design_todo_button.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { components: { @@ -16,7 +18,9 @@ export default { GlCollapse, GlButton, GlPopover, + DesignTodoButton, }, + mixins: [glFeatureFlagsMixin()], props: { design: { type: Object, @@ -37,6 +41,14 @@ export default { discussionWithOpenForm: '', }; }, + inject: { + projectPath: { + default: '', + }, + issueIid: { + default: '', + }, + }, computed: { discussions() { return extractDiscussions(this.design.discussions); @@ -59,6 +71,26 @@ export default { resolvedCommentsToggleIcon() { return this.resolvedDiscussionsExpanded ? 'chevron-down' : 'chevron-right'; }, + showTodoButton() { + return this.glFeatures.designManagementTodoButton; + }, + sidebarWrapperClass() { + return { + 'gl-pt-0': this.showTodoButton, + }; + }, + }, + watch: { + isResolvedCommentsPopoverHidden(newVal) { + if (!newVal) { + this.$refs.resolvedComments.scrollIntoView(); + } + }, + }, + mounted() { + if (!this.isResolvedCommentsPopoverHidden && this.$refs.resolvedComments) { + this.$refs.resolvedComments.$el.scrollIntoView(); + } }, methods: { handleSidebarClick() { @@ -89,7 +121,14 @@ export default { </script> <template> - <div class="image-notes" @click="handleSidebarClick"> + <div class="image-notes" :class="sidebarWrapperClass" @click="handleSidebarClick"> + <div + v-if="showTodoButton" + class="gl-py-4 gl-mb-4 gl-display-flex gl-justify-content-space-between gl-align-items-center gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" + > + <span>{{ __('To-Do') }}</span> + <design-todo-button :design="design" @error="$emit('todoError', $event)" /> + </div> <h2 class="gl-font-weight-bold gl-mt-0"> {{ issue.title }} </h2> @@ -120,15 +159,16 @@ export default { :resolved-discussions-expanded="resolvedDiscussionsExpanded" :discussion-with-open-form="discussionWithOpenForm" data-testid="unresolved-discussion" - @createNoteError="$emit('onDesignDiscussionError', $event)" - @updateNoteError="$emit('updateNoteError', $event)" - @resolveDiscussionError="$emit('resolveDiscussionError', $event)" + @create-note-error="$emit('onDesignDiscussionError', $event)" + @update-note-error="$emit('updateNoteError', $event)" + @resolve-discussion-error="$emit('resolveDiscussionError', $event)" @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" - @openForm="updateDiscussionWithOpenForm" + @open-form="updateDiscussionWithOpenForm" /> <template v-if="resolvedDiscussions.length > 0"> <gl-button id="resolved-comments" + ref="resolvedComments" data-testid="resolved-comments" :icon="resolvedCommentsToggleIcon" variant="link" @@ -151,9 +191,12 @@ export default { ) }} </p> - <a href="#" rel="noopener noreferrer" target="_blank">{{ - s__('DesignManagement|Learn more about resolving comments') - }}</a> + <a + href="https://docs.gitlab.com/ee/user/project/issues/design_management.html#resolve-design-threads" + rel="noopener noreferrer" + target="_blank" + >{{ s__('DesignManagement|Learn more about resolving comments') }}</a + > </gl-popover> <gl-collapse :visible="resolvedDiscussionsExpanded" class="gl-mt-3"> <design-discussion diff --git a/app/assets/javascripts/design_management/components/design_todo_button.vue b/app/assets/javascripts/design_management/components/design_todo_button.vue new file mode 100644 index 00000000000..aff4f348d15 --- /dev/null +++ b/app/assets/javascripts/design_management/components/design_todo_button.vue @@ -0,0 +1,168 @@ +<script> +import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql'; +import getDesignQuery from '../graphql/queries/get_design.query.graphql'; +import createDesignTodoMutation from '../graphql/mutations/create_design_todo.mutation.graphql'; +import TodoButton from '~/vue_shared/components/todo_button.vue'; +import allVersionsMixin from '../mixins/all_versions'; +import { updateStoreAfterDeleteDesignTodo } from '../utils/cache_update'; +import { findIssueId, findDesignId } from '../utils/design_management_utils'; +import { CREATE_DESIGN_TODO_ERROR, DELETE_DESIGN_TODO_ERROR } from '../utils/error_messages'; + +export default { + components: { + TodoButton, + }, + mixins: [allVersionsMixin], + props: { + design: { + type: Object, + required: true, + }, + }, + inject: { + projectPath: { + default: '', + }, + issueIid: { + default: '', + }, + }, + data() { + return { + todoLoading: false, + }; + }, + computed: { + designVariables() { + return { + fullPath: this.projectPath, + iid: this.issueIid, + filenames: [this.$route.params.id], + atVersion: this.designsVersion, + }; + }, + designTodoVariables() { + return { + projectPath: this.projectPath, + issueId: findIssueId(this.design.issue.id), + designId: findDesignId(this.design.id), + issueIid: this.issueIid, + filenames: [this.$route.params.id], + atVersion: this.designsVersion, + }; + }, + pendingTodo() { + // TODO data structure pending BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555#note_405732940 + return this.design.currentUserTodos?.nodes[0]; + }, + hasPendingTodo() { + return Boolean(this.pendingTodo); + }, + }, + methods: { + updateGlobalTodoCount(additionalTodoCount) { + const currentCount = parseInt(document.querySelector('.js-todos-count').innerText, 10); + const todoToggleEvent = new CustomEvent('todo:toggle', { + detail: { + count: Math.max(currentCount + additionalTodoCount, 0), + }, + }); + + document.dispatchEvent(todoToggleEvent); + }, + incrementGlobalTodoCount() { + this.updateGlobalTodoCount(1); + }, + decrementGlobalTodoCount() { + this.updateGlobalTodoCount(-1); + }, + createTodo() { + this.todoLoading = true; + return this.$apollo + .mutate({ + mutation: createDesignTodoMutation, + variables: this.designTodoVariables, + update: (store, { data: { createDesignTodo } }) => { + // because this is a @client mutation, + // we control what is in errors, and therefore + // we are certain that there is at most 1 item in the array + const createDesignTodoError = (createDesignTodo.errors || [])[0]; + if (createDesignTodoError) { + this.$emit('error', Error(createDesignTodoError.message)); + } + }, + }) + .then(() => { + this.incrementGlobalTodoCount(); + }) + .catch(err => { + this.$emit('error', Error(CREATE_DESIGN_TODO_ERROR)); + throw err; + }) + .finally(() => { + this.todoLoading = false; + }); + }, + deleteTodo() { + if (!this.hasPendingTodo) return Promise.reject(); + + const { id } = this.pendingTodo; + const { designVariables } = this; + + this.todoLoading = true; + return this.$apollo + .mutate({ + mutation: todoMarkDoneMutation, + variables: { + id, + }, + update( + store, + { + data: { todoMarkDone }, + }, + ) { + const todoMarkDoneFirstError = (todoMarkDone.errors || [])[0]; + if (todoMarkDoneFirstError) { + this.$emit('error', Error(todoMarkDoneFirstError)); + } else { + updateStoreAfterDeleteDesignTodo( + store, + todoMarkDone, + getDesignQuery, + designVariables, + ); + } + }, + }) + .then(() => { + this.decrementGlobalTodoCount(); + }) + .catch(err => { + this.$emit('error', Error(DELETE_DESIGN_TODO_ERROR)); + throw err; + }) + .finally(() => { + this.todoLoading = false; + }); + }, + toggleTodo() { + if (this.hasPendingTodo) { + return this.deleteTodo(); + } + + return this.createTodo(); + }, + }, +}; +</script> + +<template> + <todo-button + issuable-type="design" + :issuable-id="design.iid" + :is-todo="hasPendingTodo" + :loading="todoLoading" + @click.stop.prevent="toggleTodo" + /> +</template> diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue index 292b6e09055..36ea812d92e 100644 --- a/app/assets/javascripts/design_management/components/list/item.vue +++ b/app/assets/javascripts/design_management/components/list/item.vue @@ -1,6 +1,5 @@ <script> import { GlLoadingIcon, GlIcon, GlIntersectionObserver } from '@gitlab/ui'; -import Icon from '~/vue_shared/components/icon.vue'; import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import { n__, __ } from '~/locale'; import { DESIGN_ROUTE_NAME } from '../../router/constants'; @@ -10,7 +9,6 @@ export default { GlLoadingIcon, GlIntersectionObserver, GlIcon, - Icon, Timeago, }, props: { @@ -127,12 +125,14 @@ export default { params: { id: filename }, query: $route.query, }" - class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new" + class="card gl-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" data-testid="designEvent" class="design-event position-absolute"> + <div + class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative" + > + <div v-if="icon.name" data-testid="designEvent" class="design-event gl-absolute"> <span :title="icon.tooltip" :aria-label="icon.tooltip"> - <icon :name="icon.name" :size="18" :class="icon.classes" /> + <gl-icon :name="icon.name" :size="18" :class="icon.classes" /> </span> </div> <gl-intersection-observer @appear="onAppear"> @@ -147,25 +147,28 @@ export default { v-show="showImage" :src="imageLink" :alt="filename" - class="block mx-auto mw-100 mh-100 design-img" + class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img" data-qa-selector="design_image" @load="onImageLoad" @error="onImageError" /> </gl-intersection-observer> </div> - <div class="card-footer d-flex w-100"> - <div class="d-flex flex-column str-truncated-100"> - <span class="bold str-truncated-100" data-qa-selector="design_file_name">{{ + <div class="card-footer gl-display-flex gl-w-full"> + <div class="gl-display-flex gl-flex-direction-column str-truncated-100"> + <span class="gl-font-weight-bold str-truncated-100" data-qa-selector="design_file_name">{{ filename }}</span> <span v-if="updatedAt" class="str-truncated-100"> {{ __('Updated') }} <timeago :time="updatedAt" tooltip-placement="bottom" /> </span> </div> - <div v-if="notesCount" class="ml-auto d-flex align-items-center text-secondary"> - <icon name="comments" class="ml-1" /> - <span :aria-label="notesLabel" class="ml-1"> + <div + v-if="notesCount" + class="gl-ml-auto gl-display-flex gl-align-items-center gl-text-gray-500" + > + <gl-icon name="comments" class="gl-ml-2" /> + <span :aria-label="notesLabel" class="gl-ml-2"> {{ notesCount }} </span> </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 a03982cb91b..4a1be7b720a 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,13 @@ <script> -import { GlNewDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; import allVersionsMixin from '../../mixins/all_versions'; import { findVersionId } from '../../utils/design_management_utils'; export default { components: { - GlNewDropdown, - GlNewDropdownItem, + GlDropdown, + GlDropdownItem, GlSprintf, }, mixins: [allVersionsMixin], @@ -63,8 +63,8 @@ export default { </script> <template> - <gl-new-dropdown :text="dropdownText" size="small"> - <gl-new-dropdown-item + <gl-dropdown :text="dropdownText" size="small"> + <gl-dropdown-item v-for="(version, index) in allVersions" :key="version.id" :is-check-item="true" @@ -76,6 +76,6 @@ export default { {{ allVersions.length - index }} </template> </gl-sprintf> - </gl-new-dropdown-item> - </gl-new-dropdown> + </gl-dropdown-item> + </gl-dropdown> </template> |