diff options
Diffstat (limited to 'app/assets/javascripts/packages_and_registries/shared/components')
6 files changed, 461 insertions, 0 deletions
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue new file mode 100644 index 00000000000..105f7bbe132 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue @@ -0,0 +1,17 @@ +<script> +import { GlIcon } from '@gitlab/ui'; + +export default { + name: 'PackageIconAndName', + components: { + GlIcon, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon name="package" class="gl-ml-3 gl-mr-2" /> + <span><slot></slot></span> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue new file mode 100644 index 00000000000..6fb001e5e92 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue @@ -0,0 +1,86 @@ +<script> +import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui'; +import { joinPaths } from '~/lib/utils/url_utility'; + +export default { + name: 'PackagePath', + components: { + GlIcon, + GlLink, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + path: { + type: String, + required: true, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + pathPieces() { + return this.path.split('/'); + }, + root() { + // we skip the first part of the path since is the 'base' group + return this.pathPieces[1]; + }, + rootLink() { + return joinPaths(this.pathPieces[0], this.root); + }, + leaf() { + return this.pathPieces[this.pathPieces.length - 1]; + }, + deeplyNested() { + return this.pathPieces.length > 3; + }, + hasGroup() { + return this.root !== this.leaf; + }, + }, +}; +</script> + +<template> + <div data-qa-selector="package-path" class="gl-display-flex gl-align-items-center"> + <gl-icon data-testid="base-icon" name="project" class="gl-mx-3 gl-min-w-0" /> + + <gl-link + data-testid="root-link" + class="gl-text-gray-500 gl-min-w-0" + :href="`/${rootLink}`" + :disabled="disabled" + > + {{ root }} + </gl-link> + + <template v-if="hasGroup"> + <gl-icon data-testid="root-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" /> + + <template v-if="deeplyNested"> + <span + v-gl-tooltip="{ title: path }" + data-testid="ellipsis-icon" + class="gl-inset-border-1-gray-200 gl-rounded-base gl-px-2 gl-min-w-0" + > + <gl-icon name="ellipsis_h" /> + </span> + <gl-icon data-testid="ellipsis-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" /> + </template> + + <gl-link + data-testid="leaf-link" + class="gl-text-gray-500 gl-min-w-0" + :href="`/${path}`" + :disabled="disabled" + > + {{ leaf }} + </gl-link> + </template> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue new file mode 100644 index 00000000000..5ec950e4d45 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue @@ -0,0 +1,110 @@ +<script> +import { GlBadge, GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; +import { n__ } from '~/locale'; + +export default { + name: 'PackageTags', + components: { + GlBadge, + GlIcon, + GlSprintf, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + tagDisplayLimit: { + type: Number, + required: false, + default: 2, + }, + tags: { + type: Array, + required: true, + default: () => [], + }, + hideLabel: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + tagCount() { + return this.tags.length; + }, + tagsToRender() { + return this.tags.slice(0, this.tagDisplayLimit); + }, + moreTagsDisplay() { + return Math.max(0, this.tags.length - this.tagDisplayLimit); + }, + moreTagsTooltip() { + if (this.moreTagsDisplay) { + return this.tags + .slice(this.tagDisplayLimit) + .map((x) => x.name) + .join(', '); + } + + return ''; + }, + tagsDisplay() { + return n__('%d tag', '%d tags', this.tagCount); + }, + }, + methods: { + tagBadgeClass(index) { + return { + 'gl-display-none': true, + 'gl-display-flex': this.tagCount === 1, + 'd-md-flex': this.tagCount > 1, + 'gl-mr-2': index !== this.tagsToRender.length - 1, + 'gl-ml-3': !this.hideLabel && index === 0, + }; + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center"> + <div v-if="!hideLabel" data-testid="tagLabel" class="gl-display-flex gl-align-items-center"> + <gl-icon name="labels" class="gl-text-gray-500 gl-mr-3" /> + <span class="gl-font-weight-bold">{{ tagsDisplay }}</span> + </div> + + <gl-badge + v-for="(tag, index) in tagsToRender" + :key="index" + data-testid="tagBadge" + :class="tagBadgeClass(index)" + variant="info" + size="sm" + >{{ tag.name }}</gl-badge + > + + <gl-badge + v-if="moreTagsDisplay" + v-gl-tooltip + data-testid="moreBadge" + variant="muted" + :title="moreTagsTooltip" + size="sm" + class="gl-display-none gl-md-display-flex gl-ml-2" + ><gl-sprintf :message="__('+%{tags} more')"> + <template #tags> + {{ moreTagsDisplay }} + </template> + </gl-sprintf></gl-badge + > + + <gl-badge + v-if="moreTagsDisplay && hideLabel" + data-testid="moreBadge" + variant="muted" + class="gl-md-display-none gl-ml-2" + >{{ tagsDisplay }}</gl-badge + > + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue b/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue new file mode 100644 index 00000000000..cf555f46f8c --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue @@ -0,0 +1,60 @@ +<script> +import { GlSkeletonLoader } from '@gitlab/ui'; + +export default { + components: { + GlSkeletonLoader, + }, + shapes: [ + { type: 'rect', width: '220', height: '10', x: '0', y: '20' }, + { type: 'rect', width: '60', height: '10', x: '305', y: '20' }, + { type: 'rect', width: '60', height: '10', x: '535', y: '20' }, + { type: 'rect', width: '100', height: '10', x: '760', y: '20' }, + { type: 'rect', width: '30', height: '30', x: '970', y: '10', ref: 'button-loader' }, + ], + rowsToRender: { + mobile: 5, + desktop: 20, + }, +}; +</script> + +<template> + <div> + <div class="gl-flex-direction-column gl-sm-display-none" data-testid="mobile-loader"> + <gl-skeleton-loader + v-for="index in $options.rowsToRender.mobile" + :key="index" + :width="500" + :height="170" + preserve-aspect-ratio="xMinYMax meet" + > + <rect width="500" height="10" x="0" y="15" rx="4" /> + <rect width="500" height="10" x="0" y="45" rx="4" /> + <rect width="500" height="10" x="0" y="75" rx="4" /> + <rect width="500" height="10" x="0" y="105" rx="4" /> + <rect width="500" height="10" x="0" y="135" rx="4" /> + </gl-skeleton-loader> + </div> + <div + class="gl-display-none gl-sm-display-flex gl-flex-direction-column" + data-testid="desktop-loader" + > + <gl-skeleton-loader + v-for="index in $options.rowsToRender.desktop" + :key="index" + :width="1000" + :height="54" + preserve-aspect-ratio="xMinYMax meet" + > + <component + :is="r.type" + v-for="(r, rIndex) in $options.shapes" + :key="rIndex" + rx="4" + v-bind="r" + /> + </gl-skeleton-loader> + </div> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue b/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue new file mode 100644 index 00000000000..8a66a33f2ab --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue @@ -0,0 +1,64 @@ +<script> +import { GlIcon, GlLink } from '@gitlab/ui'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { getCommitLink } from '../utils'; + +export default { + name: 'PublishMethod', + components: { + ClipboardButton, + GlIcon, + GlLink, + }, + props: { + packageEntity: { + type: Object, + required: true, + }, + isGroup: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + hasPipeline() { + return Boolean(this.packageEntity.pipeline); + }, + packageShaShort() { + return this.packageEntity.pipeline?.sha.substring(0, 8); + }, + linkToCommit() { + return getCommitLink(this.packageEntity, this.isGroup); + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center"> + <template v-if="hasPipeline"> + <gl-icon name="git-merge" class="gl-mr-2" /> + <span data-testid="pipeline-ref" class="gl-mr-2">{{ packageEntity.pipeline.ref }}</span> + + <gl-icon name="commit" class="gl-mr-2" /> + <gl-link data-testid="pipeline-sha" :href="linkToCommit" class="gl-mr-2">{{ + packageShaShort + }}</gl-link> + + <clipboard-button + :text="packageEntity.pipeline.sha" + :title="__('Copy commit SHA')" + category="tertiary" + size="small" + /> + </template> + + <template v-else> + <gl-icon name="upload" class="gl-mr-2" /> + <span data-testid="manually-published"> + {{ s__('PackageRegistry|Manually Published') }} + </span> + </template> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue new file mode 100644 index 00000000000..79381f82009 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue @@ -0,0 +1,124 @@ +<script> +import { GlButton, GlFormCheckbox, GlKeysetPagination } from '@gitlab/ui'; +import { filter } from 'lodash'; +import { __ } from '~/locale'; + +export default { + name: 'RegistryList', + components: { + GlButton, + GlFormCheckbox, + GlKeysetPagination, + }, + props: { + title: { + type: String, + required: true, + }, + isLoading: { + type: Boolean, + default: false, + required: false, + }, + hiddenDelete: { + type: Boolean, + default: false, + required: false, + }, + pagination: { + type: Object, + required: false, + default: () => ({}), + }, + items: { + type: Array, + required: false, + default: () => [], + }, + idProperty: { + type: String, + required: false, + default: 'id', + }, + }, + data() { + return { + selectedReferences: {}, + }; + }, + computed: { + showPagination() { + return this.pagination.hasPreviousPage || this.pagination.hasNextPage; + }, + disableDeleteButton() { + return this.isLoading || filter(this.selectedReferences).length === 0; + }, + selectedItems() { + return this.items.filter(this.isSelected); + }, + selectAll: { + get() { + return this.items.every(this.isSelected); + }, + set(value) { + this.items.forEach((item) => { + const id = item[this.idProperty]; + this.$set(this.selectedReferences, id, value); + }); + }, + }, + }, + methods: { + selectItem(item) { + const id = item[this.idProperty]; + this.$set(this.selectedReferences, id, !this.selectedReferences[id]); + }, + isSelected(item) { + const id = item[this.idProperty]; + return this.selectedReferences[id]; + }, + }, + i18n: { + deleteSelected: __('Delete Selected'), + }, +}; +</script> + +<template> + <div> + <div class="gl-display-flex gl-justify-content-space-between gl-mb-3 gl-align-items-center"> + <gl-form-checkbox v-if="!hiddenDelete" v-model="selectAll" class="gl-ml-2 gl-pt-2"> + <span class="gl-font-weight-bold">{{ title }}</span> + </gl-form-checkbox> + + <gl-button + v-if="!hiddenDelete" + :disabled="disableDeleteButton" + category="secondary" + variant="danger" + @click="$emit('delete', selectedItems)" + > + {{ $options.i18n.deleteSelected }} + </gl-button> + </div> + + <div v-for="(item, index) in items" :key="index"> + <slot + :select-item="selectItem" + :is-selected="isSelected" + :item="item" + :first="index === 0" + ></slot> + </div> + + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + v-bind="pagination" + class="gl-mt-3" + @prev="$emit('prev-page')" + @next="$emit('next-page')" + /> + </div> + </div> +</template> |