Refactor thumbnail size handling to use new enum

feat/add-thumbnail-router
Ryan Wang 2025-09-29 23:39:07 +08:00
parent d24d6d5c07
commit 57602d60a2
9 changed files with 79 additions and 131 deletions

View File

@ -6679,12 +6679,18 @@
}
},
{
"description": "The size of the thumbnail,available values are s,m,l,xl",
"description": "The size of the thumbnail",
"in": "query",
"name": "size",
"required": true,
"schema": {
"type": "string"
"type": "string",
"enum": [
"S",
"M",
"L",
"XL"
]
}
}
],
@ -20587,12 +20593,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
@ -22363,12 +22369,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},

View File

@ -5475,12 +5475,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
@ -6015,12 +6015,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},

View File

@ -12903,12 +12903,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
@ -14281,12 +14281,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},

View File

@ -1159,12 +1159,18 @@
}
},
{
"description": "The size of the thumbnail,available values are s,m,l,xl",
"description": "The size of the thumbnail",
"in": "query",
"name": "size",
"required": true,
"schema": {
"type": "string"
"type": "string",
"enum": [
"S",
"M",
"L",
"XL"
]
}
}
],
@ -2613,12 +2619,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},
@ -3191,12 +3197,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},

View File

@ -2470,12 +2470,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
]
],
"default": "PUBLIC"
}
}
},

View File

