diff options
Diffstat (limited to 'app/assets/javascripts')
86 files changed, 835 insertions, 211 deletions
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue index 97232d7f783..8512bf9dd7b 100644 --- a/app/assets/javascripts/badges/components/badge.vue +++ b/app/assets/javascripts/badges/components/badge.vue @@ -1,12 +1,14 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; import Tooltip from '~/vue_shared/directives/tooltip'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { name: 'Badge', components: { Icon, Tooltip, + GlLoadingIcon, }, directives: { Tooltip, diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index aff7c4180e3..47e6e618219 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -4,6 +4,7 @@ import { mapActions, mapState } from 'vuex'; import createFlash from '~/flash'; import { s__, sprintf } from '~/locale'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import createEmptyBadge from '../empty_badge'; import Badge from './badge.vue'; @@ -14,6 +15,7 @@ export default { components: { Badge, LoadingButton, + GlLoadingIcon, }, props: { isEditing: { diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue index 359d3e10380..ab518820378 100644 --- a/app/assets/javascripts/badges/components/badge_list.vue +++ b/app/assets/javascripts/badges/components/badge_list.vue @@ -1,5 +1,6 @@ <script> import { mapState } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import BadgeListRow from './badge_list_row.vue'; import { GROUP_BADGE } from '../constants'; @@ -7,6 +8,7 @@ export default { name: 'BadgeList', components: { BadgeListRow, + GlLoadingIcon, }, computed: { ...mapState(['badges', 'isLoading', 'kind']), diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue index 5d16ba3ce6d..f28eff18f03 100644 --- a/app/assets/javascripts/badges/components/badge_list_row.vue +++ b/app/assets/javascripts/badges/components/badge_list_row.vue @@ -2,6 +2,7 @@ import { mapActions, mapState } from 'vuex'; import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { PROJECT_BADGE } from '../constants'; import Badge from './badge.vue'; @@ -10,6 +11,7 @@ export default { components: { Badge, Icon, + GlLoadingIcon, }, props: { badge: { diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 4dc56c670f0..5e28fc396ab 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -1,5 +1,6 @@ <script> import Sortable from 'sortablejs'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import boardNewIssue from './board_new_issue.vue'; import boardCard from './board_card.vue'; import eventHub from '../eventhub'; @@ -11,6 +12,7 @@ export default { components: { boardCard, boardNewIssue, + GlLoadingIcon, }, props: { groupId: { diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue index 40949cc0656..fdd1346d4c7 100644 --- a/app/assets/javascripts/boards/components/modal/index.vue +++ b/app/assets/javascripts/boards/components/modal/index.vue @@ -6,6 +6,7 @@ import ModalList from './list.vue'; import ModalFooter from './footer.vue'; import EmptyState from './empty_state.vue'; import ModalStore from '../../stores/modal_store'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { components: { @@ -13,6 +14,7 @@ export default { ModalHeader, ModalList, ModalFooter, + GlLoadingIcon, }, props: { newIssuePath: { diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 0f01a2a6c09..503417644fa 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -2,6 +2,7 @@ import $ from 'jquery'; import _ from 'underscore'; import Icon from '~/vue_shared/components/icon.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../eventhub'; import Api from '../../api'; @@ -9,6 +10,7 @@ export default { name: 'BoardProjectSelect', components: { Icon, + GlLoadingIcon, }, props: { groupId: { diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index ebf76af5966..02dfe1c7d6f 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -28,6 +28,7 @@ export default class Clusters { installIngressPath, installRunnerPath, installJupyterPath, + installKnativePath, installPrometheusPath, managePrometheusPath, clusterStatus, @@ -49,6 +50,7 @@ export default class Clusters { installRunnerEndpoint: installRunnerPath, installPrometheusEndpoint: installPrometheusPath, installJupyterEndpoint: installJupyterPath, + installKnativeEndpoint: installKnativePath, }); this.installApplication = this.installApplication.bind(this); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 6d7f45a35d8..c7ffb470d4d 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -7,6 +7,7 @@ import helmLogo from 'images/cluster_app_logos/helm.png'; import jeagerLogo from 'images/cluster_app_logos/jeager.png'; import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png'; import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png'; +import knativeLogo from 'images/cluster_app_logos/knative.png'; import meltanoLogo from 'images/cluster_app_logos/meltano.png'; import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import { s__, sprintf } from '../../locale'; @@ -53,6 +54,7 @@ export default { jeagerLogo, jupyterhubLogo, kubernetesLogo, + knativeLogo, meltanoLogo, prometheusLogo, }), @@ -136,6 +138,9 @@ export default { jupyterHostname() { return this.applications.jupyter.hostname; }, + knativeInstalled() { + return this.applications.knative.status === APPLICATION_STATUS.INSTALLED; + }, }, created() { this.helmInstallIllustration = helmInstallIllustration; @@ -321,7 +326,6 @@ export default { :request-reason="applications.jupyter.requestReason" :install-application-request-params="{ hostname: applications.jupyter.hostname }" :disabled="!helmInstalled" - class="hide-bottom-border rounded-bottom" title-link="https://jupyterhub.readthedocs.io/en/stable/" > <div slot="description"> @@ -371,6 +375,58 @@ export default { </template> </div> </application-row> + <application-row + id="knative" + :logo-url="knativeLogo" + :title="applications.knative.title" + :status="applications.knative.status" + :status-reason="applications.knative.statusReason" + :request-status="applications.knative.requestStatus" + :request-reason="applications.knative.requestReason" + :install-application-request-params="{ hostname: applications.knative.hostname}" + :disabled="!helmInstalled" + class="hide-bottom-border rounded-bottom" + title-link="https://github.com/knative/docs" + > + <div slot="description"> + <p> + {{ s__(`ClusterIntegration|A Knative build extends Kubernetes + and utilizes existing Kubernetes primitives to provide you with + the ability to run on-cluster container builds from source. + For example, you can write a build that uses Kubernetes-native + resources to obtain your source code from a repository, + build it into container a image, and then run that image.`) }} + </p> + + <template v-if="knativeInstalled"> + <div class="form-group"> + <label for="knative-domainname"> + {{ s__('ClusterIntegration|Knative Domain Name:') }} + </label> + <input + id="knative-domainname" + v-model="applications.knative.hostname" + type="text" + class="form-control js-domainname" + readonly + /> + </div> + </template> + <template v-else> + <div class="form-group"> + <label for="knative-domainname"> + {{ s__('ClusterIntegration|Knative Domain Name:') }} + </label> + <input + id="knative-domainname" + v-model="applications.knative.hostname" + type="text" + class="form-control js-domainname" + /> + </div> + </template> + </div> + </application-row> </div> </section> </template> diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index 24a49624583..d707420c845 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -16,3 +16,4 @@ export const REQUEST_SUCCESS = 'request-success'; export const REQUEST_FAILURE = 'request-failure'; export const INGRESS = 'ingress'; export const JUPYTER = 'jupyter'; +export const KNATIVE = 'knative'; diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index a7d82292ba9..da562b09ee5 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -9,6 +9,7 @@ export default class ClusterService { runner: this.options.installRunnerEndpoint, prometheus: this.options.installPrometheusEndpoint, jupyter: this.options.installJupyterEndpoint, + knative: this.options.installKnativeEndpoint, }; } diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 106ac3cb516..e45da967392 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -1,5 +1,5 @@ import { s__ } from '../../locale'; -import { INGRESS, JUPYTER } from '../constants'; +import { INGRESS, JUPYTER, KNATIVE } from '../constants'; export default class ClusterStore { constructor() { @@ -46,6 +46,14 @@ export default class ClusterStore { requestReason: null, hostname: null, }, + knative: { + title: s__('ClusterIntegration|Knative'), + status: null, + statusReason: null, + requestStatus: null, + requestReason: null, + hostname: null, + }, }, }; } @@ -93,6 +101,9 @@ export default class ClusterStore { (this.state.applications.ingress.externalIp ? `jupyter.${this.state.applications.ingress.externalIp}.nip.io` : ''); + } else if (appId === KNATIVE) { + this.state.applications.knative.hostname = + serverAppEntry.hostname || this.state.applications.knative.hostname; } }); } diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js deleted file mode 100644 index 6c18a0fd390..00000000000 --- a/app/assets/javascripts/commons/gitlab_ui.js +++ /dev/null @@ -1,4 +0,0 @@ -import Vue from 'vue'; -import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; - -Vue.component('gl-loading-icon', GlLoadingIcon); diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js index ea945cd3fa5..0d2fe2925d8 100644 --- a/app/assets/javascripts/commons/index.js +++ b/app/assets/javascripts/commons/index.js @@ -3,5 +3,4 @@ import './polyfills'; import './jquery'; import './bootstrap'; import './vue'; -import './gitlab_ui'; import '../lib/utils/axios_utils'; diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue index 10548da8ec5..ea74fd27ff6 100644 --- a/app/assets/javascripts/deploy_keys/components/action_btn.vue +++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue @@ -1,7 +1,11 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../eventhub'; export default { + components: { + GlLoadingIcon, + }, props: { deployKey: { type: Object, diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 3589599986d..631a9673b3e 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -6,11 +6,13 @@ import eventHub from '../eventhub'; import DeployKeysService from '../service'; import DeployKeysStore from '../store'; import KeysPanel from './keys_panel.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { components: { KeysPanel, NavigationTabs, + GlLoadingIcon, }, props: { endpoint: { diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 59680959bb1..7c60fb3da42 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -3,6 +3,7 @@ import { mapState, mapGetters, mapActions } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; import createFlash from '~/flash'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../../notes/event_hub'; import CompareVersions from './compare_versions.vue'; import DiffFile from './diff_file.vue'; @@ -21,6 +22,7 @@ export default { HiddenFilesWarning, CommitWidget, TreeList, + GlLoadingIcon, }, props: { endpoint: { @@ -223,7 +225,10 @@ export default { :commit="commit" /> - <div class="files d-flex prepend-top-default"> + <div + :data-can-create-note="getNoteableData.current_user.can_create_note" + class="files d-flex prepend-top-default" + > <div v-show="showTreeList" class="diff-tree-list" diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue index 29b5aff0fb1..a5b87dfc2d9 100644 --- a/app/assets/javascripts/diffs/components/compare_versions.vue +++ b/app/assets/javascripts/diffs/components/compare_versions.vue @@ -36,7 +36,7 @@ export default { }, computed: { ...mapState('diffs', ['commit', 'showTreeList']), - ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']), + ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'hasCollapsedFile']), comparableDiffs() { return this.mergeRequestDiffs.slice(1); }, @@ -113,8 +113,8 @@ export default { class="inline-parallel-buttons d-none d-md-flex ml-auto" > <a - v-if="areAllFilesCollapsed" - class="btn btn-default" + v-show="hasCollapsedFile" + class="btn btn-default append-right-8" @click="expandAllFiles" > {{ __('Expand all') }} diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index fb5556e3cd7..547742a5ff4 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -1,15 +1,22 @@ <script> -import { mapGetters, mapState } from 'vuex'; +import { mapActions, mapGetters, mapState } from 'vuex'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; -import { diffModes } from '~/ide/constants'; import InlineDiffView from './inline_diff_view.vue'; import ParallelDiffView from './parallel_diff_view.vue'; +import NoteForm from '../../notes/components/note_form.vue'; +import ImageDiffOverlay from './image_diff_overlay.vue'; +import DiffDiscussions from './diff_discussions.vue'; +import { IMAGE_DIFF_POSITION_TYPE } from '../constants'; +import { getDiffMode } from '../store/utils'; export default { components: { InlineDiffView, ParallelDiffView, DiffViewer, + NoteForm, + DiffDiscussions, + ImageDiffOverlay, }, props: { diffFile: { @@ -23,13 +30,38 @@ export default { endpoint: state => state.diffs.endpoint, }), ...mapGetters('diffs', ['isInlineView', 'isParallelView']), + ...mapGetters('diffs', ['getCommentFormForDiffFile']), + ...mapGetters(['getNoteableData', 'noteableType']), diffMode() { - const diffModeKey = Object.keys(diffModes).find(key => this.diffFile[`${key}File`]); - return diffModes[diffModeKey] || diffModes.replaced; + return getDiffMode(this.diffFile); }, isTextFile() { return this.diffFile.viewer.name === 'text'; }, + diffFileCommentForm() { + return this.getCommentFormForDiffFile(this.diffFile.fileHash); + }, + showNotesContainer() { + return this.diffFile.discussions.length || this.diffFileCommentForm; + }, + }, + methods: { + ...mapActions('diffs', ['saveDiffDiscussion', 'closeDiffFileCommentForm']), + handleSaveNote(note) { + this.saveDiffDiscussion({ + note, + formData: { + noteableData: this.getNoteableData, + noteableType: this.noteableType, + diffFile: this.diffFile, + positionType: IMAGE_DIFF_POSITION_TYPE, + x: this.diffFileCommentForm.x, + y: this.diffFileCommentForm.y, + width: this.diffFileCommentForm.width, + height: this.diffFileCommentForm.height, + }, + }); + }, }, }; </script> @@ -56,7 +88,37 @@ export default { :new-sha="diffFile.diffRefs.headSha" :old-path="diffFile.oldPath" :old-sha="diffFile.diffRefs.baseSha" - :project-path="projectPath"/> + :file-hash="diffFile.fileHash" + :project-path="projectPath" + > + <image-diff-overlay + slot="image-overlay" + :discussions="diffFile.discussions" + :file-hash="diffFile.fileHash" + :can-comment="getNoteableData.current_user.can_create_note" + /> + <div + v-if="showNotesContainer" + class="note-container" + > + <diff-discussions + v-if="diffFile.discussions.length" + class="diff-file-discussions" + :discussions="diffFile.discussions" + :should-collapse-discussions="true" + :render-avatar-badge="true" + /> + <note-form + v-if="diffFileCommentForm" + ref="noteForm" + :is-editing="false" + :save-button-title="__('Comment')" + class="diff-comment-form new-note discussion-form discussion-form-container" + @handleFormUpdate="handleSaveNote" + @cancelForm="closeDiffFileCommentForm(diffFile.fileHash)" + /> + </div> + </diff-viewer> </div> </div> </template> diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue index cddbe554fbd..e19207bdc95 100644 --- a/app/assets/javascripts/diffs/components/diff_discussions.vue +++ b/app/assets/javascripts/diffs/components/diff_discussions.vue @@ -1,24 +1,40 @@ <script> import { mapActions } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; import noteableDiscussion from '../../notes/components/noteable_discussion.vue'; export default { components: { noteableDiscussion, + Icon, }, props: { discussions: { type: Array, required: true, }, + shouldCollapseDiscussions: { + type: Boolean, + required: false, + default: false, + }, + renderAvatarBadge: { + type: Boolean, + required: false, + default: false, + }, }, methods: { + ...mapActions(['toggleDiscussion']), ...mapActions('diffs', ['removeDiscussionsFromDiff']), deleteNoteHandler(discussion) { if (discussion.notes.length <= 1) { this.removeDiscussionsFromDiff(discussion); } }, + isExpanded(discussion) { + return this.shouldCollapseDiscussions ? discussion.expanded : true; + }, }, }; </script> @@ -26,22 +42,54 @@ export default { <template> <div> <div - v-for="discussion in discussions" + v-for="(discussion, index) in discussions" :key="discussion.id" - class="discussion-notes diff-discussions" + :class="{ + collapsed: !isExpanded(discussion) + }" + class="discussion-notes diff-discussions position-relative" > <ul :data-discussion-id="discussion.id" class="notes" > + <template v-if="shouldCollapseDiscussions"> + <button + :class="{ + 'diff-notes-collapse': discussion.expanded, + 'btn-transparent badge badge-pill': !discussion.expanded + }" + type="button" + class="js-diff-notes-toggle" + @click="toggleDiscussion({ discussionId: discussion.id })" + > + <icon + v-if="discussion.expanded" + name="collapse" + class="collapse-icon" + /> + <template v-else> + {{ index + 1 }} + </template> + </button> + </template> <noteable-discussion + v-show="isExpanded(discussion)" :discussion="discussion" :render-header="false" :render-diff-file="false" :always-expanded="true" :discussions-by-diff-order="true" @noteDeleted="deleteNoteHandler" - /> + > + <span + v-if="renderAvatarBadge" + slot="avatar-badge" + class="badge badge-pill" + > + {{ index + 1 }} + </span> + </noteable-discussion> </ul> </div> </div> diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 958e57c5652..e76c7afd863 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -3,6 +3,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; import { __, sprintf } from '~/locale'; import createFlash from '~/flash'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import DiffFileHeader from './diff_file_header.vue'; import DiffContent from './diff_content.vue'; @@ -10,6 +11,7 @@ export default { components: { DiffFileHeader, DiffContent, + GlLoadingIcon, }, props: { file: { diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index f4a9be19496..e31a3546b69 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -55,11 +55,6 @@ export default { required: false, default: false, }, - isContextLine: { - type: Boolean, - required: false, - default: false, - }, isHover: { type: Boolean, required: false, @@ -81,7 +76,6 @@ export default { this.showCommentButton && this.isHover && !this.isMatchLine && - !this.isContextLine && !this.isMetaLine && !this.hasDiscussions ); diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue index 5d9a0b123fe..e26aa9c9b00 100644 --- a/app/assets/javascripts/diffs/components/diff_table_cell.vue +++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue @@ -3,7 +3,6 @@ import { mapGetters } from 'vuex'; import DiffLineGutterContent from './diff_line_gutter_content.vue'; import { MATCH_LINE_TYPE, - CONTEXT_LINE_TYPE, EMPTY_CELL_TYPE, OLD_LINE_TYPE, OLD_NO_NEW_LINE_TYPE, @@ -71,9 +70,6 @@ export default { isMatchLine() { return this.line.type === MATCH_LINE_TYPE; }, - isContextLine() { - return this.line.type === CONTEXT_LINE_TYPE; - }, isMetaLine() { const { type } = this.line; @@ -88,11 +84,7 @@ export default { [type]: type, [LINE_UNFOLD_CLASS_NAME]: this.isMatchLine, [LINE_HOVER_CLASS_NAME]: - this.isLoggedIn && - this.isHover && - !this.isMatchLine && - !this.isContextLine && - !this.isMetaLine, + this.isLoggedIn && this.isHover && !this.isMatchLine && !this.isMetaLine, }; }, lineNumber() { diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue new file mode 100644 index 00000000000..ae1b0a52901 --- /dev/null +++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue @@ -0,0 +1,139 @@ +<script> +import { mapActions, mapGetters } from 'vuex'; +import _ from 'underscore'; +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + name: 'ImageDiffOverlay', + components: { + Icon, + }, + props: { + discussions: { + type: [Array, Object], + required: true, + }, + fileHash: { + type: String, + required: true, + }, + canComment: { + type: Boolean, + required: false, + default: false, + }, + showCommentIcon: { + type: Boolean, + required: false, + default: false, + }, + badgeClass: { + type: String, + required: false, + default: 'badge badge-pill', + }, + shouldToggleDiscussion: { + type: Boolean, + required: false, + default: true, + }, + }, + computed: { + ...mapGetters('diffs', ['getDiffFileByHash', 'getCommentFormForDiffFile']), + currentCommentForm() { + return this.getCommentFormForDiffFile(this.fileHash); + }, + allDiscussions() { + return _.isArray(this.discussions) ? this.discussions : [this.discussions]; + }, + }, + methods: { + ...mapActions(['toggleDiscussion']), + ...mapActions('diffs', ['openDiffFileCommentForm']), + getImageDimensions() { + return { + width: this.$parent.width, + height: this.$parent.height, + }; + }, + getPositionForObject(meta) { + const { x, y, width, height } = meta; + const imageWidth = this.getImageDimensions().width; + const imageHeight = this.getImageDimensions().height; + const widthRatio = imageWidth / width; + const heightRatio = imageHeight / height; + + return { + x: Math.round(x * widthRatio), + y: Math.round(y * heightRatio), + }; + }, + getPosition(discussion) { + const { x, y } = this.getPositionForObject(discussion.position); + + return { + left: `${x}px`, + top: `${y}px`, + }; + }, + clickedImage(x, y) { + const { width, height } = this.getImageDimensions(); + + this.openDiffFileCommentForm({ + fileHash: this.fileHash, + width, + height, + x, + y, + }); + }, + }, +}; +</script> + +<template> + <div class="position-absolute w-100 h-100 image-diff-overlay"> + <button + v-if="canComment" + type="button" + class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button" + @click="clickedImage($event.offsetX, $event.offsetY)" + > + <span class="sr-only"> + {{ __('Add image comment') }} + </span> + </button> + <button + v-for="(discussion, index) in allDiscussions" + :key="discussion.id" + :style="getPosition(discussion)" + :class="badgeClass" + :disabled="!shouldToggleDiscussion" + class="js-image-badge" + type="button" + @click="toggleDiscussion({ discussionId: discussion.id })" + > + <icon + v-if="showCommentIcon" + name="image-comment-dark" + /> + <template v-else> + {{ index + 1 }} + </template> + </button> + <button + v-if="currentCommentForm" + :style="{ + left: `${currentCommentForm.x}px`, + top: `${currentCommentForm.y}px` + }" + :aria-label="__('Comment form position')" + class="btn-transparent comment-indicator" + type="button" + > + <icon + name="image-comment-dark" + /> + </button> + </div> +</template> diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index 542acd3d930..44c05e4b634 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -4,8 +4,6 @@ import DiffTableCell from './diff_table_cell.vue'; import { NEW_LINE_TYPE, OLD_LINE_TYPE, - CONTEXT_LINE_TYPE, - CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE, LINE_POSITION_LEFT, LINE_POSITION_RIGHT, @@ -41,13 +39,9 @@ export default { }, computed: { ...mapGetters('diffs', ['isInlineView']), - isContextLine() { - return this.line.type === CONTEXT_LINE_TYPE; - }, classNameMap() { return { [this.line.type]: this.line.type, - [CONTEXT_LINE_CLASS_NAME]: this.isContextLine, [PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView, }; }, diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue index fcc3b3e9117..39312cddfce 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue @@ -5,8 +5,6 @@ import DiffTableCell from './diff_table_cell.vue'; import { NEW_LINE_TYPE, OLD_LINE_TYPE, - CONTEXT_LINE_TYPE, - CONTEXT_LINE_CLASS_NAME, OLD_NO_NEW_LINE_TYPE, PARALLEL_DIFF_VIEW_TYPE, NEW_NO_NEW_LINE_TYPE, @@ -43,12 +41,8 @@ export default { }; }, computed: { - isContextLine() { - return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE; - }, classNameMap() { return { - [CONTEXT_LINE_CLASS_NAME]: this.isContextLine, [PARALLEL_DIFF_VIEW_TYPE]: true, }; }, diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index 6a50d2c1426..f5f5c0ffc29 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -3,7 +3,6 @@ export const PARALLEL_DIFF_VIEW_TYPE = 'parallel'; export const MATCH_LINE_TYPE = 'match'; export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline'; export const NEW_NO_NEW_LINE_TYPE = 'new-nonewline'; -export const CONTEXT_LINE_TYPE = 'context'; export const EMPTY_CELL_TYPE = 'empty-cell'; export const COMMENT_FORM_TYPE = 'commentForm'; export const DIFF_NOTE_TYPE = 'DiffNote'; @@ -12,6 +11,7 @@ export const NOTE_TYPE = 'Note'; export const NEW_LINE_TYPE = 'new'; export const OLD_LINE_TYPE = 'old'; export const TEXT_DIFF_POSITION_TYPE = 'text'; +export const IMAGE_DIFF_POSITION_TYPE = 'image'; export const LINE_POSITION_LEFT = 'left'; export const LINE_POSITION_RIGHT = 'right'; @@ -21,7 +21,6 @@ export const LINE_SIDE_RIGHT = 'right-side'; export const DIFF_VIEW_COOKIE_NAME = 'diff_view'; export const LINE_HOVER_CLASS_NAME = 'is-over'; export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold'; -export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded'; export const UNFOLD_COUNT = 20; export const COUNT_OF_AVATARS_IN_GUTTER = 3; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index ca8ae605cb4..d3e9c7c88f0 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -50,8 +50,8 @@ export const assignDiscussionsToDiff = ( }; export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => { - const { fileHash, line_code } = removeDiscussion; - commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode: line_code }); + const { fileHash, line_code, id } = removeDiscussion; + commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode: line_code, id }); }; export const startRenderDiffsQueue = ({ state, commit }) => { @@ -189,6 +189,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { return dispatch('saveNote', postData, { root: true }) .then(result => dispatch('updateDiscussion', result.discussion, { root: true })) .then(discussion => dispatch('assignDiscussionsToDiff', [discussion])) + .then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.fileHash)) .catch(() => createFlash(s__('MergeRequests|Saving the comment failed'))); }; @@ -210,5 +211,19 @@ export const toggleShowTreeList = ({ commit, state }) => { localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList); }; +export const openDiffFileCommentForm = ({ commit, getters }, formData) => { + const form = getters.getCommentFormForDiffFile(formData.fileHash); + + if (form) { + commit(types.UPDATE_DIFF_FILE_COMMENT_FORM, formData); + } else { + commit(types.OPEN_DIFF_FILE_COMMENT_FORM, formData); + } +}; + +export const closeDiffFileCommentForm = ({ commit }, fileHash) => { + commit(types.CLOSE_DIFF_FILE_COMMENT_FORM, fileHash); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index d4c205882ff..bf490f9d78a 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -5,7 +5,7 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE; -export const areAllFilesCollapsed = state => state.diffFiles.every(file => file.collapsed); +export const hasCollapsedFile = state => state.diffFiles.some(file => file.collapsed); export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null); @@ -114,5 +114,8 @@ export const allBlobs = state => Object.values(state.treeEntries).filter(f => f. export const diffFilesLength = state => state.diffFiles.length; +export const getCommentFormForDiffFile = state => fileHash => + state.commentForms.find(form => form.fileHash === fileHash); + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index 1c5c35071de..085e255f1d3 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -24,4 +24,6 @@ export default () => ({ showTreeList: storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true', currentDiffFileId: '', + projectPath: '', + commentForms: [], }); diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 6474ee628e2..e011031e72c 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -14,3 +14,7 @@ export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FIL export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN'; export const TOGGLE_SHOW_TREE_LIST = 'TOGGLE_SHOW_TREE_LIST'; export const UPDATE_CURRENT_DIFF_FILE_ID = 'UPDATE_CURRENT_DIFF_FILE_ID'; + +export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM'; +export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM'; +export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 38a65f111a2..e651c197968 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -65,7 +65,13 @@ export default { const { highlightedDiffLines, parallelDiffLines } = diffFile; removeMatchLine(diffFile, lineNumbers, bottom); - const lines = addLineReferences(contextLines, lineNumbers, bottom); + + const lines = addLineReferences(contextLines, lineNumbers, bottom).map(line => ({ + ...line, + lineCode: line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`, + discussions: line.discussions || [], + })); + addContextLines({ inlineLines: highlightedDiffLines, parallelLines: parallelDiffLines, @@ -153,20 +159,22 @@ export default { }); }, - [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) { + [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode, id }) { const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash); if (selectedFile) { - const targetLine = selectedFile.parallelDiffLines.find( - line => - (line.left && line.left.lineCode === lineCode) || - (line.right && line.right.lineCode === lineCode), - ); - if (targetLine) { - const side = targetLine.left && targetLine.left.lineCode === lineCode ? 'left' : 'right'; - - Object.assign(targetLine[side], { - discussions: [], - }); + if (selectedFile.parallelDiffLines) { + const targetLine = selectedFile.parallelDiffLines.find( + line => + (line.left && line.left.lineCode === lineCode) || + (line.right && line.right.lineCode === lineCode), + ); + if (targetLine) { + const side = targetLine.left && targetLine.left.lineCode === lineCode ? 'left' : 'right'; + + Object.assign(targetLine[side], { + discussions: [], + }); + } } if (selectedFile.highlightedDiffLines) { @@ -180,6 +188,12 @@ export default { }); } } + + if (selectedFile.discussions && selectedFile.discussions.length) { + selectedFile.discussions = selectedFile.discussions.filter( + discussion => discussion.id !== id, + ); + } } }, [types.TOGGLE_FOLDER_OPEN](state, path) { @@ -191,4 +205,25 @@ export default { [types.UPDATE_CURRENT_DIFF_FILE_ID](state, fileId) { state.currentDiffFileId = fileId; }, + [types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) { + state.commentForms.push({ + ...formData, + }); + }, + [types.UPDATE_DIFF_FILE_COMMENT_FORM](state, formData) { + const { fileHash } = formData; + + state.commentForms = state.commentForms.map(form => { + if (form.fileHash === fileHash) { + return { + ...formData, + }; + } + + return form; + }); + }, + [types.CLOSE_DIFF_FILE_COMMENT_FORM](state, fileHash) { + state.commentForms = state.commentForms.filter(form => form.fileHash !== fileHash); + }, }; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index a482a2b82c0..a935b9b1ffa 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { diffModes } from '~/ide/constants'; import { LINE_POSITION_LEFT, LINE_POSITION_RIGHT, @@ -34,6 +35,7 @@ export function getFormData(params) { noteTargetLine, diffViewType, linePosition, + positionType, } = params; const position = JSON.stringify({ @@ -42,9 +44,13 @@ export function getFormData(params) { head_sha: diffFile.diffRefs.headSha, old_path: diffFile.oldPath, new_path: diffFile.newPath, - position_type: TEXT_DIFF_POSITION_TYPE, - old_line: noteTargetLine.oldLine, - new_line: noteTargetLine.newLine, + position_type: positionType || TEXT_DIFF_POSITION_TYPE, + old_line: noteTargetLine ? noteTargetLine.oldLine : null, + new_line: noteTargetLine ? noteTargetLine.newLine : null, + x: params.x, + y: params.y, + width: params.width, + height: params.height, }); const postData = { @@ -66,7 +72,7 @@ export function getFormData(params) { diffFile.diffRefs.startSha && diffFile.diffRefs.headSha ? DIFF_NOTE_TYPE : LEGACY_DIFF_NOTE_TYPE, - line_code: noteTargetLine.lineCode, + line_code: noteTargetLine ? noteTargetLine.lineCode : null, }, }; @@ -225,6 +231,7 @@ export function prepareDiffData(diffData) { Object.assign(file, { renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY, collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED, + discussions: [], }); } } @@ -320,3 +327,8 @@ export const generateTreeList = files => }, { treeEntries: {}, tree: [] }, ); + +export const getDiffMode = diffFile => { + const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}File`]); + return diffModes[diffModeKey] || diffModes.replaced; +}; diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index 00d197d294f..a48f5fcb7d6 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import tablePagination from '../../vue_shared/components/table_pagination.vue'; import environmentTable from '../components/environments_table.vue'; @@ -6,6 +7,7 @@ export default { components: { environmentTable, tablePagination, + GlLoadingIcon, }, props: { isLoading: { diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 0a3ae384afa..03c3ad0401f 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -4,6 +4,7 @@ import { formatTime } from '~/lib/utils/datetime_utility'; import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub'; import tooltip from '../../vue_shared/directives/tooltip'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { directives: { @@ -11,6 +12,7 @@ export default { }, components: { Icon, + GlLoadingIcon, }, props: { actions: { diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 9e137f79dcc..69856abc2d5 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -9,10 +9,12 @@ import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import eventHub from '../event_hub'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { components: { Icon, + GlLoadingIcon, }, directives: { diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 16abafebbc0..c03d4f29ff9 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -2,11 +2,13 @@ /** * Render environments table. */ +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import environmentItem from './environment_item.vue'; export default { components: { environmentItem, + GlLoadingIcon, }, props: { diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue index 70a8838b772..159c0bdc992 100644 --- a/app/assets/javascripts/frequent_items/components/app.vue +++ b/app/assets/javascripts/frequent_items/components/app.vue @@ -1,6 +1,7 @@ <script> import { mapState, mapActions, mapGetters } from 'vuex'; import AccessorUtilities from '~/lib/utils/accessor'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../event_hub'; import store from '../store/'; import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants'; @@ -14,6 +15,7 @@ export default { components: { FrequentItemsSearchInput, FrequentItemsList, + GlLoadingIcon, }, mixins: [frequentItemsMixin], props: { diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index a032f291546..2a4a39436e7 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -8,6 +8,7 @@ import { HIDDEN_CLASS } from '~/lib/utils/constants'; import { getParameterByName } from '~/lib/utils/common_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../event_hub'; import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants'; import groupsComponent from './groups.vue'; @@ -16,6 +17,7 @@ export default { components: { DeprecatedModal, groupsComponent, + GlLoadingIcon, }, props: { action: { diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 52ccc537c9d..358f1153de2 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -2,12 +2,14 @@ import { mapActions, mapState } from 'vuex'; import _ from 'underscore'; import Icon from '~/vue_shared/components/icon.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Item from './item.vue'; export default { components: { Item, Icon, + GlLoadingIcon, }, data() { return { diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue index a20dc0a7006..2d9bd99e82a 100644 --- a/app/assets/javascripts/ide/components/error_message.vue +++ b/app/assets/javascripts/ide/components/error_message.vue @@ -1,7 +1,11 @@ <script> import { mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { + components: { + GlLoadingIcon, + }, props: { message: { type: Object, diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue index 94222c08e91..891f7d48b4c 100644 --- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue +++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue @@ -2,10 +2,12 @@ import $ from 'jquery'; import { mapActions, mapState } from 'vuex'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { components: { DropdownButton, + GlLoadingIcon, }, props: { data: { diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index acd37605d16..57da8b4e2cb 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -1,10 +1,12 @@ <script> import { mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Stage from './stage.vue'; export default { components: { Stage, + GlLoadingIcon, }, props: { stages: { diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index ec168d36b9e..5644759d2f9 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import tooltip from '../../../vue_shared/directives/tooltip'; import Icon from '../../../vue_shared/components/icon.vue'; import CiIcon from '../../../vue_shared/components/ci_icon.vue'; @@ -12,6 +13,7 @@ export default { Icon, CiIcon, Item, + GlLoadingIcon, }, props: { stage: { diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue index f5e42e87f1b..e4000f588bd 100644 --- a/app/assets/javascripts/ide/components/merge_requests/list.vue +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -3,6 +3,7 @@ import { mapActions, mapState } from 'vuex'; import _ from 'underscore'; import { __ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Item from './item.vue'; import TokenedInput from '../shared/tokened_input.vue'; @@ -16,6 +17,7 @@ export default { TokenedInput, Item, Icon, + GlLoadingIcon, }, data() { return { diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index b670b0355b7..16aec1decd6 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -1,6 +1,7 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { sprintf, __ } from '../../../locale'; import Icon from '../../../vue_shared/components/icon.vue'; import CiIcon from '../../../vue_shared/components/ci_icon.vue'; @@ -17,6 +18,7 @@ export default { Tab, JobsList, EmptyState, + GlLoadingIcon, }, computed: { ...mapState(['pipelinesEmptyStateSvgPath', 'links']), diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue index 37a8ad36507..0bd56ff6e9b 100644 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -3,6 +3,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; import { Manager } from 'smooshpack'; import { listen } from 'codesandbox-api'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Navigator from './navigator.vue'; import { packageJsonPath } from '../../constants'; import { createPathWithExt } from '../../utils'; @@ -10,6 +11,7 @@ import { createPathWithExt } from '../../utils'; export default { components: { Navigator, + GlLoadingIcon, }, data() { return { diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue index 42f23801692..af8959186f9 100644 --- a/app/assets/javascripts/ide/components/preview/navigator.vue +++ b/app/assets/javascripts/ide/components/preview/navigator.vue @@ -1,10 +1,12 @@ <script> import { listen } from 'codesandbox-api'; import Icon from '~/vue_shared/components/icon.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { components: { Icon, + GlLoadingIcon, }, props: { manager: { diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index d23915966de..35104c80694 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -16,6 +16,8 @@ import Log from './job_log.vue'; import LogTopBar from './job_log_controllers.vue'; import StuckBlock from './stuck_block.vue'; import Sidebar from './sidebar.vue'; +import { sprintf } from '~/locale'; +import delayedJobMixin from '../mixins/delayed_job_mixin'; export default { name: 'JobPageApp', @@ -26,13 +28,14 @@ export default { EmptyState, EnvironmentsBlock, ErasedBlock, - GlLoadingIcon, Icon, Log, LogTopBar, StuckBlock, Sidebar, + GlLoadingIcon, }, + mixins: [delayedJobMixin], props: { runnerSettingsUrl: { type: String, @@ -92,6 +95,17 @@ export default { shouldRenderContent() { return !this.isLoading && !this.hasError; }, + + emptyStateTitle() { + const { emptyStateIllustration, remainingTime } = this; + const { title } = emptyStateIllustration; + + if (this.isDelayedJob) { + return sprintf(title, { remainingTime }); + } + + return title; + }, }, watch: { // Once the job log is loaded, @@ -272,7 +286,7 @@ export default { class="js-job-empty-state" :illustration-path="emptyStateIllustration.image" :illustration-size-class="emptyStateIllustration.size" - :title="emptyStateIllustration.title" + :title="emptyStateTitle" :content="emptyStateIllustration.content" :action="emptyStateAction" /> diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue index cdac8a391d1..3ddcfd11dca 100644 --- a/app/assets/javascripts/jobs/components/job_container_item.vue +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -1,7 +1,10 @@ <script> -import { GlTooltipDirective, GlLink } from '@gitlab-org/gitlab-ui'; +import { GlLink } from '@gitlab-org/gitlab-ui'; +import tooltip from '~/vue_shared/directives/tooltip'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; +import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin'; +import { sprintf } from '~/locale'; export default { components: { @@ -10,8 +13,9 @@ export default { GlLink, }, directives: { - GlTooltip: GlTooltipDirective, + tooltip, }, + mixins: [delayedJobMixin], props: { job: { type: Object, @@ -24,7 +28,14 @@ export default { }, computed: { tooltipText() { - return `${this.job.name} - ${this.job.status.tooltip}`; + const { name, status } = this.job; + const text = `${name} - ${status.tooltip}`; + + if (this.isDelayedJob) { + return sprintf(text, { remainingTime: this.remainingTime }); + } + + return text; }, }, }; @@ -39,7 +50,7 @@ export default { }" > <gl-link - v-gl-tooltip + v-tooltip :href="job.status.details_path" :title="tooltipText" data-boundary="viewport" diff --git a/app/assets/javascripts/jobs/mixins/delayed_job_mixin.js b/app/assets/javascripts/jobs/mixins/delayed_job_mixin.js new file mode 100644 index 00000000000..8c7fb785a61 --- /dev/null +++ b/app/assets/javascripts/jobs/mixins/delayed_job_mixin.js @@ -0,0 +1,50 @@ +import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility'; + +export default { + data() { + return { + remainingTime: formatTime(0), + remainingTimeIntervalId: null, + }; + }, + + mounted() { + this.startRemainingTimeInterval(); + }, + + beforeDestroy() { + if (this.remainingTimeIntervalId) { + clearInterval(this.remainingTimeIntervalId); + } + }, + + computed: { + isDelayedJob() { + return this.job && this.job.scheduled; + }, + }, + + watch: { + isDelayedJob() { + this.startRemainingTimeInterval(); + }, + }, + + methods: { + startRemainingTimeInterval() { + if (this.remainingTimeIntervalId) { + clearInterval(this.remainingTimeIntervalId); + } + + if (this.isDelayedJob) { + this.updateRemainingTime(); + this.remainingTimeIntervalId = setInterval(() => this.updateRemainingTime(), 1000); + } + }, + + updateRemainingTime() { + const remainingMilliseconds = calculateRemainingMilliseconds(this.job.scheduled_at); + this.remainingTime = formatTime(remainingMilliseconds); + }, + }, +}; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 2950c2299ab..d8255181574 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -11,7 +11,6 @@ import bp from './breakpoints'; import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { getLocationHash } from './lib/utils/url_utility'; -import initDiscussionTab from './image_diff/init_discussion_tab'; import Diff from './diff'; import { localTimeAgo } from './lib/utils/datetime_utility'; import syntaxHighlight from './syntax_highlight'; @@ -207,8 +206,6 @@ export default class MergeRequestTabs { } this.resetViewContainer(); this.destroyPipelinesView(); - - initDiscussionTab(); } if (this.setUrl) { this.setCurrentAction(action); diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index eaa0cded224..b209f736c3f 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -1,15 +1,18 @@ <script> import { mapState, mapActions } from 'vuex'; -import imageDiffHelper from '~/image_diff/helpers/index'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; +import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; +import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue'; import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui'; -import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; +import { trimFirstCharOfLineContent, getDiffMode } from '~/diffs/store/utils'; export default { components: { DiffFileHeader, GlSkeletonLoading, + DiffViewer, + ImageDiffOverlay, }, props: { discussion: { @@ -25,7 +28,11 @@ export default { computed: { ...mapState({ noteableData: state => state.notes.noteableData, + projectPath: state => state.diffs.projectPath, }), + diffMode() { + return getDiffMode(this.diffFile); + }, hasTruncatedDiffLines() { return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0; }, @@ -62,11 +69,7 @@ export default { }, }, mounted() { - if (this.isImageDiff) { - const canCreateNote = false; - const renderCommentBadge = true; - imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge); - } else if (!this.hasTruncatedDiffLines) { + if (!this.hasTruncatedDiffLines) { this.fetchDiff(); } }, @@ -160,7 +163,24 @@ export default { <div v-else > - <div v-html="imageDiffHtml"></div> + <diff-viewer + :diff-mode="diffMode" + :new-path="diffFile.newPath" + :new-sha="diffFile.diffRefs.headSha" + :old-path="diffFile.oldPath" + :old-sha="diffFile.diffRefs.baseSha" + :file-hash="diffFile.fileHash" + :project-path="projectPath" + > + <image-diff-overlay + slot="image-overlay" + :discussions="discussion" + :file-hash="diffFile.fileHash" + :show-comment-icon="true" + :should-toggle-discussion="false" + badge-class="image-comment-badge" + /> + </diff-viewer> <slot></slot> </div> </div> diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index e075f94b82b..01cbe40f444 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -9,11 +9,13 @@ import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg'; import ellipsisSvg from 'icons/_ellipsis_v.svg'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { name: 'NoteActions', components: { Icon, + GlLoadingIcon, }, directives: { tooltip, @@ -246,7 +248,7 @@ export default { <ul class="dropdown-menu more-actions-dropdown dropdown-open-left"> <li v-if="canReportAsAbuse"> <a :href="reportAbusePath"> - Report as abuse + {{ __('Report abuse to GitLab') }} </a> </li> <li v-if="noteUrl"> @@ -255,7 +257,7 @@ export default { type="button" class="btn-default btn-transparent js-btn-copy-note-link" > - Copy link + {{ __('Copy link') }} </button> </li> <li v-if="canEdit"> @@ -264,7 +266,7 @@ export default { type="button" @click.prevent="onDelete"> <span class="text-danger"> - Delete comment + {{ __('Delete comment') }} </span> </button> </li> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 6293dd5b7e1..07115ca07c4 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -350,11 +350,18 @@ Please check your network connection and try again.`; <ul class="notes"> <component :is="componentName(note)" - v-for="note in discussion.notes" + v-for="(note, index) in discussion.notes" :key="note.id" :note="componentData(note)" @handleDeleteNote="deleteNoteHandler" - /> + > + <slot + v-if="index === 0" + slot="avatar-badge" + name="avatar-badge" + > + </slot> + </component> </ul> <div :class="{ 'is-replying': isReplying }" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index f391ed848a4..40222ac4a80 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -182,7 +182,13 @@ export default { :img-src="author.avatar_url" :img-alt="author.name" :img-size="40" - /> + > + <slot + slot="avatar-badge" + name="avatar-badge" + > + </slot> + </user-avatar-link> </div> <div class="timeline-content"> <div class="note-header"> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 23c0be7742e..4de8b3401e8 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,10 +1,12 @@ <script> import _ from 'underscore'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import StageColumnComponent from './stage_column_component.vue'; export default { components: { StageColumnComponent, + GlLoadingIcon, }, props: { isLoading: { diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index a1504592bbc..7cdde8a53b3 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_item.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue @@ -2,6 +2,8 @@ import ActionComponent from './action_component.vue'; import JobNameComponent from './job_name_component.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; +import { sprintf } from '~/locale'; +import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin'; /** * Renders the badge for the pipeline graph and the job's dropdown. @@ -36,6 +38,7 @@ export default { directives: { tooltip, }, + mixins: [delayedJobMixin], props: { job: { type: Object, @@ -52,6 +55,7 @@ export default { default: Infinity, }, }, + computed: { status() { return this.job && this.job.status ? this.job.status : {}; @@ -59,17 +63,23 @@ export default { tooltipText() { const textBuilder = []; + const { name: jobName } = this.job; - if (this.job.name) { - textBuilder.push(this.job.name); + if (jobName) { + textBuilder.push(jobName); } - if (this.job.name && this.status.tooltip) { + const { tooltip: statusTooltip } = this.status; + if (jobName && statusTooltip) { textBuilder.push('-'); } - if (this.status.tooltip) { - textBuilder.push(this.job.status.tooltip); + if (statusTooltip) { + if (this.isDelayedJob) { + textBuilder.push(sprintf(statusTooltip, { remainingTime: this.remainingTime })); + } else { + textBuilder.push(statusTooltip); + } } return textBuilder.join(' '); @@ -88,6 +98,7 @@ export default { return this.job.status && this.job.status.action && this.job.status.action.path; }, }, + methods: { pipelineActionRequestComplete() { this.$emit('pipelineActionRequestComplete'); diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index 1f9187c3d65..8f004b491c8 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import ciHeader from '../../vue_shared/components/header_ci_component.vue'; import eventHub from '../event_hub'; @@ -6,6 +7,7 @@ export default { name: 'PipelineHeaderSection', components: { ciHeader, + GlLoadingIcon, }, props: { pipeline: { diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index 07a4af3e61e..cb47704ca26 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -4,6 +4,7 @@ import eventHub from '../event_hub'; import Icon from '../../vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { directives: { @@ -12,6 +13,7 @@ export default { components: { Icon, GlCountdown, + GlLoadingIcon, }, props: { actions: { diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 7ec55792850..3df8f7a6da6 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -13,6 +13,7 @@ */ import $ from 'jquery'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { __ } from '../../locale'; import Flash from '../../flash'; import axios from '../../lib/utils/axios_utils'; @@ -26,6 +27,7 @@ export default { components: { Icon, JobItem, + GlLoadingIcon, }, directives: { diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 85781f548c6..41bc5dcce5c 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -1,4 +1,5 @@ import Visibility from 'visibilityjs'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { __ } from '../../locale'; import Flash from '../../flash'; import Poll from '../../lib/utils/poll'; @@ -13,6 +14,7 @@ export default { PipelinesTableComponent, SvgBlankState, EmptyState, + GlLoadingIcon, }, data() { return { diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js index d5266544307..f5dae5ad808 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import store from '../store'; @@ -11,6 +12,7 @@ export default { DropdownButton, DropdownSearchInput, DropdownHiddenInput, + GlLoadingIcon, }, props: { fieldId: { diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index 120b4fc2f2b..9a729ca9b91 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -5,6 +5,7 @@ import Poll from '~/lib/utils/poll'; import Flash from '~/flash'; import { s__, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import CommitPipelineService from '../services/commit_pipeline_service'; export default { @@ -13,6 +14,7 @@ export default { }, components: { ciIcon, + GlLoadingIcon, }, props: { endpoint: { diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index 9dd1c87a87d..0a906f40f5a 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -1,5 +1,6 @@ <script> import { mapGetters, mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Flash from '../../flash'; import store from '../stores'; import collapsibleContainer from './collapsible_container.vue'; @@ -9,6 +10,7 @@ export default { name: 'RegistryListApp', components: { collapsibleContainer, + GlLoadingIcon, }, props: { endpoint: { diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 501b2625ae5..be9816a55c4 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,5 +1,6 @@ <script> import { mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Flash from '../../flash'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -12,6 +13,7 @@ export default { components: { clipboardButton, tableRegistry, + GlLoadingIcon, }, directives: { tooltip, diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue index 51188981bed..a44ba833b63 100644 --- a/app/assets/javascripts/reports/components/summary_row.vue +++ b/app/assets/javascripts/reports/components/summary_row.vue @@ -1,6 +1,7 @@ <script> import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Popover from '~/vue_shared/components/help_popover.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; /** * Renders the summary row for each report @@ -15,6 +16,7 @@ export default { components: { CiIcon, Popover, + GlLoadingIcon, }, props: { summary: { diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index 11b5dbe5f8e..fe73f6a0cef 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -2,6 +2,7 @@ import { __, n__, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { directives: { @@ -9,6 +10,7 @@ export default { }, components: { userAvatarImage, + GlLoadingIcon, }, props: { loading: { diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue index bc59774f0a8..913a616d9f1 100644 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue +++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue @@ -1,6 +1,7 @@ <script> import { __ } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import Icon from '~/vue_shared/components/icon.vue'; @@ -13,6 +14,7 @@ export default { }, components: { Icon, + GlLoadingIcon, }, props: { issuableId: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index ba6a1687e51..b3340290ed3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,9 +1,11 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import ciIcon from '../../vue_shared/components/ci_icon.vue'; export default { components: { ciIcon, + GlLoadingIcon, }, props: { status: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index 4f8b07484c0..4bfbdcf1404 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -6,6 +7,7 @@ export default { name: 'MRWidgetAutoMergeFailed', components: { statusIcon, + GlLoadingIcon, }, props: { mr: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 656c3b5c47e..7e33021e4b4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -6,6 +6,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; import statusIcon from '../mr_widget_status_icon.vue'; import eventHub from '../../event_hub'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { name: 'MRWidgetMerged', @@ -16,6 +17,7 @@ export default { MrWidgetAuthorTime, statusIcon, ClipboardButton, + GlLoadingIcon, }, props: { mr: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index 041fa13a8f5..0e714cc2aa1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import simplePoll from '../../../lib/utils/simple_poll'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -8,6 +9,7 @@ export default { name: 'MRWidgetRebase', components: { statusIcon, + GlLoadingIcon, }, props: { mr: { diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index 8163947cd0c..6f2f0f98690 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -17,19 +17,37 @@ export default { type: Boolean, default: true, }, + innerCssClasses: { + type: [Array, Object, String], + required: false, + default: '', + }, }, data() { return { width: 0, height: 0, - isZoomable: false, - isZoomed: false, + isLoaded: false, }; }, computed: { fileSizeReadable() { return numberToHumanSize(this.fileSize); }, + dimensionStyles() { + if (!this.isLoaded) return {}; + + return { + width: `${this.width}px`, + height: `${this.height}px`, + }; + }, + hasFileSize() { + return this.fileSize > 0; + }, + hasDimensions() { + return this.width && this.height; + }, }, beforeDestroy() { window.removeEventListener('resize', this.resizeThrottled, false); @@ -48,51 +66,52 @@ export default { const { contentImg } = this.$refs; if (contentImg) { - this.isZoomable = - contentImg.naturalWidth > contentImg.width || - contentImg.naturalHeight > contentImg.height; - this.width = contentImg.naturalWidth; this.height = contentImg.naturalHeight; - this.$emit('imgLoaded', { - width: this.width, - height: this.height, - renderedWidth: contentImg.clientWidth, - renderedHeight: contentImg.clientHeight, + this.$nextTick(() => { + this.isLoaded = true; + + this.$emit('imgLoaded', { + width: this.width, + height: this.height, + renderedWidth: contentImg.clientWidth, + renderedHeight: contentImg.clientHeight, + }); }); } }, - onImgClick() { - if (this.isZoomable) this.isZoomed = !this.isZoomed; - }, }, }; </script> <template> - <div class="file-container"> - <div class="file-content image_file"> + <div> + <div + :class="innerCssClasses" + :style="dimensionStyles" + class="position-relative" + > <img ref="contentImg" - :class="{ 'is-zoomable': isZoomable, 'is-zoomed': isZoomed }" :src="path" - :alt="path" @load="onImgLoad" - @click="onImgClick"/> - <p - v-if="renderInfo" - class="file-info prepend-top-10"> - <template v-if="fileSize>0"> - {{ fileSizeReadable }} - </template> - <template v-if="fileSize>0 && width && height"> - | - </template> - <template v-if="width && height"> - W: {{ width }} | H: {{ height }} - </template> - </p> + /> + <slot name="image-overlay"></slot> </div> + <p + v-if="renderInfo" + class="image-info" + > + <template v-if="hasFileSize"> + {{ fileSizeReadable }} + </template> + <template v-if="hasFileSize && hasDimensions"> + | + </template> + <template v-if="hasDimensions"> + <strong>W</strong>: {{ width }} | <strong>H</strong>: {{ height }} + </template> + </p> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue index cfc5343217c..9c3f3e7f7a9 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue @@ -69,6 +69,13 @@ export default { :new-path="fullNewPath" :old-path="fullOldPath" :project-path="projectPath" - /> + > + <slot + slot="image-overlay" + name="image-overlay" + > + </slot> + </component> + <slot></slot> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue index 38e881d17a2..cd0c1e850af 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue @@ -15,11 +15,6 @@ export default { type: String, required: true, }, - projectPath: { - type: String, - required: false, - default: '', - }, }, data() { return { @@ -120,7 +115,6 @@ export default { key="onionOldImg" :render-info="false" :path="oldPath" - :project-path="projectPath" @imgLoaded="onionOldImgLoaded" /> </div> @@ -136,9 +130,14 @@ export default { key="onionNewImg" :render-info="false" :path="newPath" - :project-path="projectPath" @imgLoaded="onionNewImgLoaded" - /> + > + <slot + slot="image-overlay" + name="image-overlay" + > + </slot> + </image-viewer> </div> <div class="controls"> <div class="transparent"></div> diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue index 86366c799a2..c3cfe54eb4d 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue @@ -16,11 +16,6 @@ export default { type: String, required: true, }, - projectPath: { - type: String, - required: false, - default: '', - }, }, data() { return { @@ -117,16 +112,14 @@ export default { 'height': swipeMaxPixelHeight, }" class="swipe-frame"> - <div class="frame deleted"> - <image-viewer - key="swipeOldImg" - ref="swipeOldImg" - :render-info="false" - :path="oldPath" - :project-path="projectPath" - @imgLoaded="swipeOldImgLoaded" - /> - </div> + <image-viewer + key="swipeOldImg" + ref="swipeOldImg" + :render-info="false" + :path="oldPath" + class="frame deleted" + @imgLoaded="swipeOldImgLoaded" + /> <div ref="swipeWrap" :style="{ @@ -134,15 +127,19 @@ export default { 'height': swipeMaxPixelHeight, }" class="swipe-wrap"> - <div class="frame added"> - <image-viewer - key="swipeNewImg" - :render-info="false" - :path="newPath" - :project-path="projectPath" - @imgLoaded="swipeNewImgLoaded" - /> - </div> + <image-viewer + key="swipeNewImg" + :render-info="false" + :path="newPath" + class="frame added" + @imgLoaded="swipeNewImgLoaded" + > + <slot + slot="image-overlay" + name="image-overlay" + > + </slot> + </image-viewer> </div> <span ref="swipeBar" diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/two_up_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/two_up_viewer.vue index 9c19266ecdf..9806d65e940 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/two_up_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/two_up_viewer.vue @@ -14,28 +14,29 @@ export default { type: String, required: true, }, - projectPath: { - type: String, - required: false, - default: '', - }, }, }; </script> <template> - <div class="two-up view row"> - <div class="col-sm-6 frame deleted"> - <image-viewer - :path="oldPath" - :project-path="projectPath" - /> - </div> - <div class="col-sm-6 frame added"> - <image-viewer - :path="newPath" - :project-path="projectPath" - /> - </div> + <div class="two-up view"> + <image-viewer + :path="oldPath" + :render-info="true" + inner-css-classes="frame deleted" + class="wrap" + /> + <image-viewer + :path="newPath" + :render-info="true" + :inner-css-classes="['frame', 'added']" + class="wrap" + > + <slot + slot="image-overlay" + name="image-overlay" + > + </slot> + </image-viewer> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue index 1af85283277..e68a2aa73fa 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue @@ -8,9 +8,6 @@ import { diffModes, imageViewMode } from '../constants'; export default { components: { ImageViewer, - TwoUpViewer, - SwipeViewer, - OnionSkinViewer, }, props: { diffMode: { @@ -25,17 +22,32 @@ export default { type: String, required: true, }, - projectPath: { - type: String, - required: false, - default: '', - }, }, data() { return { mode: imageViewMode.twoup, }; }, + computed: { + imageViewComponent() { + switch (this.mode) { + case imageViewMode.twoup: + return TwoUpViewer; + case imageViewMode.swipe: + return SwipeViewer; + case imageViewMode.onion: + return OnionSkinViewer; + default: + return undefined; + } + }, + isNew() { + return this.diffMode === diffModes.new; + }, + imagePath() { + return this.isNew ? this.newPath : this.oldPath; + }, + }, methods: { changeMode(newMode) { this.mode = newMode; @@ -52,15 +64,16 @@ export default { v-if="diffMode === $options.diffModes.replaced" class="diff-viewer"> <div class="image js-replaced-image"> - <two-up-viewer - v-if="mode === $options.imageViewMode.twoup" - v-bind="$props"/> - <swipe-viewer - v-else-if="mode === $options.imageViewMode.swipe" - v-bind="$props"/> - <onion-skin-viewer - v-else-if="mode === $options.imageViewMode.onion" - v-bind="$props"/> + <component + :is="imageViewComponent" + v-bind="$props" + > + <slot + slot="image-overlay" + name="image-overlay" + > + </slot> + </component> </div> <div class="view-modes"> <ul class="view-modes-menu"> @@ -87,23 +100,27 @@ export default { </li> </ul> </div> - <div class="note-container"></div> - </div> - <div - v-else-if="diffMode === $options.diffModes.new" - class="diff-viewer added"> - <image-viewer - :path="newPath" - :project-path="projectPath" - /> </div> <div v-else - class="diff-viewer deleted"> - <image-viewer - :path="oldPath" - :project-path="projectPath" - /> + class="diff-viewer" + > + <div class="image"> + <image-viewer + :path="imagePath" + :inner-css-classes="['frame', { + 'added': isNew, + 'deleted': diffMode === $options.diffModes.deleted + }]" + > + <slot + v-if="isNew" + slot="image-overlay" + name="image-overlay" + > + </slot> + </image-viewer> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue index 31087017968..0e194eaaed5 100644 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue +++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue @@ -1,7 +1,11 @@ <script> import { __ } from '~/locale'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { + components: { + GlLoadingIcon, + }, props: { isDisabled: { type: Boolean, diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue index 408f7d7965f..03818be6a69 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import getIconForFile from './file_icon/file_icon_map'; import icon from '../../vue_shared/components/icon.vue'; @@ -17,6 +18,7 @@ import icon from '../../vue_shared/components/icon.vue'; export default { components: { icon, + GlLoadingIcon, }, props: { fileName: { diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue index f9b7fd5b1f9..69d7e5c46f5 100644 --- a/app/assets/javascripts/vue_shared/components/loading_button.vue +++ b/app/assets/javascripts/vue_shared/components/loading_button.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; /* eslint-disable vue/require-default-prop */ /* This is a re-usable vue component for rendering a button that will probably be sending off ajax requests and need @@ -18,6 +19,9 @@ */ export default { + components: { + GlLoadingIcon, + }, props: { loading: { type: Boolean, diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 500586302cf..5b12bb6b59e 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import datePicker from '../pikaday.vue'; import toggleSidebar from './toggle_sidebar.vue'; import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; @@ -10,6 +11,7 @@ export default { datePicker, toggleSidebar, collapsedCalendarIcon, + GlLoadingIcon, }, props: { blockClass: { diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue index 3df286de129..e50d612ce36 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue @@ -4,6 +4,7 @@ import { __ } from '~/locale'; import LabelsSelect from '~/labels_select'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import DropdownTitle from './dropdown_title.vue'; import DropdownValue from './dropdown_value.vue'; import DropdownValueCollapsed from './dropdown_value_collapsed.vue'; @@ -24,6 +25,7 @@ export default { DropdownSearchInput, DropdownFooter, DropdownCreateLabel, + GlLoadingIcon, }, props: { showCreate: { diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue index 4e9289cbed8..e7cb5cfac12 100644 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { s__ } from '../../locale'; import icon from './icon.vue'; @@ -10,6 +11,7 @@ const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF'); export default { components: { icon, + GlLoadingIcon, }, model: { diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue index 86c7498a092..dd6f96e2609 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue @@ -99,6 +99,6 @@ export default { v-tooltip :title="tooltipText" :tooltip-placement="tooltipPlacement" - >{{ username }}</span> + >{{ username }}</span><slot name="avatar-badge"></slot> </gl-link> </template> |