diff options
Diffstat (limited to 'app/assets/javascripts/design_management_new')
54 files changed, 0 insertions, 4311 deletions
diff --git a/app/assets/javascripts/design_management_new/components/app.vue b/app/assets/javascripts/design_management_new/components/app.vue deleted file mode 100644 index 98240aef810..00000000000 --- a/app/assets/javascripts/design_management_new/components/app.vue +++ /dev/null @@ -1,3 +0,0 @@ -<template> - <router-view /> -</template> diff --git a/app/assets/javascripts/design_management_new/components/delete_button.vue b/app/assets/javascripts/design_management_new/components/delete_button.vue deleted file mode 100644 index 77e1b97a227..00000000000 --- a/app/assets/javascripts/design_management_new/components/delete_button.vue +++ /dev/null @@ -1,81 +0,0 @@ -<script> -import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui'; -import { uniqueId } from 'lodash'; -import { s__ } from '~/locale'; - -export default { - name: 'DeleteButton', - components: { - GlButton, - GlModal, - }, - directives: { - GlModalDirective, - }, - props: { - isDeleting: { - type: Boolean, - required: false, - default: false, - }, - buttonClass: { - type: String, - required: false, - default: '', - }, - buttonVariant: { - type: String, - required: false, - default: 'info', - }, - buttonSize: { - type: String, - required: false, - default: 'medium', - }, - hasSelectedDesigns: { - type: Boolean, - required: false, - default: true, - }, - }, - data() { - return { - modalId: uniqueId('design-deletion-confirmation-'), - }; - }, - modal: { - title: s__('DesignManagement|Delete designs confirmation'), - actionPrimary: { - text: s__('Delete'), - attributes: { variant: 'danger' }, - }, - actionCancel: { - text: s__('Cancel'), - }, - }, -}; -</script> - -<template> - <div class="gl-display-flex gl-align-items-center gl-h-full"> - <gl-modal - :modal-id="modalId" - :title="$options.modal.title" - :action-primary="$options.modal.actionPrimary" - :action-cancel="$options.modal.actionCancel" - @ok="$emit('deleteSelectedDesigns')" - > - <p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p> - </gl-modal> - <gl-button - v-gl-modal-directive="modalId" - :variant="buttonVariant" - :size="buttonSize" - :class="buttonClass" - :disabled="isDeleting || !hasSelectedDesigns" - > - <slot></slot> - </gl-button> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_destroyer.vue b/app/assets/javascripts/design_management_new/components/design_destroyer.vue deleted file mode 100644 index 7ae569216f0..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_destroyer.vue +++ /dev/null @@ -1,67 +0,0 @@ -<script> -import { ApolloMutation } from 'vue-apollo'; -import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; -import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql'; -import { updateStoreAfterDesignsDelete } from '../utils/cache_update'; - -export default { - components: { - ApolloMutation, - }, - props: { - filenames: { - type: Array, - required: true, - }, - }, - inject: { - projectPath: { - default: '', - }, - iid: { - from: 'issueIid', - defaut: '', - }, - }, - computed: { - projectQueryBody() { - return { - query: getDesignListQuery, - variables: { fullPath: this.projectPath, iid: this.iid, atVersion: null }, - }; - }, - }, - methods: { - updateStoreAfterDelete( - store, - { - data: { designManagementDelete }, - }, - ) { - updateStoreAfterDesignsDelete( - store, - designManagementDelete, - this.projectQueryBody, - this.filenames, - ); - }, - }, - destroyDesignMutation, -}; -</script> - -<template> - <apollo-mutation - #default="{ mutate, loading, error }" - :mutation="$options.destroyDesignMutation" - :variables="{ - filenames, - projectPath, - iid, - }" - :update="updateStoreAfterDelete" - v-on="$listeners" - > - <slot v-bind="{ mutate, loading, error }"></slot> - </apollo-mutation> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_note_pin.vue b/app/assets/javascripts/design_management_new/components/design_note_pin.vue deleted file mode 100644 index 0811397fbad..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_note_pin.vue +++ /dev/null @@ -1,61 +0,0 @@ -<script> -import { __, sprintf } from '~/locale'; -import Icon from '~/vue_shared/components/icon.vue'; - -export default { - name: 'DesignNotePin', - components: { - Icon, - }, - props: { - position: { - type: Object, - required: true, - }, - label: { - type: Number, - required: false, - default: null, - }, - repositioning: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - isNewNote() { - return this.label === null; - }, - pinStyle() { - return this.repositioning ? { ...this.position, cursor: 'move' } : this.position; - }, - pinLabel() { - return this.isNewNote - ? __('Comment form position') - : sprintf(__("Comment '%{label}' position"), { label: this.label }); - }, - }, -}; -</script> - -<template> - <button - :style="pinStyle" - :aria-label="pinLabel" - :class="{ - '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" - type="button" - @mousedown="$emit('mousedown', $event)" - @mouseup="$emit('mouseup', $event)" - @click="$emit('click', $event)" - > - <icon v-if="isNewNote" name="image-comment-dark" /> - <template v-else> - {{ label }} - </template> - </button> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue deleted file mode 100644 index 4aaf43e3a5b..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_notes/design_discussion.vue +++ /dev/null @@ -1,297 +0,0 @@ -<script> -import { ApolloMutation } from 'vue-apollo'; -import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui'; -import { s__ } from '~/locale'; -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'; - -export default { - components: { - ApolloMutation, - DesignNote, - ReplyPlaceholder, - DesignReplyForm, - GlIcon, - GlLoadingIcon, - GlLink, - ToggleRepliesWidget, - TimeAgoTooltip, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [allVersionsMixin], - props: { - discussion: { - type: Object, - required: true, - }, - noteableId: { - type: String, - required: true, - }, - designId: { - type: String, - required: true, - }, - markdownPreviewPath: { - type: String, - required: false, - default: '', - }, - resolvedDiscussionsExpanded: { - type: Boolean, - required: true, - }, - discussionWithOpenForm: { - type: String, - required: true, - }, - }, - apollo: { - 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', - }); - } - }, - }, - }, - data() { - return { - discussionComment: '', - isFormRendered: false, - activeDiscussion: {}, - isResolving: false, - shouldChangeResolvedStatus: false, - areRepliesCollapsed: this.discussion.resolved, - }; - }, - computed: { - mutationPayload() { - return { - noteableId: this.noteableId, - body: this.discussionComment, - discussionId: this.discussion.id, - }; - }, - designVariables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - filenames: [this.$route.params.id], - atVersion: this.designsVersion, - }; - }, - isDiscussionHighlighted() { - return this.discussion.notes[0].id === this.activeDiscussion.id; - }, - resolveCheckboxText() { - return this.discussion.resolved - ? s__('DesignManagement|Unresolve thread') - : s__('DesignManagement|Resolve thread'); - }, - firstNote() { - return this.discussion.notes[0]; - }, - discussionReplies() { - return this.discussion.notes.slice(1); - }, - areRepliesShown() { - return !this.discussion.resolved || !this.areRepliesCollapsed; - }, - resolveIconName() { - return this.discussion.resolved ? 'check-circle-filled' : 'check-circle'; - }, - isRepliesWidgetVisible() { - return this.discussion.resolved && this.discussionReplies.length > 0; - }, - isReplyPlaceholderVisible() { - return this.areRepliesShown || !this.discussionReplies.length; - }, - isFormVisible() { - return this.isFormRendered && this.discussionWithOpenForm === this.discussion.id; - }, - }, - methods: { - addDiscussionComment( - store, - { - data: { createNote }, - }, - ) { - updateStoreAfterAddDiscussionComment( - store, - createNote, - getDesignQuery, - this.designVariables, - this.discussion.id, - ); - }, - onDone() { - this.discussionComment = ''; - this.hideForm(); - if (this.shouldChangeResolvedStatus) { - this.toggleResolvedStatus(); - } - }, - onCreateNoteError(err) { - this.$emit('createNoteError', err); - }, - hideForm() { - this.isFormRendered = false; - this.discussionComment = ''; - }, - showForm() { - this.$emit('openForm', this.discussion.id); - this.isFormRendered = true; - }, - toggleResolvedStatus() { - this.isResolving = true; - this.$apollo - .mutate({ - mutation: toggleResolveDiscussionMutation, - variables: { id: this.discussion.id, resolve: !this.discussion.resolved }, - }) - .then(({ data }) => { - if (data.errors?.length > 0) { - this.$emit('resolveDiscussionError', data.errors[0]); - } - }) - .catch(err => { - this.$emit('resolveDiscussionError', err); - }) - .finally(() => { - this.isResolving = false; - }); - }, - }, - createNoteMutation, -}; -</script> - -<template> - <div class="design-discussion-wrapper"> - <div - class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center" - :class="{ resolved: discussion.resolved }" - type="button" - > - {{ discussion.index }} - </div> - <ul - class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none" - data-qa-selector="design_discussion_content" - > - <design-note - :note="firstNote" - :markdown-preview-path="markdownPreviewPath" - :is-resolving="isResolving" - :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }" - @error="$emit('updateNoteError', $event)" - > - <template v-if="discussion.resolvable" #resolveDiscussion> - <button - v-gl-tooltip - :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" - > - <gl-icon v-if="!isResolving" :name="resolveIconName" data-testid="resolve-icon" /> - <gl-loading-icon v-else inline /> - </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"> - {{ __('Resolved by') }} - <gl-link - class="gl-text-gray-700 gl-text-decoration-none gl-font-sm link-inherit-color" - :href="discussion.resolvedBy.webUrl" - target="_blank" - >{{ discussion.resolvedBy.name }}</gl-link - > - <time-ago-tooltip :time="discussion.resolvedAt" tooltip-placement="bottom" /> - </p> - </template> - </design-note> - <toggle-replies-widget - v-if="isRepliesWidgetVisible" - :collapsed="areRepliesCollapsed" - :replies="discussionReplies" - @toggle="areRepliesCollapsed = !areRepliesCollapsed" - /> - <design-note - v-for="note in discussionReplies" - v-show="areRepliesShown" - :key="note.id" - :note="note" - :markdown-preview-path="markdownPreviewPath" - :is-resolving="isResolving" - :class="{ 'gl-bg-blue-50': isDiscussionHighlighted }" - @error="$emit('updateNoteError', $event)" - /> - <li v-show="isReplyPlaceholderVisible" class="reply-wrapper"> - <reply-placeholder - v-if="!isFormVisible" - class="qa-discussion-reply" - :button-text="__('Reply...')" - @onClick="showForm" - /> - <apollo-mutation - v-else - #default="{ mutate, loading }" - :mutation="$options.createNoteMutation" - :variables="{ - input: mutationPayload, - }" - :update="addDiscussionComment" - @done="onDone" - @error="onCreateNoteError" - > - <design-reply-form - v-model="discussionComment" - :is-saving="loading" - :markdown-preview-path="markdownPreviewPath" - @submitForm="mutate" - @cancelForm="hideForm" - > - <template v-if="discussion.resolvable" #resolveCheckbox> - <label data-testid="resolve-checkbox"> - <input v-model="shouldChangeResolvedStatus" type="checkbox" /> - {{ resolveCheckboxText }} - </label> - </template> - </design-reply-form> - </apollo-mutation> - </li> - </ul> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue deleted file mode 100644 index 172e61920ef..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_notes/design_note.vue +++ /dev/null @@ -1,156 +0,0 @@ -<script> -import { ApolloMutation } from 'vue-apollo'; -import { GlTooltipDirective, GlIcon } 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 { hasErrors } from '../../utils/cache_update'; - -export default { - components: { - UserAvatarLink, - TimelineEntryItem, - TimeAgoTooltip, - DesignReplyForm, - ApolloMutation, - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - note: { - type: Object, - required: true, - }, - markdownPreviewPath: { - type: String, - required: false, - default: '', - }, - }, - data() { - return { - noteText: this.note.body, - isEditing: false, - }; - }, - computed: { - author() { - return this.note.author; - }, - noteAnchorId() { - return findNoteId(this.note.id); - }, - isNoteLinked() { - return this.$route.hash === `#note_${this.noteAnchorId}`; - }, - mutationPayload() { - return { - id: this.note.id, - body: this.noteText, - }; - }, - isEditButtonVisible() { - return !this.isEditing && this.note.userPermissions.adminNote; - }, - }, - mounted() { - if (this.isNoteLinked) { - this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' }); - } - }, - methods: { - hideForm() { - this.isEditing = false; - this.noteText = this.note.body; - }, - onDone({ data }) { - this.hideForm(); - if (hasErrors(data.updateNote)) { - this.$emit('error', data.errors[0]); - } - }, - }, - updateNoteMutation, -}; -</script> - -<template> - <timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form"> - <user-avatar-link - :link-href="author.webUrl" - :img-src="author.avatarUrl" - :img-alt="author.username" - :img-size="40" - /> - <div class="d-flex justify-content-between"> - <div> - <a - 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-headline-light">@{{ author.username }}</span> - </a> - <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> - </span> - </div> - <div class="gl-display-flex"> - <slot name="resolveDiscussion"></slot> - <button - v-if="isEditButtonVisible" - v-gl-tooltip - type="button" - :title="__('Edit comment')" - class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button" - @click="isEditing = true" - > - <gl-icon name="pencil" class="link-highlight" /> - </button> - </div> - </div> - <template v-if="!isEditing"> - <div - class="note-text js-note-text md" - data-qa-selector="note_content" - v-html="note.bodyHtml" - ></div> - <slot name="resolvedStatus"></slot> - </template> - <apollo-mutation - v-else - #default="{ mutate, loading }" - :mutation="$options.updateNoteMutation" - :variables="{ - input: mutationPayload, - }" - @error="$emit('error', $event)" - @done="onDone" - > - <design-reply-form - v-model="noteText" - :is-saving="loading" - :markdown-preview-path="markdownPreviewPath" - :is-new-comment="false" - class="mt-5" - @submitForm="mutate" - @cancelForm="hideForm" - /> - </apollo-mutation> - </timeline-entry-item> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue deleted file mode 100644 index 969034909f2..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_notes/design_reply_form.vue +++ /dev/null @@ -1,141 +0,0 @@ -<script> -import { GlDeprecatedButton, GlModal } from '@gitlab/ui'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { s__ } from '~/locale'; - -export default { - name: 'DesignReplyForm', - components: { - MarkdownField, - GlDeprecatedButton, - GlModal, - }, - props: { - markdownPreviewPath: { - type: String, - required: false, - default: '', - }, - value: { - type: String, - required: true, - }, - isSaving: { - type: Boolean, - required: true, - }, - isNewComment: { - type: Boolean, - required: false, - default: true, - }, - }, - data() { - return { - formText: this.value, - }; - }, - computed: { - hasValue() { - return this.value.trim().length > 0; - }, - modalSettings() { - if (this.isNewComment) { - return { - title: s__('DesignManagement|Cancel comment confirmation'), - okTitle: s__('DesignManagement|Discard comment'), - cancelTitle: s__('DesignManagement|Keep comment'), - content: s__('DesignManagement|Are you sure you want to cancel creating this comment?'), - }; - } - return { - title: s__('DesignManagement|Cancel comment update confirmation'), - okTitle: s__('DesignManagement|Cancel changes'), - cancelTitle: s__('DesignManagement|Keep changes'), - content: s__('DesignManagement|Are you sure you want to cancel changes to this comment?'), - }; - }, - buttonText() { - return this.isNewComment - ? s__('DesignManagement|Comment') - : s__('DesignManagement|Save comment'); - }, - }, - mounted() { - this.focusInput(); - }, - methods: { - submitForm() { - if (this.hasValue) this.$emit('submitForm'); - }, - cancelComment() { - if (this.hasValue && this.formText !== this.value) { - this.$refs.cancelCommentModal.show(); - } else { - this.$emit('cancelForm'); - } - }, - focusInput() { - this.$refs.textarea.focus(); - }, - }, -}; -</script> - -<template> - <form class="new-note common-note-form" @submit.prevent> - <markdown-field - :markdown-preview-path="markdownPreviewPath" - :can-attach-file="false" - :enable-autocomplete="true" - :textarea-value="value" - markdown-docs-path="/help/user/markdown" - class="bordered-box" - > - <template #textarea> - <textarea - ref="textarea" - :value="value" - class="note-textarea js-gfm-input js-autosize markdown-area" - dir="auto" - data-supports-quick-actions="false" - data-qa-selector="note_textarea" - :aria-label="__('Description')" - :placeholder="__('Write a comment…')" - @input="$emit('input', $event.target.value)" - @keydown.meta.enter="submitForm" - @keydown.ctrl.enter="submitForm" - @keyup.esc.stop="cancelComment" - > - </textarea> - </template> - </markdown-field> - <slot name="resolveCheckbox"></slot> - <div class="note-form-actions gl-display-flex gl-justify-content-space-between"> - <gl-deprecated-button - ref="submitButton" - :disabled="!hasValue || isSaving" - variant="success" - type="submit" - data-track-event="click_button" - data-qa-selector="save_comment_button" - @click="$emit('submitForm')" - > - {{ buttonText }} - </gl-deprecated-button> - <gl-deprecated-button ref="cancelButton" @click="cancelComment">{{ - __('Cancel') - }}</gl-deprecated-button> - </div> - <gl-modal - ref="cancelCommentModal" - ok-variant="danger" - :title="modalSettings.title" - :ok-title="modalSettings.okTitle" - :cancel-title="modalSettings.cancelTitle" - modal-id="cancel-comment-modal" - @ok="$emit('cancelForm')" - >{{ modalSettings.content }} - </gl-modal> - </form> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue b/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue deleted file mode 100644 index 46c73e3eea8..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_notes/toggle_replies_widget.vue +++ /dev/null @@ -1,70 +0,0 @@ -<script> -import { GlIcon, GlButton, GlLink } from '@gitlab/ui'; -import { __, n__ } from '~/locale'; -import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; - -export default { - name: 'ToggleNotesWidget', - components: { - GlIcon, - GlButton, - GlLink, - TimeAgoTooltip, - }, - props: { - collapsed: { - type: Boolean, - required: true, - }, - replies: { - type: Array, - required: true, - }, - }, - computed: { - lastReply() { - return this.replies[this.replies.length - 1]; - }, - iconName() { - return this.collapsed ? 'chevron-right' : 'chevron-down'; - }, - toggleText() { - return this.collapsed - ? `${this.replies.length} ${n__('reply', 'replies', this.replies.length)}` - : __('Collapse replies'); - }, - }, -}; -</script> - -<template> - <li - class="toggle-comments gl-bg-gray-50 gl-display-flex gl-align-items-center gl-py-3" - :class="{ expanded: !collapsed }" - data-testid="toggle-comments-wrapper" - > - <gl-icon :name="iconName" class="gl-ml-3" @click.stop="$emit('toggle')" /> - <gl-button - variant="link" - class="toggle-comments-button gl-ml-2 gl-mr-2" - @click.stop="$emit('toggle')" - > - {{ toggleText }} - </gl-button> - <template v-if="collapsed"> - <span class="gl-text-gray-700">{{ __('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" - > - {{ lastReply.author.name }} - </gl-link> - <time-ago-tooltip - :time="lastReply.createdAt" - tooltip-placement="bottom" - class="gl-text-gray-700" - /> - </template> - </li> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_overlay.vue b/app/assets/javascripts/design_management_new/components/design_overlay.vue deleted file mode 100644 index 926e7c74802..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_overlay.vue +++ /dev/null @@ -1,287 +0,0 @@ -<script> -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'; -import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants'; - -export default { - name: 'DesignOverlay', - components: { - DesignNotePin, - }, - props: { - dimensions: { - type: Object, - required: true, - }, - position: { - type: Object, - required: true, - }, - notes: { - type: Array, - required: false, - default: () => [], - }, - currentCommentForm: { - type: Object, - required: false, - default: null, - }, - disableCommenting: { - type: Boolean, - required: false, - default: false, - }, - resolvedDiscussionsExpanded: { - type: Boolean, - required: true, - }, - }, - apollo: { - activeDiscussion: { - query: activeDiscussionQuery, - }, - }, - data() { - return { - movingNoteNewPosition: null, - movingNoteStartPosition: null, - activeDiscussion: {}, - }; - }, - computed: { - overlayStyle() { - const cursor = this.disableCommenting ? 'unset' : undefined; - - return { - cursor, - width: `${this.dimensions.width}px`, - height: `${this.dimensions.height}px`, - ...this.position, - }; - }, - isMovingCurrentComment() { - return Boolean(this.movingNoteStartPosition && !this.movingNoteStartPosition.noteId); - }, - currentCommentPositionStyle() { - return this.isMovingCurrentComment && this.movingNoteNewPosition - ? this.getNotePositionStyle(this.movingNoteNewPosition) - : this.getNotePositionStyle(this.currentCommentForm); - }, - }, - methods: { - setNewNoteCoordinates({ x, y }) { - this.$emit('openCommentForm', { x, y }); - }, - getNoteRelativePosition(position) { - const { x, y, width, height } = position; - const widthRatio = this.dimensions.width / width; - const heightRatio = this.dimensions.height / height; - return { - left: Math.round(x * widthRatio), - top: Math.round(y * heightRatio), - }; - }, - getNotePositionStyle(position) { - const { left, top } = this.getNoteRelativePosition(position); - return { - left: `${left}px`, - top: `${top}px`, - }; - }, - getMovingNotePositionDelta(e) { - let deltaX = 0; - let deltaY = 0; - - if (this.movingNoteStartPosition) { - const { clientX, clientY } = this.movingNoteStartPosition; - deltaX = e.clientX - clientX; - deltaY = e.clientY - clientY; - } - - return { - deltaX, - deltaY, - }; - }, - isMovingNote(noteId) { - const movingNoteId = this.movingNoteStartPosition?.noteId; - return Boolean(movingNoteId && movingNoteId === noteId); - }, - canMoveNote(note) { - const { userPermissions } = note; - const { adminNote } = userPermissions || {}; - - return Boolean(adminNote); - }, - isPositionInOverlay(position) { - const { top, left } = this.getNoteRelativePosition(position); - const { height, width } = this.dimensions; - - return top >= 0 && top <= height && left >= 0 && left <= width; - }, - onNewNoteMove(e) { - if (!this.isMovingCurrentComment) return; - - const { deltaX, deltaY } = this.getMovingNotePositionDelta(e); - const x = this.currentCommentForm.x + deltaX; - const y = this.currentCommentForm.y + deltaY; - - const movingNoteNewPosition = { - x, - y, - width: this.dimensions.width, - height: this.dimensions.height, - }; - - if (!this.isPositionInOverlay(movingNoteNewPosition)) { - this.onNewNoteMouseup(); - return; - } - - this.movingNoteNewPosition = movingNoteNewPosition; - }, - onExistingNoteMove(e) { - const note = this.notes.find(({ id }) => id === this.movingNoteStartPosition.noteId); - if (!note || !this.canMoveNote(note)) return; - - const { position } = note; - const { width, height } = position; - const widthRatio = this.dimensions.width / width; - const heightRatio = this.dimensions.height / height; - - const { deltaX, deltaY } = this.getMovingNotePositionDelta(e); - const x = position.x * widthRatio + deltaX; - const y = position.y * heightRatio + deltaY; - - const movingNoteNewPosition = { - x, - y, - width: this.dimensions.width, - height: this.dimensions.height, - }; - - if (!this.isPositionInOverlay(movingNoteNewPosition)) { - this.onExistingNoteMouseup(); - return; - } - - this.movingNoteNewPosition = movingNoteNewPosition; - }, - onNewNoteMouseup() { - if (!this.movingNoteNewPosition) return; - - const { x, y } = this.movingNoteNewPosition; - this.setNewNoteCoordinates({ x, y }); - }, - onExistingNoteMouseup(note) { - if (!this.movingNoteStartPosition || !this.movingNoteNewPosition) { - this.updateActiveDiscussion(note.id); - this.$emit('closeCommentForm'); - return; - } - - const { x, y } = this.movingNoteNewPosition; - this.$emit('moveNote', { - noteId: this.movingNoteStartPosition.noteId, - discussionId: this.movingNoteStartPosition.discussionId, - coordinates: { x, y }, - }); - }, - onNoteMousedown({ clientX, clientY }, note) { - this.movingNoteStartPosition = { - noteId: note?.id, - discussionId: note?.discussion.id, - clientX, - clientY, - }; - }, - onOverlayMousemove(e) { - if (!this.movingNoteStartPosition) return; - - if (this.isMovingCurrentComment) { - this.onNewNoteMove(e); - } else { - this.onExistingNoteMove(e); - } - }, - onNoteMouseup(note) { - if (!this.movingNoteStartPosition) return; - - if (this.isMovingCurrentComment) { - this.onNewNoteMouseup(); - } else { - this.onExistingNoteMouseup(note); - } - - this.movingNoteStartPosition = null; - this.movingNoteNewPosition = null; - }, - onAddCommentMouseup({ offsetX, offsetY }) { - if (this.disableCommenting) return; - if (this.activeDiscussion.id) { - this.updateActiveDiscussion(); - } - - this.setNewNoteCoordinates({ x: offsetX, y: offsetY }); - }, - updateActiveDiscussion(id) { - this.$apollo.mutate({ - mutation: updateActiveDiscussionMutation, - variables: { - id, - source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin, - }, - }); - }, - isNoteInactive(note) { - return this.activeDiscussion.id && this.activeDiscussion.id !== note.id; - }, - designPinClass(note) { - return { inactive: this.isNoteInactive(note), resolved: note.resolved }; - }, - }, -}; -</script> - -<template> - <div - class="position-absolute image-diff-overlay frame" - :style="overlayStyle" - @mousemove="onOverlayMousemove" - @mouseleave="onNoteMouseup" - > - <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" - 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-if="currentCommentForm" - :position="currentCommentPositionStyle" - :repositioning="isMovingCurrentComment" - @mousedown.stop="onNoteMousedown" - @mouseup.stop="onNoteMouseup" - /> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_presentation.vue b/app/assets/javascripts/design_management_new/components/design_presentation.vue deleted file mode 100644 index 84dbb2809d9..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_presentation.vue +++ /dev/null @@ -1,322 +0,0 @@ -<script> -import { throttle } from 'lodash'; -import DesignImage from './image.vue'; -import DesignOverlay from './design_overlay.vue'; - -const CLICK_DRAG_BUFFER_PX = 2; - -export default { - components: { - DesignImage, - DesignOverlay, - }, - props: { - image: { - type: String, - required: false, - default: '', - }, - imageName: { - type: String, - required: false, - default: '', - }, - discussions: { - type: Array, - required: true, - }, - isAnnotating: { - type: Boolean, - required: false, - default: false, - }, - scale: { - type: Number, - required: false, - default: 1, - }, - resolvedDiscussionsExpanded: { - type: Boolean, - required: true, - }, - }, - data() { - return { - overlayDimensions: null, - overlayPosition: null, - currentAnnotationPosition: null, - zoomFocalPoint: { - x: 0, - y: 0, - width: 0, - height: 0, - }, - initialLoad: true, - lastDragPosition: null, - isDraggingDesign: false, - }; - }, - computed: { - discussionStartingNotes() { - return this.discussions.map(discussion => ({ - ...discussion.notes[0], - index: discussion.index, - })); - }, - currentCommentForm() { - return (this.isAnnotating && this.currentAnnotationPosition) || null; - }, - presentationStyle() { - return { - cursor: this.isDraggingDesign ? 'grabbing' : undefined, - }; - }, - }, - beforeDestroy() { - const { presentationViewport } = this.$refs; - if (!presentationViewport) return; - - presentationViewport.removeEventListener('scroll', this.scrollThrottled, false); - }, - mounted() { - const { presentationViewport } = this.$refs; - if (!presentationViewport) return; - - this.scrollThrottled = throttle(() => { - this.shiftZoomFocalPoint(); - }, 400); - - presentationViewport.addEventListener('scroll', this.scrollThrottled, false); - }, - methods: { - syncCurrentAnnotationPosition() { - if (!this.currentAnnotationPosition) return; - - const widthRatio = this.overlayDimensions.width / this.currentAnnotationPosition.width; - const heightRatio = this.overlayDimensions.height / this.currentAnnotationPosition.height; - const x = this.currentAnnotationPosition.x * widthRatio; - const y = this.currentAnnotationPosition.y * heightRatio; - - this.currentAnnotationPosition = this.getAnnotationPositon({ x, y }); - }, - setOverlayDimensions(overlayDimensions) { - this.overlayDimensions = overlayDimensions; - - // every time we set overlay dimensions, we need to - // update the current annotation as well - this.syncCurrentAnnotationPosition(); - }, - setOverlayPosition() { - if (!this.overlayDimensions) { - this.overlayPosition = {}; - } - - const { presentationViewport } = this.$refs; - if (!presentationViewport) return; - - // default to center - this.overlayPosition = { - left: `calc(50% - ${this.overlayDimensions.width / 2}px)`, - top: `calc(50% - ${this.overlayDimensions.height / 2}px)`, - }; - - // if the overlay overflows, then don't center - if (this.overlayDimensions.width > presentationViewport.offsetWidth) { - this.overlayPosition.left = '0'; - } - if (this.overlayDimensions.height > presentationViewport.offsetHeight) { - this.overlayPosition.top = '0'; - } - }, - /** - * Return a point that represents the center of an - * overflowing child element w.r.t it's parent - */ - getViewportCenter() { - const { presentationViewport } = this.$refs; - if (!presentationViewport) return {}; - - // get height of scroll bars (i.e. the max values for scrollTop, scrollLeft) - const scrollBarWidth = presentationViewport.scrollWidth - presentationViewport.offsetWidth; - const scrollBarHeight = presentationViewport.scrollHeight - presentationViewport.offsetHeight; - - // determine how many child pixels have been scrolled - const xScrollRatio = - presentationViewport.scrollLeft > 0 ? presentationViewport.scrollLeft / scrollBarWidth : 0; - const yScrollRatio = - presentationViewport.scrollTop > 0 ? presentationViewport.scrollTop / scrollBarHeight : 0; - const xScrollOffset = - (presentationViewport.scrollWidth - presentationViewport.offsetWidth - 0) * xScrollRatio; - const yScrollOffset = - (presentationViewport.scrollHeight - presentationViewport.offsetHeight - 0) * yScrollRatio; - - const viewportCenterX = presentationViewport.offsetWidth / 2; - const viewportCenterY = presentationViewport.offsetHeight / 2; - const focalPointX = viewportCenterX + xScrollOffset; - const focalPointY = viewportCenterY + yScrollOffset; - - return { - x: focalPointX, - y: focalPointY, - }; - }, - /** - * Scroll the viewport such that the focal point is positioned centrally - */ - scrollToFocalPoint() { - const { presentationViewport } = this.$refs; - if (!presentationViewport) return; - - const scrollX = this.zoomFocalPoint.x - presentationViewport.offsetWidth / 2; - const scrollY = this.zoomFocalPoint.y - presentationViewport.offsetHeight / 2; - - presentationViewport.scrollTo(scrollX, scrollY); - }, - scaleZoomFocalPoint() { - const { x, y, width, height } = this.zoomFocalPoint; - const widthRatio = this.overlayDimensions.width / width; - const heightRatio = this.overlayDimensions.height / height; - - this.zoomFocalPoint = { - x: Math.round(x * widthRatio * 100) / 100, - y: Math.round(y * heightRatio * 100) / 100, - ...this.overlayDimensions, - }; - }, - shiftZoomFocalPoint() { - this.zoomFocalPoint = { - ...this.getViewportCenter(), - ...this.overlayDimensions, - }; - }, - onImageResize(imageDimensions) { - this.setOverlayDimensions(imageDimensions); - this.setOverlayPosition(); - - this.$nextTick(() => { - if (this.initialLoad) { - // set focal point on initial load - this.shiftZoomFocalPoint(); - this.initialLoad = false; - } else { - this.scaleZoomFocalPoint(); - this.scrollToFocalPoint(); - } - }); - }, - getAnnotationPositon(coordinates) { - const { x, y } = coordinates; - const { width, height } = this.overlayDimensions; - return { - x: Math.round(x), - y: Math.round(y), - width: Math.round(width), - height: Math.round(height), - }; - }, - openCommentForm(coordinates) { - this.currentAnnotationPosition = this.getAnnotationPositon(coordinates); - this.$emit('openCommentForm', this.currentAnnotationPosition); - }, - closeCommentForm() { - this.currentAnnotationPosition = null; - this.$emit('closeCommentForm'); - }, - moveNote({ noteId, discussionId, coordinates }) { - const position = this.getAnnotationPositon(coordinates); - this.$emit('moveNote', { noteId, discussionId, position }); - }, - onPresentationMousedown({ clientX, clientY }) { - if (!this.isDesignOverflowing()) return; - - this.lastDragPosition = { - x: clientX, - y: clientY, - }; - }, - getDragDelta(clientX, clientY) { - return { - deltaX: this.lastDragPosition.x - clientX, - deltaY: this.lastDragPosition.y - clientY, - }; - }, - exceedsDragThreshold(clientX, clientY) { - const { deltaX, deltaY } = this.getDragDelta(clientX, clientY); - - return Math.abs(deltaX) > CLICK_DRAG_BUFFER_PX || Math.abs(deltaY) > CLICK_DRAG_BUFFER_PX; - }, - shouldDragDesign(clientX, clientY) { - return ( - this.lastDragPosition && - (this.isDraggingDesign || this.exceedsDragThreshold(clientX, clientY)) - ); - }, - onPresentationMousemove({ clientX, clientY }) { - const { presentationViewport } = this.$refs; - if (!presentationViewport || !this.shouldDragDesign(clientX, clientY)) return; - - this.isDraggingDesign = true; - - const { scrollLeft, scrollTop } = presentationViewport; - const { deltaX, deltaY } = this.getDragDelta(clientX, clientY); - presentationViewport.scrollTo(scrollLeft + deltaX, scrollTop + deltaY); - - this.lastDragPosition = { - x: clientX, - y: clientY, - }; - }, - onPresentationMouseup() { - this.lastDragPosition = null; - this.isDraggingDesign = false; - }, - isDesignOverflowing() { - const { presentationViewport } = this.$refs; - if (!presentationViewport) return false; - - return ( - presentationViewport.scrollWidth > presentationViewport.offsetWidth || - presentationViewport.scrollHeight > presentationViewport.offsetHeight - ); - }, - }, -}; -</script> - -<template> - <div - ref="presentationViewport" - class="h-100 w-100 p-3 overflow-auto position-relative" - :style="presentationStyle" - @mousedown="onPresentationMousedown" - @mousemove="onPresentationMousemove" - @mouseup="onPresentationMouseup" - @mouseleave="onPresentationMouseup" - @touchstart="onPresentationMousedown" - @touchmove="onPresentationMousemove" - @touchend="onPresentationMouseup" - @touchcancel="onPresentationMouseup" - > - <div class="h-100 w-100 d-flex align-items-center position-relative"> - <design-image - v-if="image" - :image="image" - :name="imageName" - :scale="scale" - @resize="onImageResize" - /> - <design-overlay - v-if="overlayDimensions && overlayPosition" - :dimensions="overlayDimensions" - :position="overlayPosition" - :notes="discussionStartingNotes" - :current-comment-form="currentCommentForm" - :disable-commenting="isDraggingDesign" - :resolved-discussions-expanded="resolvedDiscussionsExpanded" - @openCommentForm="openCommentForm" - @closeCommentForm="closeCommentForm" - @moveNote="moveNote" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_scaler.vue b/app/assets/javascripts/design_management_new/components/design_scaler.vue deleted file mode 100644 index 55dee74bef5..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_scaler.vue +++ /dev/null @@ -1,65 +0,0 @@ -<script> -import { GlIcon } from '@gitlab/ui'; - -const SCALE_STEP_SIZE = 0.2; -const DEFAULT_SCALE = 1; -const MIN_SCALE = 1; -const MAX_SCALE = 2; - -export default { - components: { - GlIcon, - }, - data() { - return { - scale: DEFAULT_SCALE, - }; - }, - computed: { - disableReset() { - return this.scale <= MIN_SCALE; - }, - disableDecrease() { - return this.scale === DEFAULT_SCALE; - }, - disableIncrease() { - return this.scale >= MAX_SCALE; - }, - }, - methods: { - setScale(scale) { - if (scale < MIN_SCALE) { - return; - } - - this.scale = Math.round(scale * 100) / 100; - this.$emit('scale', this.scale); - }, - incrementScale() { - this.setScale(this.scale + SCALE_STEP_SIZE); - }, - decrementScale() { - this.setScale(this.scale - SCALE_STEP_SIZE); - }, - resetScale() { - this.setScale(DEFAULT_SCALE); - }, - }, -}; -</script> - -<template> - <div class="design-scaler btn-group" role="group"> - <button class="btn" :disabled="disableDecrease" @click="decrementScale"> - <span class="d-flex-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> -</template> diff --git a/app/assets/javascripts/design_management_new/components/design_sidebar.vue b/app/assets/javascripts/design_management_new/components/design_sidebar.vue deleted file mode 100644 index 333ad2557e8..00000000000 --- a/app/assets/javascripts/design_management_new/components/design_sidebar.vue +++ /dev/null @@ -1,178 +0,0 @@ -<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 updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql'; -import { extractDiscussions, extractParticipants } from '../utils/design_management_utils'; -import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants'; -import DesignDiscussion from './design_notes/design_discussion.vue'; -import Participants from '~/sidebar/components/participants/participants.vue'; - -export default { - components: { - DesignDiscussion, - Participants, - GlCollapse, - GlButton, - GlPopover, - }, - props: { - design: { - type: Object, - required: true, - }, - resolvedDiscussionsExpanded: { - type: Boolean, - required: true, - }, - markdownPreviewPath: { - type: String, - required: true, - }, - }, - data() { - return { - isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)), - discussionWithOpenForm: '', - }; - }, - computed: { - discussions() { - return extractDiscussions(this.design.discussions); - }, - issue() { - return { - ...this.design.issue, - webPath: this.design.issue.webPath.substr(1), - }; - }, - discussionParticipants() { - return extractParticipants(this.issue.participants); - }, - resolvedDiscussions() { - return this.discussions.filter(discussion => discussion.resolved); - }, - unresolvedDiscussions() { - return this.discussions.filter(discussion => !discussion.resolved); - }, - resolvedCommentsToggleIcon() { - return this.resolvedDiscussionsExpanded ? 'chevron-down' : 'chevron-right'; - }, - }, - methods: { - handleSidebarClick() { - this.isResolvedCommentsPopoverHidden = true; - Cookies.set(this.$options.cookieKey, 'true', { expires: 365 * 10 }); - this.updateActiveDiscussion(); - }, - updateActiveDiscussion(id) { - this.$apollo.mutate({ - mutation: updateActiveDiscussionMutation, - variables: { - id, - source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion, - }, - }); - }, - closeCommentForm() { - this.comment = ''; - this.$emit('closeCommentForm'); - }, - updateDiscussionWithOpenForm(id) { - this.discussionWithOpenForm = id; - }, - }, - resolveCommentsToggleText: s__('DesignManagement|Resolved Comments'), - cookieKey: 'hide_design_resolved_comments_popover', -}; -</script> - -<template> - <div class="image-notes" @click="handleSidebarClick"> - <h2 class="gl-font-weight-bold gl-mt-0"> - {{ issue.title }} - </h2> - <a - class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block" - :href="issue.webUrl" - >{{ issue.webPath }}</a - > - <participants - :participants="discussionParticipants" - :show-participant-label="false" - class="gl-mb-4" - /> - <h2 - v-if="unresolvedDiscussions.length === 0" - class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4" - data-testid="new-discussion-disclaimer" - > - {{ s__("DesignManagement|Click the image where you'd like to start a new discussion") }} - </h2> - <design-discussion - v-for="discussion in unresolvedDiscussions" - :key="discussion.id" - :discussion="discussion" - :design-id="$route.params.id" - :noteable-id="design.id" - :markdown-preview-path="markdownPreviewPath" - :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)" - @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" - @openForm="updateDiscussionWithOpenForm" - /> - <template v-if="resolvedDiscussions.length > 0"> - <gl-button - id="resolved-comments" - 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" - @click="$emit('toggleResolvedComments')" - >{{ $options.resolveCommentsToggleText }} ({{ resolvedDiscussions.length }}) - </gl-button> - <gl-popover - v-if="!isResolvedCommentsPopoverHidden" - :show="!isResolvedCommentsPopoverHidden" - target="resolved-comments" - container="popovercontainer" - placement="top" - :title="s__('DesignManagement|Resolved Comments')" - > - <p> - {{ - s__( - 'DesignManagement|Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below', - ) - }} - </p> - <a href="#" 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 - v-for="discussion in resolvedDiscussions" - :key="discussion.id" - :discussion="discussion" - :design-id="$route.params.id" - :noteable-id="design.id" - :markdown-preview-path="markdownPreviewPath" - :resolved-discussions-expanded="resolvedDiscussionsExpanded" - :discussion-with-open-form="discussionWithOpenForm" - data-testid="resolved-discussion" - @error="$emit('onDesignDiscussionError', $event)" - @updateNoteError="$emit('updateNoteError', $event)" - @openForm="updateDiscussionWithOpenForm" - @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" - /> - </gl-collapse> - </template> - <slot name="replyForm"></slot> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/image.vue b/app/assets/javascripts/design_management_new/components/image.vue deleted file mode 100644 index 91b7b576e0c..00000000000 --- a/app/assets/javascripts/design_management_new/components/image.vue +++ /dev/null @@ -1,110 +0,0 @@ -<script> -import { throttle } from 'lodash'; -import { GlIcon } from '@gitlab/ui'; - -export default { - components: { - GlIcon, - }, - props: { - image: { - type: String, - required: false, - default: '', - }, - name: { - type: String, - required: false, - default: '', - }, - scale: { - type: Number, - required: false, - default: 1, - }, - }, - data() { - return { - baseImageSize: null, - imageStyle: null, - imageError: false, - }; - }, - watch: { - scale(val) { - this.zoom(val); - }, - }, - beforeDestroy() { - window.removeEventListener('resize', this.resizeThrottled, false); - }, - mounted() { - this.onImgLoad(); - - this.resizeThrottled = throttle(() => { - // NOTE: if imageStyle is set, then baseImageSize - // won't change due to resize. We must still emit a - // `resize` event so that the parent can handle - // resizes appropriately (e.g. for design_overlay) - this.setBaseImageSize(); - }, 400); - window.addEventListener('resize', this.resizeThrottled, false); - }, - methods: { - onImgLoad() { - requestIdleCallback(this.setBaseImageSize, { timeout: 1000 }); - }, - onImgError() { - this.imageError = true; - }, - setBaseImageSize() { - const { contentImg } = this.$refs; - if (!contentImg || contentImg.offsetHeight === 0 || contentImg.offsetWidth === 0) return; - - this.baseImageSize = { - height: contentImg.offsetHeight, - width: contentImg.offsetWidth, - }; - this.onResize({ width: this.baseImageSize.width, height: this.baseImageSize.height }); - }, - onResize({ width, height }) { - this.$emit('resize', { width, height }); - }, - zoom(amount) { - if (amount === 1) { - this.imageStyle = null; - this.$nextTick(() => { - this.setBaseImageSize(); - }); - return; - } - const width = this.baseImageSize.width * amount; - const height = this.baseImageSize.height * amount; - - this.imageStyle = { - width: `${width}px`, - height: `${height}px`, - }; - - this.onResize({ width, height }); - }, - }, -}; -</script> - -<template> - <div class="m-auto js-design-image"> - <gl-icon v-if="imageError" class="text-secondary-100" name="media-broken" :size="48" /> - <img - v-show="!imageError" - ref="contentImg" - class="mh-100" - :src="image" - :alt="name" - :style="imageStyle" - :class="{ 'img-fluid': !imageStyle }" - @error="onImgError" - @load="onImgLoad" - /> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/list/item.vue b/app/assets/javascripts/design_management_new/components/list/item.vue deleted file mode 100644 index b19aef9c22d..00000000000 --- a/app/assets/javascripts/design_management_new/components/list/item.vue +++ /dev/null @@ -1,174 +0,0 @@ -<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'; - -export default { - components: { - GlLoadingIcon, - GlIntersectionObserver, - GlIcon, - Icon, - Timeago, - }, - props: { - id: { - type: [Number, String], - required: true, - }, - event: { - type: String, - required: true, - }, - notesCount: { - type: Number, - required: true, - }, - image: { - type: String, - required: true, - }, - filename: { - type: String, - required: true, - }, - updatedAt: { - type: String, - required: false, - default: null, - }, - isUploading: { - type: Boolean, - required: false, - default: true, - }, - imageV432x230: { - type: String, - required: false, - default: null, - }, - }, - data() { - return { - imageLoading: true, - imageError: false, - wasInView: false, - }; - }, - computed: { - icon() { - const normalizedEvent = this.event.toLowerCase(); - const icons = { - creation: { - name: 'file-addition-solid', - classes: 'text-success-500', - tooltip: __('Added in this version'), - }, - modification: { - name: 'file-modified-solid', - classes: 'text-primary-500', - tooltip: __('Modified in this version'), - }, - deletion: { - name: 'file-deletion-solid', - classes: 'text-danger-500', - tooltip: __('Deleted in this version'), - }, - }; - - return icons[normalizedEvent] ? icons[normalizedEvent] : {}; - }, - notesLabel() { - return n__('%d comment', '%d comments', this.notesCount); - }, - imageLink() { - return this.wasInView ? this.imageV432x230 || this.image : ''; - }, - showLoadingSpinner() { - return this.imageLoading || this.isUploading; - }, - showImageErrorIcon() { - return this.wasInView && this.imageError; - }, - showImage() { - return !this.showLoadingSpinner && !this.showImageErrorIcon; - }, - }, - methods: { - onImageLoad() { - this.imageLoading = false; - this.imageError = false; - }, - onImageError() { - this.imageLoading = false; - this.imageError = true; - }, - onAppear() { - // do nothing if image has previously - // been in view - if (this.wasInView) { - return; - } - - this.wasInView = true; - this.imageLoading = true; - }, - }, - DESIGN_ROUTE_NAME, -}; -</script> - -<template> - <router-link - :to="{ - name: $options.DESIGN_ROUTE_NAME, - params: { id: filename }, - query: $route.query, - }" - 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"> - <span :title="icon.tooltip" :aria-label="icon.tooltip"> - <icon :name="icon.name" :size="18" :class="icon.classes" /> - </span> - </div> - <gl-intersection-observer @appear="onAppear"> - <gl-loading-icon v-if="showLoadingSpinner" size="md" /> - <gl-icon - v-else-if="showImageErrorIcon" - name="media-broken" - class="text-secondary" - :size="32" - /> - <img - v-show="showImage" - :src="imageLink" - :alt="filename" - class="block mx-auto mw-100 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">{{ - 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"> - {{ notesCount }} - </span> - </div> - </div> - </router-link> -</template> diff --git a/app/assets/javascripts/design_management_new/components/toolbar/index.vue b/app/assets/javascripts/design_management_new/components/toolbar/index.vue deleted file mode 100644 index 0b51035e83e..00000000000 --- a/app/assets/javascripts/design_management_new/components/toolbar/index.vue +++ /dev/null @@ -1,124 +0,0 @@ -<script> -import { GlDeprecatedButton } 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 DeleteButton from '../delete_button.vue'; -import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql'; -import { DESIGNS_ROUTE_NAME } from '../../router/constants'; - -export default { - components: { - Icon, - Pagination, - DeleteButton, - GlDeprecatedButton, - }, - mixins: [timeagoMixin], - props: { - id: { - type: String, - required: true, - }, - isDeleting: { - type: Boolean, - required: true, - }, - filename: { - type: String, - required: false, - default: '', - }, - updatedAt: { - type: String, - required: false, - default: null, - }, - updatedBy: { - type: Object, - required: false, - default: () => ({}), - }, - isLatestVersion: { - type: Boolean, - required: true, - }, - image: { - type: String, - required: true, - }, - }, - data() { - return { - permissions: { - createDesign: false, - }, - }; - }, - inject: { - projectPath: { - default: '', - }, - issueIid: { - default: '', - }, - }, - apollo: { - permissions: { - query: permissionsQuery, - variables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - }; - }, - update: data => data.project.issue.userPermissions, - }, - }, - computed: { - updatedText() { - return sprintf(__('Updated %{updated_at} by %{updated_by}'), { - updated_at: this.timeFormatted(this.updatedAt), - updated_by: this.updatedBy.name, - }); - }, - canDeleteDesign() { - return this.permissions.createDesign; - }, - }, - DESIGNS_ROUTE_NAME, -}; -</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')" - data-testid="close-design" - 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> - </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> - <delete-button - v-if="isLatestVersion && canDeleteDesign" - :is-deleting="isDeleting" - button-variant="danger" - @deleteSelectedDesigns="$emit('delete')" - > - <icon :size="18" name="remove" /> - </delete-button> - </header> -</template> diff --git a/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue b/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue deleted file mode 100644 index bf62a8f66a6..00000000000 --- a/app/assets/javascripts/design_management_new/components/toolbar/pagination.vue +++ /dev/null @@ -1,83 +0,0 @@ -<script> -/* global Mousetrap */ -import 'mousetrap'; -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, - }, - mixins: [allDesignsMixin], - props: { - id: { - type: String, - required: true, - }, - }, - computed: { - designsCount() { - return this.designs.length; - }, - currentIndex() { - return this.designs.findIndex(design => design.filename === this.id); - }, - paginationText() { - return sprintf(s__('DesignManagement|%{current_design} of %{designs_count}'), { - current_design: this.currentIndex + 1, - designs_count: this.designsCount, - }); - }, - previousDesign() { - if (!this.designsCount) return null; - - return this.designs[this.currentIndex - 1]; - }, - nextDesign() { - if (!this.designsCount) return null; - - return this.designs[this.currentIndex + 1]; - }, - }, - mounted() { - Mousetrap.bind('left', () => this.navigateToDesign(this.previousDesign)); - Mousetrap.bind('right', () => this.navigateToDesign(this.nextDesign)); - }, - beforeDestroy() { - Mousetrap.unbind(['left', 'right'], this.navigateToDesign); - }, - methods: { - navigateToDesign(design) { - if (design) { - this.$router.push({ - name: DESIGN_ROUTE_NAME, - params: { id: design.filename }, - query: this.$route.query, - }); - } - }, - }, -}; -</script> - -<template> - <div v-if="designsCount" class="d-flex align-items-center"> - {{ paginationText }} - <div class="btn-group ml-3 mr-3"> - <pagination-button - :design="previousDesign" - :title="s__('DesignManagement|Go to previous design')" - icon-name="angle-left" - class="js-previous-design" - /> - <pagination-button - :design="nextDesign" - :title="s__('DesignManagement|Go to next design')" - icon-name="angle-right" - class="js-next-design" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue b/app/assets/javascripts/design_management_new/components/toolbar/pagination_button.vue deleted file mode 100644 index f00ecefca01..00000000000 --- a/app/assets/javascripts/design_management_new/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_new/components/upload/button.vue b/app/assets/javascripts/design_management_new/components/upload/button.vue deleted file mode 100644 index de8a38334ac..00000000000 --- a/app/assets/javascripts/design_management_new/components/upload/button.vue +++ /dev/null @@ -1,59 +0,0 @@ -<script> -import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; -import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants'; - -export default { - components: { - GlButton, - GlLoadingIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - isSaving: { - type: Boolean, - required: true, - }, - }, - methods: { - openFileUpload() { - this.$refs.fileUpload.click(); - }, - onFileUploadChange(e) { - this.$emit('upload', e.target.files); - }, - }, - VALID_DESIGN_FILE_MIMETYPE, -}; -</script> - -<template> - <div> - <gl-button - v-gl-tooltip.hover - :title=" - s__( - 'DesignManagement|Adding a design with the same filename replaces the file in a new version.', - ) - " - :disabled="isSaving" - variant="success" - size="small" - @click="openFileUpload" - > - {{ s__('DesignManagement|Upload designs') }} - <gl-loading-icon v-if="isSaving" inline class="ml-1" /> - </gl-button> - - <input - ref="fileUpload" - type="file" - name="design_file" - :accept="$options.VALID_DESIGN_FILE_MIMETYPE.mimetype" - class="hide" - multiple - @change="onFileUploadChange" - /> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue b/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue deleted file mode 100644 index 7b829d63330..00000000000 --- a/app/assets/javascripts/design_management_new/components/upload/design_dropzone.vue +++ /dev/null @@ -1,136 +0,0 @@ -<script> -import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; -import 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, - }, - }, - data() { - return { - dragCounter: 0, - isDragDataValid: false, - }; - }, - computed: { - dragging() { - return this.dragCounter !== 0; - }, - }, - 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="w-100 position-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 w-100 h-100 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="24" :class="hasDesigns ? 'gl-mb-2' : 'gl-mr-4'" /> - <p class="gl-font-weight-bold gl-mb-0"> - <gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} Designs to attach')"> - <template #link="{ content }"> - <gl-link class="gl-font-weight-normal" @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" - class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white" - > - <div v-show="!isDragDataValid" class="mw-50 text-center"> - <h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Oh no!') }}</h3> - <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 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_new/components/upload/design_version_dropdown.vue b/app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue deleted file mode 100644 index 5299d0ce09e..00000000000 --- a/app/assets/javascripts/design_management_new/components/upload/design_version_dropdown.vue +++ /dev/null @@ -1,76 +0,0 @@ -<script> -import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; -import allVersionsMixin from '../../mixins/all_versions'; -import { findVersionId } from '../../utils/design_management_utils'; - -export default { - components: { - GlNewDropdown, - GlNewDropdownItem, - }, - mixins: [allVersionsMixin], - computed: { - queryVersion() { - return this.$route.query.version; - }, - currentVersionIdx() { - if (!this.queryVersion) return 0; - - const idx = this.allVersions.findIndex( - version => this.findVersionId(version.node.id) === this.queryVersion, - ); - - // if the currentVersionId isn't a valid version (i.e. not in allVersions) - // then return the latest version (index 0) - return idx !== -1 ? idx : 0; - }, - currentVersionId() { - if (this.queryVersion) return this.queryVersion; - - const currentVersion = this.allVersions[this.currentVersionIdx]; - return this.findVersionId(currentVersion.node.id); - }, - dropdownText() { - if (this.isLatestVersion) { - 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}'), { - versionNumber: currentVersionNumber, - }); - }, - }, - methods: { - findVersionId, - }, -}; -</script> - -<template> - <gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown"> - <gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id"> - <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-new-dropdown-item> - </gl-new-dropdown> -</template> diff --git a/app/assets/javascripts/design_management_new/constants.js b/app/assets/javascripts/design_management_new/constants.js deleted file mode 100644 index 21ff361a277..00000000000 --- a/app/assets/javascripts/design_management_new/constants.js +++ /dev/null @@ -1,16 +0,0 @@ -// WARNING: replace this with something -// more sensical as per https://gitlab.com/gitlab-org/gitlab/issues/118611 -export const VALID_DESIGN_FILE_MIMETYPE = { - mimetype: 'image/*', - 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', -}; - -export const DESIGN_DETAIL_LAYOUT_CLASSLIST = ['design-detail-layout', 'overflow-hidden', 'm-0']; diff --git a/app/assets/javascripts/design_management_new/graphql.js b/app/assets/javascripts/design_management_new/graphql.js deleted file mode 100644 index fae337aa75b..00000000000 --- a/app/assets/javascripts/design_management_new/graphql.js +++ /dev/null @@ -1,45 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import { uniqueId } from 'lodash'; -import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; -import createDefaultClient from '~/lib/graphql'; -import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql'; -import typeDefs from './graphql/typedefs.graphql'; - -Vue.use(VueApollo); - -const resolvers = { - Mutation: { - updateActiveDiscussion: (_, { id = null, source }, { cache }) => { - const data = cache.readQuery({ query: activeDiscussionQuery }); - data.activeDiscussion = { - __typename: 'ActiveDiscussion', - id, - source, - }; - cache.writeQuery({ query: activeDiscussionQuery, data }); - }, - }, -}; - -const defaultClient = createDefaultClient( - resolvers, - // This config is added temporarily to resolve an issue with duplicate design IDs. - // Should be removed as soon as https://gitlab.com/gitlab-org/gitlab/issues/13495 is resolved - { - cacheConfig: { - dataIdFromObject: object => { - // eslint-disable-next-line no-underscore-dangle, @gitlab/require-i18n-strings - if (object.__typename === 'Design') { - return object.id && object.image ? `${object.id}-${object.image}` : uniqueId(); - } - return defaultDataIdFromObject(object); - }, - }, - typeDefs, - }, -); - -export default new VueApollo({ - defaultClient, -}); diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql deleted file mode 100644 index 4b1703e41c3..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/design.fragment.graphql +++ /dev/null @@ -1,24 +0,0 @@ -#import "./design_note.fragment.graphql" -#import "./design_list.fragment.graphql" -#import "./diff_refs.fragment.graphql" -#import "./discussion_resolved_status.fragment.graphql" - -fragment DesignItem on Design { - ...DesignListItem - fullPath - diffRefs { - ...DesignDiffRefs - } - discussions { - nodes { - id - replyId - ...ResolvedStatus - notes { - nodes { - ...DesignNote - } - } - } - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql deleted file mode 100644 index bc3132f9b42..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/design_list.fragment.graphql +++ /dev/null @@ -1,8 +0,0 @@ -fragment DesignListItem on Design { - id - event - filename - notesCount - image - imageV432x230 -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql deleted file mode 100644 index 26edd2c0be1..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/design_note.fragment.graphql +++ /dev/null @@ -1,29 +0,0 @@ -#import "./diff_refs.fragment.graphql" -#import "~/graphql_shared/fragments/author.fragment.graphql" -#import "./note_permissions.fragment.graphql" - -fragment DesignNote on Note { - id - author { - ...Author - } - body - bodyHtml - createdAt - resolved - position { - diffRefs { - ...DesignDiffRefs - } - x - y - height - width - } - userPermissions { - ...DesignNotePermissions - } - discussion { - id - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql deleted file mode 100644 index 984a55814b0..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/diff_refs.fragment.graphql +++ /dev/null @@ -1,5 +0,0 @@ -fragment DesignDiffRefs on DiffRefs { - baseSha - startSha - headSha -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql deleted file mode 100644 index 7483b508721..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/discussion_resolved_status.fragment.graphql +++ /dev/null @@ -1,9 +0,0 @@ -fragment ResolvedStatus on Discussion { - resolvable - resolved - resolvedAt - resolvedBy { - name - webUrl - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql deleted file mode 100644 index c243e39f3d3..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/note_permissions.fragment.graphql +++ /dev/null @@ -1,3 +0,0 @@ -fragment DesignNotePermissions on NotePermissions { - adminNote -} diff --git a/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql b/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql deleted file mode 100644 index 7eb40b12f51..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/fragments/version.fragment.graphql +++ /dev/null @@ -1,4 +0,0 @@ -fragment VersionListItem on DesignVersion { - id - sha -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql deleted file mode 100644 index c8ade328120..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "../fragments/design_note.fragment.graphql" - -mutation createImageDiffNote($input: CreateImageDiffNoteInput!) { - createImageDiffNote(input: $input) { - note { - ...DesignNote - discussion { - id - replyId - notes { - edges { - node { - ...DesignNote - } - } - } - } - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql deleted file mode 100644 index 184ee6955dc..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/create_note.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/design_note.fragment.graphql" - -mutation createNote($input: CreateNoteInput!) { - createNote(input: $input) { - note { - ...DesignNote - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql deleted file mode 100644 index 0b3cf636cdb..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/destroy_design.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/version.fragment.graphql" - -mutation destroyDesign($filenames: [String!]!, $projectPath: ID!, $iid: ID!) { - designManagementDelete(input: { projectPath: $projectPath, iid: $iid, filenames: $filenames }) { - version { - ...VersionListItem - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql deleted file mode 100644 index 1157fc05d5f..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql +++ /dev/null @@ -1,17 +0,0 @@ -#import "../fragments/design_note.fragment.graphql" -#import "../fragments/discussion_resolved_status.fragment.graphql" - -mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) { - discussionToggleResolve(input: { id: $id, resolve: $resolve }) { - discussion { - id - ...ResolvedStatus - notes { - nodes { - ...DesignNote - } - } - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql deleted file mode 100644 index a24b6737159..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation updateActiveDiscussion($id: String, $source: String) { - updateActiveDiscussion(id: $id, source: $source) @client -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_image_diff_note.mutation.graphql deleted file mode 100644 index 5562ca9d89f..00000000000 --- a/app/assets/javascripts/design_management_new/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_new/graphql/mutations/update_note.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql deleted file mode 100644 index b995e99fb6a..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/update_note.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/design_note.fragment.graphql" - -mutation updateNote($input: UpdateNoteInput!) { - updateNote(input: $input) { - note { - ...DesignNote - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql deleted file mode 100644 index d694e6558a0..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/mutations/upload_design.mutation.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "../fragments/design.fragment.graphql" - -mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) { - designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) { - designs { - ...DesignItem - versions { - edges { - node { - id - sha - } - } - } - } - skippedDesigns { - filename - } - errors - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql deleted file mode 100644 index 111023cea68..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/queries/active_discussion.query.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query activeDiscussion { - activeDiscussion @client { - id - source - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/design_permissions.query.graphql deleted file mode 100644 index a87b256dc95..00000000000 --- a/app/assets/javascripts/design_management_new/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_new/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql deleted file mode 100644 index 07a9af55787..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/queries/get_design.query.graphql +++ /dev/null @@ -1,31 +0,0 @@ -#import "../fragments/design.fragment.graphql" -#import "~/graphql_shared/fragments/author.fragment.graphql" - -query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [String!]) { - project(fullPath: $fullPath) { - id - issue(iid: $iid) { - designCollection { - designs(atVersion: $atVersion, filenames: $filenames) { - edges { - node { - ...DesignItem - issue { - title - webPath - webUrl - participants { - edges { - node { - ...Author - } - } - } - } - } - } - } - } - } - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql b/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql deleted file mode 100644 index 121a50555b3..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/queries/get_design_list.query.graphql +++ /dev/null @@ -1,26 +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 { - designs(atVersion: $atVersion) { - edges { - node { - ...DesignListItem - } - } - } - versions { - edges { - node { - ...VersionListItem - } - } - } - } - } - } -} diff --git a/app/assets/javascripts/design_management_new/graphql/typedefs.graphql b/app/assets/javascripts/design_management_new/graphql/typedefs.graphql deleted file mode 100644 index fdbad4a90e0..00000000000 --- a/app/assets/javascripts/design_management_new/graphql/typedefs.graphql +++ /dev/null @@ -1,12 +0,0 @@ -type ActiveDiscussion { - id: ID - source: String -} - -extend type Query { - activeDiscussion: ActiveDiscussion -} - -extend type Mutation { - updateActiveDiscussion(id: ID!, source: String!): Boolean -} diff --git a/app/assets/javascripts/design_management_new/index.js b/app/assets/javascripts/design_management_new/index.js deleted file mode 100644 index 20c9cacf83f..00000000000 --- a/app/assets/javascripts/design_management_new/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import Vue from 'vue'; -import createRouter from './router'; -import App from './components/app.vue'; -import apolloProvider from './graphql'; - -export default () => { - const el = document.querySelector('.js-design-management-new'); - const { issueIid, projectPath, issuePath } = el.dataset; - const router = createRouter(issuePath); - - apolloProvider.clients.defaultClient.cache.writeData({ - data: { - activeDiscussion: { - __typename: 'ActiveDiscussion', - id: null, - source: null, - }, - }, - }); - - return new Vue({ - el, - router, - apolloProvider, - provide: { - projectPath, - issueIid, - }, - render(createElement) { - return createElement(App); - }, - }); -}; diff --git a/app/assets/javascripts/design_management_new/mixins/all_designs.js b/app/assets/javascripts/design_management_new/mixins/all_designs.js deleted file mode 100644 index f7d6551c46c..00000000000 --- a/app/assets/javascripts/design_management_new/mixins/all_designs.js +++ /dev/null @@ -1,49 +0,0 @@ -import { propertyOf } from 'lodash'; -import 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'; - -export default { - mixins: [allVersionsMixin], - apollo: { - designs: { - query: getDesignListQuery, - variables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - atVersion: this.designsVersion, - }; - }, - update: data => { - const designEdges = propertyOf(data)(['project', 'issue', 'designCollection', 'designs']); - if (designEdges) { - return extractNodes(designEdges); - } - return []; - }, - error() { - this.error = true; - }, - result() { - if (this.$route.query.version && !this.hasValidVersion) { - createFlash( - s__( - 'DesignManagement|Requested design version does not exist. Showing latest version instead', - ), - ); - this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } }); - } - }, - }, - }, - data() { - return { - designs: [], - error: false, - }; - }, -}; diff --git a/app/assets/javascripts/design_management_new/mixins/all_versions.js b/app/assets/javascripts/design_management_new/mixins/all_versions.js deleted file mode 100644 index 99e2ee9561c..00000000000 --- a/app/assets/javascripts/design_management_new/mixins/all_versions.js +++ /dev/null @@ -1,59 +0,0 @@ -import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; -import { findVersionId } from '../utils/design_management_utils'; - -export default { - apollo: { - allVersions: { - query: getDesignListQuery, - variables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - atVersion: null, - }; - }, - update: data => data.project.issue.designCollection.versions.edges, - }, - }, - inject: { - projectPath: { - default: '', - }, - issueIid: { - default: '', - }, - }, - computed: { - hasValidVersion() { - return ( - this.$route.query.version && - this.allVersions && - this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version)) - ); - }, - designsVersion() { - return this.hasValidVersion - ? `gid://gitlab/DesignManagement::Version/${this.$route.query.version}` - : null; - }, - latestVersionId() { - const latestVersion = this.allVersions[0]; - return latestVersion && findVersionId(latestVersion.node.id); - }, - isLatestVersion() { - if (this.allVersions.length > 0) { - return ( - !this.$route.query.version || - !this.latestVersionId || - this.$route.query.version === this.latestVersionId - ); - } - return true; - }, - }, - data() { - return { - allVersions: [], - }; - }, -}; diff --git a/app/assets/javascripts/design_management_new/pages/design/index.vue b/app/assets/javascripts/design_management_new/pages/design/index.vue deleted file mode 100644 index 47f5e3a786f..00000000000 --- a/app/assets/javascripts/design_management_new/pages/design/index.vue +++ /dev/null @@ -1,367 +0,0 @@ -<script> -import Mousetrap from 'mousetrap'; -import { GlLoadingIcon, GlAlert } from '@gitlab/ui'; -import { ApolloMutation } from 'vue-apollo'; -import createFlash from '~/flash'; -import { fetchPolicies } from '~/lib/graphql'; -import allVersionsMixin from '../../mixins/all_versions'; -import Toolbar from '../../components/toolbar/index.vue'; -import DesignDestroyer from '../../components/design_destroyer.vue'; -import DesignScaler from '../../components/design_scaler.vue'; -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 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'; -import { - extractDiscussions, - extractDesign, - updateImageDiffNoteOptimisticResponse, -} from '../../utils/design_management_utils'; -import { - updateStoreAfterAddImageDiffNote, - updateStoreAfterUpdateImageDiffNote, -} from '../../utils/cache_update'; -import { - ADD_DISCUSSION_COMMENT_ERROR, - ADD_IMAGE_DIFF_NOTE_ERROR, - UPDATE_IMAGE_DIFF_NOTE_ERROR, - DESIGN_NOT_FOUND_ERROR, - DESIGN_VERSION_NOT_EXIST_ERROR, - UPDATE_NOTE_ERROR, - designDeletionError, -} from '../../utils/error_messages'; -import { trackDesignDetailView } from '../../utils/tracking'; -import { DESIGNS_ROUTE_NAME } from '../../router/constants'; -import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants'; - -export default { - components: { - ApolloMutation, - DesignReplyForm, - DesignPresentation, - DesignScaler, - DesignDestroyer, - Toolbar, - GlLoadingIcon, - GlAlert, - DesignSidebar, - }, - mixins: [allVersionsMixin], - props: { - id: { - type: String, - required: true, - }, - }, - data() { - return { - design: {}, - comment: '', - annotationCoordinates: null, - errorMessage: '', - scale: 1, - resolvedDiscussionsExpanded: false, - }; - }, - apollo: { - design: { - query: getDesignQuery, - // We want to see cached design version if we have one, and fetch newer version on the background to update discussions - fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, - variables() { - return this.designVariables; - }, - update: data => extractDesign(data), - result(res) { - this.onDesignQueryResult(res); - }, - error() { - this.onQueryError(DESIGN_NOT_FOUND_ERROR); - }, - }, - }, - computed: { - isFirstLoading() { - // We only want to show spinner on initial design load (when opened from a deep link to design) - // If we already have cached a design, loading shouldn't be indicated to user - return this.$apollo.queries.design.loading && !this.design.filename; - }, - discussions() { - if (!this.design.discussions) { - return []; - } - return extractDiscussions(this.design.discussions); - }, - markdownPreviewPath() { - return `/${this.projectPath}/preview_markdown?target_type=Issue`; - }, - isSubmitButtonDisabled() { - return this.comment.trim().length === 0; - }, - designVariables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - filenames: [this.$route.params.id], - atVersion: this.designsVersion, - }; - }, - mutationPayload() { - const { x, y, width, height } = this.annotationCoordinates; - return { - noteableId: this.design.id, - body: this.comment, - position: { - headSha: this.design.diffRefs.headSha, - baseSha: this.design.diffRefs.baseSha, - startSha: this.design.diffRefs.startSha, - x, - y, - width, - height, - paths: { - newPath: this.design.fullPath, - }, - }, - }; - }, - isAnnotating() { - return Boolean(this.annotationCoordinates); - }, - resolvedDiscussions() { - return this.discussions.filter(discussion => discussion.resolved); - }, - }, - watch: { - resolvedDiscussions(val) { - if (!val.length) { - this.resolvedDiscussionsExpanded = false; - } - }, - }, - mounted() { - Mousetrap.bind('esc', this.closeDesign); - this.trackEvent(); - // We need to reset the active discussion when opening a new design - this.updateActiveDiscussion(); - }, - beforeDestroy() { - Mousetrap.unbind('esc', this.closeDesign); - }, - methods: { - addImageDiffNoteToStore( - store, - { - data: { createImageDiffNote }, - }, - ) { - updateStoreAfterAddImageDiffNote( - store, - createImageDiffNote, - getDesignQuery, - this.designVariables, - ); - }, - updateImageDiffNoteInStore( - store, - { - data: { updateImageDiffNote }, - }, - ) { - return updateStoreAfterUpdateImageDiffNote( - store, - updateImageDiffNote, - getDesignQuery, - this.designVariables, - ); - }, - onMoveNote({ noteId, discussionId, position }) { - const discussion = this.discussions.find(({ id }) => id === discussionId); - const note = discussion.notes.find( - ({ discussion: noteDiscussion }) => noteDiscussion.id === discussionId, - ); - - const mutationPayload = { - optimisticResponse: updateImageDiffNoteOptimisticResponse(note, { - position, - }), - variables: { - input: { - id: noteId, - position, - }, - }, - mutation: updateImageDiffNoteMutation, - update: this.updateImageDiffNoteInStore, - }; - - return this.$apollo.mutate(mutationPayload).catch(e => this.onUpdateImageDiffNoteError(e)); - }, - onDesignQueryResult({ data, loading }) { - // On the initial load with cache-and-network policy data is undefined while loading is true - // To prevent throwing an error, we don't perform any logic until loading is false - if (loading) { - return; - } - - if (!data || !extractDesign(data)) { - this.onQueryError(DESIGN_NOT_FOUND_ERROR); - } else if (this.$route.query.version && !this.hasValidVersion) { - this.onQueryError(DESIGN_VERSION_NOT_EXIST_ERROR); - } - }, - onQueryError(message) { - // because we redirect user to /designs (the issue page), - // we want to create these flashes on the issue page - createFlash(message); - this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME }); - }, - onError(message, e) { - this.errorMessage = message; - throw e; - }, - onCreateImageDiffNoteError(e) { - this.onError(ADD_IMAGE_DIFF_NOTE_ERROR, e); - }, - onUpdateNoteError(e) { - this.onError(UPDATE_NOTE_ERROR, e); - }, - onDesignDiscussionError(e) { - this.onError(ADD_DISCUSSION_COMMENT_ERROR, e); - }, - onUpdateImageDiffNoteError(e) { - this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e); - }, - onDesignDeleteError(e) { - this.onError(designDeletionError({ singular: true }), e); - }, - onResolveDiscussionError(e) { - this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e); - }, - openCommentForm(annotationCoordinates) { - this.annotationCoordinates = annotationCoordinates; - if (this.$refs.newDiscussionForm) { - this.$refs.newDiscussionForm.focusInput(); - } - }, - closeCommentForm() { - this.comment = ''; - this.annotationCoordinates = null; - }, - closeDesign() { - this.$router.push({ - name: this.$options.DESIGNS_ROUTE_NAME, - query: this.$route.query, - }); - }, - trackEvent() { - // TODO: This needs to be made aware of referers, or if it's rendered in a different context than a Issue - trackDesignDetailView( - 'issue-design-collection', - 'issue', - this.$route.query.version || this.latestVersionId, - this.isLatestVersion, - ); - }, - updateActiveDiscussion(id) { - this.$apollo.mutate({ - mutation: updateActiveDiscussionMutation, - variables: { - id, - source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion, - }, - }); - }, - toggleResolvedComments() { - this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded; - }, - }, - createImageDiffNoteMutation, - DESIGNS_ROUTE_NAME, -}; -</script> - -<template> - <div - class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row" - > - <gl-loading-icon v-if="isFirstLoading" size="xl" class="align-self-center" /> - <template v-else> - <div class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"> - <design-destroyer - :filenames="[design.filename]" - :project-path="projectPath" - :iid="issueIid" - @done="$router.push({ name: $options.DESIGNS_ROUTE_NAME })" - @error="onDesignDeleteError" - > - <template #default="{ mutate, loading }"> - <toolbar - :id="id" - :is-deleting="loading" - :is-latest-version="isLatestVersion" - v-bind="design" - @delete="mutate" - /> - </template> - </design-destroyer> - - <div v-if="errorMessage" class="p-3"> - <gl-alert variant="danger" @dismiss="errorMessage = null"> - {{ errorMessage }} - </gl-alert> - </div> - <design-presentation - :image="design.image" - :image-name="design.filename" - :discussions="discussions" - :is-annotating="isAnnotating" - :scale="scale" - :resolved-discussions-expanded="resolvedDiscussionsExpanded" - @openCommentForm="openCommentForm" - @closeCommentForm="closeCommentForm" - @moveNote="onMoveNote" - /> - - <div class="design-scaler-wrapper position-absolute mb-4 d-flex-center"> - <design-scaler @scale="scale = $event" /> - </div> - </div> - <design-sidebar - :design="design" - :resolved-discussions-expanded="resolvedDiscussionsExpanded" - :markdown-preview-path="markdownPreviewPath" - @onDesignDiscussionError="onDesignDiscussionError" - @onCreateImageDiffNoteError="onCreateImageDiffNoteError" - @updateNoteError="onUpdateNoteError" - @resolveDiscussionError="onResolveDiscussionError" - @toggleResolvedComments="toggleResolvedComments" - > - <template #replyForm> - <apollo-mutation - v-if="isAnnotating" - #default="{ mutate, loading }" - :mutation="$options.createImageDiffNoteMutation" - :variables="{ - input: mutationPayload, - }" - :update="addImageDiffNoteToStore" - @done="closeCommentForm" - @error="onCreateImageDiffNoteError" - > - <design-reply-form - ref="newDiscussionForm" - v-model="comment" - :is-saving="loading" - :markdown-preview-path="markdownPreviewPath" - @submitForm="mutate" - @cancelForm="closeCommentForm" - /> </apollo-mutation - ></template> - </design-sidebar> - </template> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/pages/index.vue b/app/assets/javascripts/design_management_new/pages/index.vue deleted file mode 100644 index 700fa903a9c..00000000000 --- a/app/assets/javascripts/design_management_new/pages/index.vue +++ /dev/null @@ -1,346 +0,0 @@ -<script> -import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui'; -import createFlash from '~/flash'; -import { s__, sprintf } from '~/locale'; -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 uploadDesignMutation from '../graphql/mutations/upload_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, - designUploadSkippedWarning, - designDeletionError, -} from '../utils/error_messages'; -import { updateStoreAfterUploadDesign } from '../utils/cache_update'; -import { - designUploadOptimisticResponse, - isValidDesignFile, -} from '../utils/design_management_utils'; -import { getFilename } from '~/lib/utils/file_upload'; -import { DESIGNS_ROUTE_NAME } from '../router/constants'; - -const MAXIMUM_FILE_UPLOAD_LIMIT = 10; - -export default { - components: { - GlLoadingIcon, - GlAlert, - GlButton, - UploadButton, - Design, - DesignDestroyer, - DesignVersionDropdown, - DeleteButton, - DesignDropzone, - }, - mixins: [allDesignsMixin], - apollo: { - permissions: { - query: permissionsQuery, - variables() { - return { - fullPath: this.projectPath, - iid: this.issueIid, - }; - }, - update: data => data.project.issue.userPermissions, - }, - }, - data() { - return { - permissions: { - createDesign: false, - }, - filesToBeSaved: [], - selectedDesigns: [], - }; - }, - computed: { - isLoading() { - return this.$apollo.queries.designs.loading || this.$apollo.queries.permissions.loading; - }, - isSaving() { - return this.filesToBeSaved.length > 0; - }, - canCreateDesign() { - return this.permissions.createDesign; - }, - showToolbar() { - return this.canCreateDesign && this.allVersions.length > 0; - }, - hasDesigns() { - return this.designs.length > 0; - }, - hasSelectedDesigns() { - return this.selectedDesigns.length > 0; - }, - canDeleteDesigns() { - return this.isLatestVersion && this.hasSelectedDesigns; - }, - projectQueryBody() { - return { - query: getDesignListQuery, - variables: { fullPath: this.projectPath, iid: this.issueIid, atVersion: null }, - }; - }, - selectAllButtonText() { - return this.hasSelectedDesigns - ? 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); - }, - methods: { - resetFilesToBeSaved() { - this.filesToBeSaved = []; - }, - /** - * Determine if a design upload is valid, given [files] - * @param {Array<File>} files - */ - isValidDesignUpload(files) { - if (!this.canCreateDesign) return false; - - if (files.length > MAXIMUM_FILE_UPLOAD_LIMIT) { - createFlash( - sprintf( - s__( - 'DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again.', - ), - { - upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT, - }, - ), - ); - - return false; - } - return true; - }, - onUploadDesign(files) { - // convert to Array so that we have Array methods (.map, .some, etc.) - this.filesToBeSaved = Array.from(files); - if (!this.isValidDesignUpload(this.filesToBeSaved)) return null; - - const mutationPayload = { - optimisticResponse: designUploadOptimisticResponse(this.filesToBeSaved), - variables: { - files: this.filesToBeSaved, - projectPath: this.projectPath, - iid: this.issueIid, - }, - context: { - hasUpload: true, - }, - mutation: uploadDesignMutation, - update: this.afterUploadDesign, - }; - - return this.$apollo - .mutate(mutationPayload) - .then(res => this.onUploadDesignDone(res)) - .catch(() => this.onUploadDesignError()); - }, - afterUploadDesign( - store, - { - data: { designManagementUpload }, - }, - ) { - updateStoreAfterUploadDesign(store, designManagementUpload, this.projectQueryBody); - }, - onUploadDesignDone(res) { - const skippedFiles = res?.data?.designManagementUpload?.skippedDesigns || []; - const skippedWarningMessage = designUploadSkippedWarning(this.filesToBeSaved, skippedFiles); - if (skippedWarningMessage) { - createFlash(skippedWarningMessage, 'warning'); - } - - // if this upload resulted in a new version being created, redirect user to the latest version - if (!this.isLatestVersion) { - this.$router.push({ name: DESIGNS_ROUTE_NAME }); - } - this.resetFilesToBeSaved(); - }, - onUploadDesignError() { - this.resetFilesToBeSaved(); - createFlash(UPLOAD_DESIGN_ERROR); - }, - changeSelectedDesigns(filename) { - if (this.isDesignSelected(filename)) { - this.selectedDesigns = this.selectedDesigns.filter(design => design !== filename); - } else { - this.selectedDesigns.push(filename); - } - }, - toggleDesignsSelection() { - if (this.hasSelectedDesigns) { - this.selectedDesigns = []; - } else { - this.selectedDesigns = this.designs.map(design => design.filename); - } - }, - isDesignSelected(filename) { - return this.selectedDesigns.includes(filename); - }, - isDesignToBeSaved(filename) { - return this.filesToBeSaved.some(file => file.name === filename); - }, - canSelectDesign(filename) { - return this.isLatestVersion && this.canCreateDesign && !this.isDesignToBeSaved(filename); - }, - onDesignDelete() { - this.selectedDesigns = []; - if (this.$route.query.version) this.$router.push({ name: DESIGNS_ROUTE_NAME }); - }, - onDesignDeleteError() { - const errorMessage = designDeletionError({ singular: this.selectedDesigns.length === 1 }); - createFlash(errorMessage); - }, - onExistingDesignDropzoneChange(files, existingDesignFilename) { - const filesArr = Array.from(files); - - if (filesArr.length > 1) { - createFlash(EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE); - return; - } - - if (!filesArr.some(({ name }) => existingDesignFilename === name)) { - createFlash(EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE); - return; - } - - this.onUploadDesign(files); - }, - onDesignPaste(event) { - const { clipboardData } = event; - const files = Array.from(clipboardData.files); - if (clipboardData && files.length > 0) { - if (!files.some(isValidDesignFile)) { - return; - } - event.preventDefault(); - let filename = getFilename(event); - if (!filename || filename === 'image.png') { - filename = `design_${Date.now()}.png`; - } - const newFile = new File([files[0]], filename); - this.onUploadDesign([newFile]); - } - }, - toggleOnPasteListener() { - document.addEventListener('paste', this.onDesignPaste); - }, - toggleOffPasteListener() { - document.removeEventListener('paste', this.onDesignPaste); - }, - }, - beforeRouteUpdate(to, from, next) { - this.selectedDesigns = []; - next(); - }, -}; -</script> - -<template> - <div - data-testid="designs-root" - class="gl-mt-5" - :class="{ 'designs-root': !isDesignListEmpty }" - @mouseenter="toggleOnPasteListener" - @mouseleave="toggleOffPasteListener" - > - <header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex"> - <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"> - <div> - <span class="gl-font-weight-bold gl-mr-3">{{ s__('DesignManagement|Designs') }}</span> - <design-version-dropdown /> - </div> - <div v-show="hasDesigns" class="qa-selector-toolbar gl-display-flex"> - <gl-button - v-if="isLatestVersion" - variant="link" - size="small" - class="gl-mr-2 js-select-all" - @click="toggleDesignsSelection" - >{{ selectAllButtonText }} - </gl-button> - <design-destroyer - #default="{ mutate, loading }" - :filenames="selectedDesigns" - @done="onDesignDelete" - @error="onDesignDeleteError" - > - <delete-button - v-if="isLatestVersion" - :is-deleting="loading" - button-variant="danger" - button-class="gl-mr-4" - button-size="small" - :has-selected-designs="hasSelectedDesigns" - @deleteSelectedDesigns="mutate()" - > - {{ s__('DesignManagement|Delete selected') }} - <gl-loading-icon v-if="loading" inline class="ml-1" /> - </delete-button> - </design-destroyer> - <upload-button v-if="canCreateDesign" :is-saving="isSaving" @upload="onUploadDesign" /> - </div> - </div> - </header> - <div class="mt-4"> - <gl-loading-icon v-if="isLoading" size="md" /> - <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"> - <span - v-if="isDesignListEmpty && !allVersions.length" - class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4" - >{{ s__('DesignManagement|Designs') }}</span - > - <li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper"> - <design-dropzone - :class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }" - :has-designs="hasDesigns" - @change="onUploadDesign" - /> - </li> - <li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3"> - <design-dropzone - :has-designs="hasDesigns" - @change="onExistingDesignDropzoneChange($event, design.filename)" - ><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)" - /></design-dropzone> - - <input - v-if="canSelectDesign(design.filename)" - :checked="isDesignSelected(design.filename)" - type="checkbox" - class="design-checkbox" - @change="changeSelectedDesigns(design.filename)" - /> - </li> - </ol> - </div> - <router-view :key="$route.fullPath" /> - </div> -</template> diff --git a/app/assets/javascripts/design_management_new/router/constants.js b/app/assets/javascripts/design_management_new/router/constants.js deleted file mode 100644 index dd2ee8d8689..00000000000 --- a/app/assets/javascripts/design_management_new/router/constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const DESIGNS_ROUTE_NAME = 'designs'; -export const DESIGN_ROUTE_NAME = 'design'; diff --git a/app/assets/javascripts/design_management_new/router/index.js b/app/assets/javascripts/design_management_new/router/index.js deleted file mode 100644 index 40e2d35bc40..00000000000 --- a/app/assets/javascripts/design_management_new/router/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import VueRouter from 'vue-router'; -import routes from './routes'; -import { DESIGN_ROUTE_NAME } from './constants'; -import { getPageLayoutElement } from '~/design_management_new/utils/design_management_utils'; -import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '../constants'; - -Vue.use(VueRouter); - -export default function createRouter(base) { - const router = new VueRouter({ - 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_new/router/routes.js b/app/assets/javascripts/design_management_new/router/routes.js deleted file mode 100644 index d888b856611..00000000000 --- a/app/assets/javascripts/design_management_new/router/routes.js +++ /dev/null @@ -1,29 +0,0 @@ -import Home from '../pages/index.vue'; -import DesignDetail from '../pages/design/index.vue'; -import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from './constants'; - -export default [ - { - name: DESIGNS_ROUTE_NAME, - path: '/', - component: Home, - alias: '/designs', - }, - { - name: DESIGN_ROUTE_NAME, - path: '/designs/:id', - component: DesignDetail, - beforeEnter( - { - params: { id }, - }, - _, - next, - ) { - if (typeof id === 'string') { - next(); - } - }, - props: ({ params: { id } }) => ({ id }), - }, -]; diff --git a/app/assets/javascripts/design_management_new/utils/cache_update.js b/app/assets/javascripts/design_management_new/utils/cache_update.js deleted file mode 100644 index 24b374b79fd..00000000000 --- a/app/assets/javascripts/design_management_new/utils/cache_update.js +++ /dev/null @@ -1,276 +0,0 @@ -/* eslint-disable @gitlab/require-i18n-strings */ - -import createFlash from '~/flash'; -import { extractCurrentDiscussion, extractDesign } from './design_management_utils'; -import { - ADD_IMAGE_DIFF_NOTE_ERROR, - UPDATE_IMAGE_DIFF_NOTE_ERROR, - ADD_DISCUSSION_COMMENT_ERROR, - designDeletionError, -} from './error_messages'; - -const deleteDesignsFromStore = (store, query, selectedDesigns) => { - const data = store.readQuery(query); - - const changedDesigns = data.project.issue.designCollection.designs.edges.filter( - ({ node }) => !selectedDesigns.includes(node.filename), - ); - data.project.issue.designCollection.designs.edges = [...changedDesigns]; - - store.writeQuery({ - ...query, - data, - }); -}; - -/** - * Adds a new version of designs to store - * - * @param {Object} store - * @param {Object} query - * @param {Object} version - */ -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, - ]; - - store.writeQuery({ - ...query, - data, - }); -}; - -const addDiscussionCommentToStore = (store, createNote, query, queryVariables, discussionId) => { - const data = store.readQuery({ - query, - variables: queryVariables, - }); - - const design = extractDesign(data); - const currentDiscussion = extractCurrentDiscussion(design.discussions, discussionId); - currentDiscussion.notes.nodes = [...currentDiscussion.notes.nodes, createNote.note]; - - design.notesCount += 1; - if ( - !design.issue.participants.edges.some( - participant => participant.node.username === createNote.note.author.username, - ) - ) { - design.issue.participants.edges = [ - ...design.issue.participants.edges, - { - __typename: 'UserEdge', - node: { - __typename: 'User', - ...createNote.note.author, - }, - }, - ]; - } - store.writeQuery({ - query, - variables: queryVariables, - data: { - ...data, - design: { - ...design, - }, - }, - }); -}; - -const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) => { - const data = store.readQuery({ - query, - variables, - }); - const newDiscussion = { - __typename: 'Discussion', - id: createImageDiffNote.note.discussion.id, - replyId: createImageDiffNote.note.discussion.replyId, - resolvable: true, - resolved: false, - resolvedAt: null, - resolvedBy: null, - notes: { - __typename: 'NoteConnection', - nodes: [createImageDiffNote.note], - }, - }; - const design = extractDesign(data); - 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.edges = [ - ...design.issue.participants.edges, - { - __typename: 'UserEdge', - node: { - __typename: 'User', - ...createImageDiffNote.note.author, - }, - }, - ]; - } - store.writeQuery({ - query, - variables, - data: { - ...data, - design: { - ...design, - notesCount, - }, - }, - }); -}; - -const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => { - const data = store.readQuery({ - query, - variables, - }); - - const design = extractDesign(data); - const discussion = extractCurrentDiscussion( - design.discussions, - updateImageDiffNote.note.discussion.id, - ); - - discussion.notes = { - ...discussion.notes, - nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)], - }; - - store.writeQuery({ - query, - variables, - data: { - ...data, - design, - }, - }); -}; - -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); - - let newVersionNode; - const findNewVersions = designManagementUpload.designs.find(design => design.versions); - - if (findNewVersions) { - const findNewVersionsEdges = findNewVersions.versions.edges; - - if (findNewVersionsEdges && findNewVersionsEdges.length) { - newVersionNode = [findNewVersionsEdges[0]]; - } - } - - const newVersions = [ - ...(newVersionNode || []), - ...data.project.issue.designCollection.versions.edges, - ]; - - const updatedDesigns = { - __typename: 'DesignCollection', - designs: { - __typename: 'DesignConnection', - edges: newDesigns.map(design => ({ - __typename: 'DesignEdge', - node: design, - })), - }, - versions: { - __typename: 'DesignVersionConnection', - edges: newVersions, - }, - }; - - data.project.issue.designCollection = updatedDesigns; - - store.writeQuery({ - ...query, - data, - }); -}; - -const onError = (data, message) => { - createFlash(message); - throw new Error(data.errors); -}; - -export const hasErrors = ({ errors = [] }) => errors?.length; - -/** - * Updates a store after design deletion - * - * @param {Object} store - * @param {Object} data - * @param {Object} query - * @param {Array} designs - */ -export const updateStoreAfterDesignsDelete = (store, data, query, designs) => { - if (hasErrors(data)) { - onError(data, designDeletionError({ singular: designs.length === 1 })); - } else { - deleteDesignsFromStore(store, query, designs); - addNewVersionToStore(store, query, data.version); - } -}; - -export const updateStoreAfterAddDiscussionComment = ( - store, - data, - query, - queryVariables, - discussionId, -) => { - if (hasErrors(data)) { - onError(data, ADD_DISCUSSION_COMMENT_ERROR); - } else { - addDiscussionCommentToStore(store, data, query, queryVariables, discussionId); - } -}; - -export const updateStoreAfterAddImageDiffNote = (store, data, query, queryVariables) => { - if (hasErrors(data)) { - onError(data, ADD_IMAGE_DIFF_NOTE_ERROR); - } else { - addImageDiffNoteToStore(store, data, query, queryVariables); - } -}; - -export const updateStoreAfterUpdateImageDiffNote = (store, data, query, queryVariables) => { - if (hasErrors(data)) { - onError(data, UPDATE_IMAGE_DIFF_NOTE_ERROR); - } else { - updateImageDiffNoteInStore(store, data, query, queryVariables); - } -}; - -export const updateStoreAfterUploadDesign = (store, data, query) => { - if (hasErrors(data)) { - onError(data, data.errors[0]); - } else { - addNewDesignToStore(store, data, query); - } -}; diff --git a/app/assets/javascripts/design_management_new/utils/design_management_utils.js b/app/assets/javascripts/design_management_new/utils/design_management_utils.js deleted file mode 100644 index 22705cf67a1..00000000000 --- a/app/assets/javascripts/design_management_new/utils/design_management_utils.js +++ /dev/null @@ -1,128 +0,0 @@ -import { uniqueId } from 'lodash'; -import { VALID_DESIGN_FILE_MIMETYPE } from '../constants'; - -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 - * - * @param {Array} discussions - */ - -export const extractDiscussions = discussions => - discussions.nodes.map((discussion, index) => ({ - ...discussion, - index: index + 1, - notes: discussion.notes.nodes, - })); - -/** - * Returns a discussion with the given id from discussions array - * - * @param {Array} discussions - */ - -export const extractCurrentDiscussion = (discussions, id) => - discussions.nodes.find(discussion => discussion.id === id); - -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 extractDesign = data => (extractDesigns(data) || [])[0]?.node; - -/** - * Generates optimistic response for a design upload mutation - * @param {Array<File>} files - */ -export const designUploadOptimisticResponse = files => { - const designs = files.map(file => ({ - // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 - // eslint-disable-next-line @gitlab/require-i18n-strings - __typename: 'Design', - id: -uniqueId(), - image: '', - imageV432x230: '', - filename: file.name, - fullPath: '', - notesCount: 0, - event: 'NONE', - diffRefs: { - __typename: 'DiffRefs', - baseSha: '', - startSha: '', - headSha: '', - }, - discussions: { - __typename: 'DesignDiscussion', - nodes: [], - }, - versions: { - __typename: 'DesignVersionConnection', - edges: { - __typename: 'DesignVersionEdge', - node: { - __typename: 'DesignVersion', - id: -uniqueId(), - sha: -uniqueId(), - }, - }, - }, - })); - - return { - // 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', - designManagementUpload: { - __typename: 'DesignManagementUploadPayload', - designs, - skippedDesigns: [], - errors: [], - }, - }; -}; - -/** - * Generates optimistic response for a design upload mutation - * @param {Array<File>} files - */ -export const updateImageDiffNoteOptimisticResponse = (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', - note: { - ...note, - position: { - ...note.position, - ...position, - }, - }, - 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 getPageLayoutElement = () => document.querySelector('.layout-page'); diff --git a/app/assets/javascripts/design_management_new/utils/error_messages.js b/app/assets/javascripts/design_management_new/utils/error_messages.js deleted file mode 100644 index 7666c726c2f..00000000000 --- a/app/assets/javascripts/design_management_new/utils/error_messages.js +++ /dev/null @@ -1,95 +0,0 @@ -import { __, s__, n__, sprintf } from '~/locale'; - -export const ADD_DISCUSSION_COMMENT_ERROR = s__( - 'DesignManagement|Could not add a new comment. Please try again.', -); - -export const ADD_IMAGE_DIFF_NOTE_ERROR = s__( - 'DesignManagement|Could not create new discussion. Please try again.', -); - -export const UPDATE_IMAGE_DIFF_NOTE_ERROR = s__( - 'DesignManagement|Could not update discussion. Please try again.', -); - -export const UPDATE_NOTE_ERROR = s__('DesignManagement|Could not update note. Please try again.'); - -export const UPLOAD_DESIGN_ERROR = s__( - 'DesignManagement|Error uploading a new design. Please try again.', -); - -export const UPLOAD_DESIGN_INVALID_FILETYPE_ERROR = __( - 'Could not upload your designs as one or more files uploaded are not supported.', -); - -export const DESIGN_NOT_FOUND_ERROR = __('Could not find design.'); - -export const DESIGN_VERSION_NOT_EXIST_ERROR = __('Requested design version does not exist.'); - -const DESIGN_UPLOAD_SKIPPED_MESSAGE = s__('DesignManagement|Upload skipped.'); - -const ALL_DESIGNS_SKIPPED_MESSAGE = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__( - 'The designs you tried uploading did not change.', -)}`; - -export const EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE = __( - 'You can only upload one design when dropping onto an existing design.', -); - -export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __( - 'You must upload a file with the same file name when dropping onto an existing design.', -); - -const MAX_SKIPPED_FILES_LISTINGS = 5; - -const oneDesignSkippedMessage = filename => - `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${sprintf(s__('DesignManagement|%{filename} did not change.'), { - filename, - })}`; - -/** - * Return warning message indicating that some (but not all) uploaded - * files were skipped. - * @param {Array<{ filename }>} skippedFiles - */ -const someDesignsSkippedMessage = skippedFiles => { - const designsSkippedMessage = `${DESIGN_UPLOAD_SKIPPED_MESSAGE} ${s__( - 'Some of the designs you tried uploading did not change:', - )}`; - - const moreText = sprintf(s__(`DesignManagement|and %{moreCount} more.`), { - moreCount: skippedFiles.length - MAX_SKIPPED_FILES_LISTINGS, - }); - - return `${designsSkippedMessage} ${skippedFiles - .slice(0, MAX_SKIPPED_FILES_LISTINGS) - .map(({ filename }) => filename) - .join(', ')}${skippedFiles.length > MAX_SKIPPED_FILES_LISTINGS ? `, ${moreText}` : '.'}`; -}; - -export const designDeletionError = ({ singular = true } = {}) => { - const design = singular ? __('a design') : __('designs'); - return sprintf(s__('Could not delete %{design}. Please try again.'), { - design, - }); -}; - -/** - * Return warning message, if applicable, that one, some or all uploaded - * files were skipped. - * @param {Array<{ filename }>} uploadedDesigns - * @param {Array<{ filename }>} skippedFiles - */ -export const designUploadSkippedWarning = (uploadedDesigns, skippedFiles) => { - if (skippedFiles.length === 0) { - return null; - } - - if (skippedFiles.length === uploadedDesigns.length) { - const { filename } = skippedFiles[0]; - - return n__(oneDesignSkippedMessage(filename), ALL_DESIGNS_SKIPPED_MESSAGE, skippedFiles.length); - } - - return someDesignsSkippedMessage(skippedFiles); -}; diff --git a/app/assets/javascripts/design_management_new/utils/tracking.js b/app/assets/javascripts/design_management_new/utils/tracking.js deleted file mode 100644 index b3ecc1453a6..00000000000 --- a/app/assets/javascripts/design_management_new/utils/tracking.js +++ /dev/null @@ -1,27 +0,0 @@ -import Tracking from '~/tracking'; - -// Tracking Constants -const DESIGN_TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/design_management_context/jsonschema/1-0-0'; -const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design'; -const DESIGN_TRACKING_EVENT_NAME = 'view_design'; - -// eslint-disable-next-line import/prefer-default-export -export function trackDesignDetailView( - referer = '', - owner = '', - designVersion = 1, - latestVersion = false, -) { - Tracking.event(DESIGN_TRACKING_PAGE_NAME, DESIGN_TRACKING_EVENT_NAME, { - label: DESIGN_TRACKING_EVENT_NAME, - context: { - schema: DESIGN_TRACKING_CONTEXT_SCHEMA, - data: { - 'design-version-number': designVersion, - 'design-is-current-version': latestVersion, - 'internal-object-referrer': referer, - 'design-collection-owner': owner, - }, - }, - }); -} |