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", "in": "query",
"name": "size", "name": "size",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
"enum": [
"S",
"M",
"L",
"XL"
]
} }
} }
], ],
@ -20587,12 +20593,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "PRIVATE"
] ],
"default": "PUBLIC"
} }
} }
}, },
@ -22363,12 +22369,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "PRIVATE"
] ],
"default": "PUBLIC"
} }
} }
}, },

View File

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

View File

@ -12903,12 +12903,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "PRIVATE"
] ],
"default": "PUBLIC"
} }
} }
}, },
@ -14281,12 +14281,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "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", "in": "query",
"name": "size", "name": "size",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
"enum": [
"S",
"M",
"L",
"XL"
]
} }
} }
], ],
@ -2613,12 +2619,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "PRIVATE"
] ],
"default": "PUBLIC"
} }
} }
}, },
@ -3191,12 +3197,12 @@
}, },
"visible": { "visible": {
"type": "string", "type": "string",
"default": "PUBLIC",
"enum": [ "enum": [
"PUBLIC", "PUBLIC",
"INTERNAL", "INTERNAL",
"PRIVATE" "PRIVATE"
] ],
"default": "PUBLIC"
} }
} }
}, },

View File

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

View File

@ -1,87 +1,39 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { SIZE_MAP } from "../composables/use-thumbnail-detail";
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";
const props = withDefaults( const { size, permalink } = withDefaults(
defineProps<{ defineProps<{
thumbnail: LocalThumbnail; size: string;
permalink: string;
}>(), }>(),
{} {}
); );
const { thumbnail } = toRefs(props);
const { phase } = useThumbnailDetail(thumbnail);
const { handleRetry } = useThumbnailControl(thumbnail);
</script> </script>
<template> <template>
<li> <li>
<div <div
class="flex w-full cursor-pointer items-center justify-between space-x-3 rounded border p-3 hover:border-primary" class="flex w-full cursor-pointer items-center justify-between space-x-3 rounded border p-3 hover:border-primary"
> >
<a <a :href="permalink" target="_blank" class="block flex-none">
:href="thumbnail.spec.thumbnailUri"
target="_blank"
class="block flex-none"
>
<img <img
v-if=" :src="permalink"
thumbnail.status.phase === LocalThumbnailStatusPhaseEnum.Succeeded
"
:src="thumbnail.spec.thumbnailUri"
alt="" alt=""
class="h-10 w-10 rounded-md object-cover" class="h-10 w-10 rounded-md object-cover"
loading="lazy" 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> </a>
<div class="flex min-w-0 flex-1 flex-col space-y-2 text-xs text-gray-900"> <div class="flex min-w-0 flex-1 flex-col space-y-2 text-xs text-gray-900">
<span class="font-semibold"> <span class="font-semibold">
{{ SIZE_MAP[thumbnail.spec.size] }} {{ SIZE_MAP[size] }}
</span> </span>
<a <a
:href="thumbnail.spec.thumbnailUri" :href="permalink"
target="_blank" target="_blank"
class="line-clamp-1 hover:text-gray-600" class="line-clamp-1 hover:text-gray-600"
> >
{{ thumbnail.spec.thumbnailUri }} {{ permalink }}
</a> </a>
</div> </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> </div>
</li> </li>
</template> </template>

View File

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

View File

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

View File

@ -30,11 +30,11 @@ export const ThumbnailV1alpha1PublicApiAxiosParamCreator = function (configurati
/** /**
* Get thumbnail by URI * Get thumbnail by URI
* @param {string} uri The URI of the image * @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. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @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 // verify required parameter 'uri' is not null or undefined
assertParamExists('getThumbnailByUri', 'uri', uri) assertParamExists('getThumbnailByUri', 'uri', uri)
// verify required parameter 'size' is not null or undefined // verify required parameter 'size' is not null or undefined
@ -91,11 +91,11 @@ export const ThumbnailV1alpha1PublicApiFp = function(configuration?: Configurati
/** /**
* Get thumbnail by URI * Get thumbnail by URI
* @param {string} uri The URI of the image * @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. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @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 localVarAxiosArgs = await localVarAxiosParamCreator.getThumbnailByUri(uri, size, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['ThumbnailV1alpha1PublicApi.getThumbnailByUri']?.[localVarOperationServerIndex]?.url; const localVarOperationServerBasePath = operationServerMap['ThumbnailV1alpha1PublicApi.getThumbnailByUri']?.[localVarOperationServerIndex]?.url;
@ -137,11 +137,11 @@ export interface ThumbnailV1alpha1PublicApiGetThumbnailByUriRequest {
readonly uri: string readonly uri: string
/** /**
* The size of the thumbnail,available values are s,m,l,xl * The size of the thumbnail
* @type {string} * @type {'S' | 'M' | 'L' | 'XL'}
* @memberof ThumbnailV1alpha1PublicApiGetThumbnailByUri * @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];