summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/packages_and_registries/shared/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/packages_and_registries/shared/components')
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_path.vue86
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue110
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue60
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue64
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue124
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>