@ -1,87 +1,39 @@
<script lang="ts" setup>
import {
LocalThumbnailStatusPhaseEnum,
type LocalThumbnail,
} from "@halo-dev/api-client";
import { VButton } from "@halo-dev/components";
import { toRefs } from "vue";
import { useThumbnailControl } from "../composables/use-thumbnail-control";
import {
SIZE_MAP,
useThumbnailDetail,
} from "../composables/use-thumbnail-detail";
import { SIZE_MAP } from "../composables/use-thumbnail-detail";
const props = withDefaults(
const { size, permalink } = withDefaults(
defineProps<{
thumbnail: LocalThumbnail;
size: string;
permalink: string;
}>(),
{}
);
const { thumbnail } = toRefs(props);
const { phase } = useThumbnailDetail(thumbnail);
const { handleRetry } = useThumbnailControl(thumbnail);
</script>
<template>
<li>
<div
class="flex w-full cursor-pointer items-center justify-between space-x-3 rounded border p-3 hover:border-primary"
>
<a
:href="thumbnail.spec.thumbnailUri"
target="_blank"
class="block flex-none"
>
<a :href="permalink" target="_blank" class="block flex-none">
<img
v-if="
thumbnail.status.phase === LocalThumbnailStatusPhaseEnum.Succeeded
"
:src="thumbnail.spec.thumbnailUri"
:src="permalink"
alt=""
class="h-10 w-10 rounded-md object-cover"
loading="lazy"
/>
<div
v-else
class="flex h-10 w-10 items-center justify-center rounded-md border"
>
<component
:is="phase.icon"
class="h-4.5 w-4.5"
:class="phase.color"
/>
</div>
</a>
<div class="flex min-w-0 flex-1 flex-col space-y-2 text-xs text-gray-900">
<span class="font-semibold">
{{ SIZE_MAP[thumbnail.spec.size] }}
{{ SIZE_MAP[size] }}
</span>
<a
:href="thumbnail.spec.thumbnailUri"
:href="permalink"
target="_blank"
class="line-clamp-1 hover:text-gray-600"
>
{{ thumbnail.spec.thumbnailUri }}
{{ permalink }}
</a>
</div>
<div class="flex flex-none items-center gap-2">
<component
:is="phase.icon"
v-tooltip="$t(phase.label)"
class="h-4.5 w-4.5"
:class="phase.color"
/>
<VButton
v-if="
thumbnail.status.phase !== LocalThumbnailStatusPhaseEnum.Succeeded
"
size="sm"
@click="handleRetry"
>
{{ $t("core.common.buttons.retry") }}
</VButton>
</div>
</div>
</li>
</template>

View File

@ -1,14 +1,8 @@
<script lang="ts" setup>
import { storageAnnotations } from "@/constants/annotations";
import {
coreApiClient,
LocalThumbnailSpecSizeEnum,
LocalThumbnailStatusPhaseEnum,
GetThumbnailByUriSizeEnum,
type Attachment,
} from "@halo-dev/api-client";
import { VLoading } from "@halo-dev/components";
import { useQuery } from "@tanstack/vue-query";
import sha256 from "crypto-js/sha256";
import { computed, toRefs } from "vue";
import AttachmentSingleThumbnailItem from "./AttachmentSingleThumbnailItem.vue";
@ -23,53 +17,33 @@ const props = withDefaults(
const { attachment } = toRefs(props);
const imageSignature = computed(() => {
const uri = attachment.value?.metadata.annotations?.[storageAnnotations.URI];
if (!uri) {
return undefined;
}
return sha256(uri);
});
const sizeOrder: Record<LocalThumbnailSpecSizeEnum, number> = {
const sizeOrder: Record<GetThumbnailByUriSizeEnum, number> = {
XL: 4,
L: 3,
M: 2,
S: 1,
};
const { data: thumbnails, isLoading } = useQuery({
queryKey: ["core:attachments:thumbnails", attachment, imageSignature],
queryFn: async () => {
const { data } =
await coreApiClient.storage.localThumbnail.listLocalThumbnail({
fieldSelector: [`spec.imageSignature=${imageSignature.value}`],
});
return data.items.sort((a, b) => {
const aSize = a.spec.size as keyof typeof sizeOrder;
const bSize = b.spec.size as keyof typeof sizeOrder;
return (sizeOrder[bSize] || 0) - (sizeOrder[aSize] || 0);
});
},
enabled: computed(() => !!imageSignature.value),
refetchInterval: (data) => {
const hasAbnormalData = data?.some(
(thumbnail) =>
thumbnail.status.phase !== LocalThumbnailStatusPhaseEnum.Succeeded
);
return hasAbnormalData ? 1000 : false;
},
const thumbnails = computed(() => {
return Object.entries(attachment.value?.status?.thumbnails || {})
.sort(
([sizeA], [sizeB]) =>
(sizeOrder[sizeB as GetThumbnailByUriSizeEnum] || 0) -
(sizeOrder[sizeA as GetThumbnailByUriSizeEnum] || 0)
)
.map(([size, permalink]) => ({
size: size as GetThumbnailByUriSizeEnum,
permalink,
}));
});
</script>
<template>
<VLoading v-if="isLoading" />
<ul v-else class="flex flex-col space-y-2">
<ul class="flex flex-col space-y-2">
<AttachmentSingleThumbnailItem
v-for="thumbnail in thumbnails"
:key="thumbnail.metadata.name"
:thumbnail="thumbnail"
:key="thumbnail.size"
:size="thumbnail.size"
:permalink="thumbnail.permalink"
/>
</ul>
</template>

View File

@ -1,5 +1,5 @@
import {
LocalThumbnailSpecSizeEnum,
GetThumbnailByUriSizeEnum,
LocalThumbnailStatusPhaseEnum,
type LocalThumbnail,
} from "@halo-dev/api-client";
@ -7,7 +7,7 @@ import { IconCheckboxCircle, IconErrorWarning } from "@halo-dev/components";
import { computed, type Component, type Ref } from "vue";
import RiTimeLine from "~icons/ri/time-line";
export const SIZE_MAP: Record<LocalThumbnailSpecSizeEnum, string> = {
export const SIZE_MAP: Record<GetThumbnailByUriSizeEnum, string> = {
XL: "1600w",
L: "1200w",
M: "800w",

View File

@ -30,11 +30,11 @@ export const ThumbnailV1alpha1PublicApiAxiosParamCreator = function (configurati
/**
* Get thumbnail by URI
* @param {string} uri The URI of the image
* @param {string} size The size of the thumbnail,available values are s,m,l,xl
* @param {GetThumbnailByUriSizeEnum} size The size of the thumbnail
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getThumbnailByUri: async (uri: string, size: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
getThumbnailByUri: async (uri: string, size: GetThumbnailByUriSizeEnum, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'uri' is not null or undefined
assertParamExists('getThumbnailByUri', 'uri', uri)
// verify required parameter 'size' is not null or undefined
@ -91,11 +91,11 @@ export const ThumbnailV1alpha1PublicApiFp = function(configuration?: Configurati
/**
* Get thumbnail by URI
* @param {string} uri The URI of the image
* @param {string} size The size of the thumbnail,available values are s,m,l,xl
* @param {GetThumbnailByUriSizeEnum} size The size of the thumbnail
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getThumbnailByUri(uri: string, size: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
async getThumbnailByUri(uri: string, size: GetThumbnailByUriSizeEnum, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getThumbnailByUri(uri, size, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['ThumbnailV1alpha1PublicApi.getThumbnailByUri']?.[localVarOperationServerIndex]?.url;
@ -137,11 +137,11 @@ export interface ThumbnailV1alpha1PublicApiGetThumbnailByUriRequest {
readonly uri: string
/**
* The size of the thumbnail,available values are s,m,l,xl
* @type {string}
* The size of the thumbnail
* @type {'S' | 'M' | 'L' | 'XL'}
* @memberof ThumbnailV1alpha1PublicApiGetThumbnailByUri
*/
readonly size: string
readonly size: GetThumbnailByUriSizeEnum
}
/**
@ -163,3 +163,13 @@ export class ThumbnailV1alpha1PublicApi extends BaseAPI {
}
}
/**
* @export
*/
export const GetThumbnailByUriSizeEnum = {
S: 'S',
M: 'M',
L: 'L',
Xl: 'XL'
} as const;
export type GetThumbnailByUriSizeEnum = typeof GetThumbnailByUriSizeEnum[keyof typeof GetThumbnailByUriSizeEnum];