Files
tyx_AI_xhs/packages/static-sticker-catalog/src/index.ts
T
suyx c92a91a127
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 30s
feat: publish admin sticker releases (TASK-WP5-05)
2026-08-03 19:43:10 +08:00

90 lines
3.3 KiB
TypeScript

export const STATIC_STICKER_CATALOG_SCHEMA_VERSION = "StaticStickerCatalog/v1" as const;
export const STATIC_STICKER_CATALOG_ORIGIN = "bundled_read_only" as const;
export interface StaticStickerThumbnailReference {
media: "thumbnail";
resource_id: string;
resource_version: string;
url: string;
}
export interface StaticStickerCatalogItem {
stable_id: string;
part: number;
order: number;
original_filename: string;
relative_path: string;
width: number;
height: number;
mime_type: "image/png" | "image/webp";
mime: "image/png" | "image/webp";
sha256: string;
original_reference: string;
thumbnail_reference: StaticStickerThumbnailReference;
resource_version: string;
enabled: boolean;
origin: "bundled_read_only" | "admin_uploaded";
}
export interface StaticStickerCatalog {
schema_version: typeof STATIC_STICKER_CATALOG_SCHEMA_VERSION;
release_version: string;
manifest_sha256: string;
count: number;
part_counts: Record<string, number>;
duplicate_sha256_groups: Array<{ sha256: string; stable_ids: string[] }>;
items: StaticStickerCatalogItem[];
}
export interface VirtualStickerWindowOptions {
itemHeight: number;
viewportHeight: number;
columns: number;
overscanScreens: number;
scrollTop: number;
}
export interface VirtualStickerWindow<T extends { stable_id: string }> {
start: number;
end: number;
top_spacer_px: number;
bottom_spacer_px: number;
items: T[];
}
export function stableStickerId(index: number): string {
if (!Number.isInteger(index) || index < 1) throw new Error("sticker_index_invalid");
return `STK${String(index).padStart(index < 1_000 ? 3 : 4, "0")}`;
}
export function getVirtualStickerWindow<T extends { stable_id: string }>(items: readonly T[], options: VirtualStickerWindowOptions): VirtualStickerWindow<T> {
const itemHeight = Math.max(1, options.itemHeight);
const viewportHeight = Math.max(1, options.viewportHeight);
const columns = Math.max(1, Math.floor(options.columns));
const overscanScreens = Math.max(0, options.overscanScreens);
const scrollTop = Math.max(0, options.scrollTop);
const totalRows = Math.ceil(items.length / columns);
const visibleRows = Math.max(1, Math.ceil(viewportHeight / itemHeight));
const overscanRows = Math.ceil((visibleRows * overscanScreens) / 2);
const firstVisibleRow = Math.min(totalRows, Math.floor(scrollTop / itemHeight));
const startRow = Math.max(0, firstVisibleRow - overscanRows);
const endRow = Math.min(totalRows, firstVisibleRow + visibleRows + overscanRows);
const start = startRow * columns;
const end = Math.min(items.length, endRow * columns);
return {
bottom_spacer_px: Math.max(0, (totalRows - endRow) * itemHeight),
end,
items: items.slice(start, end) as T[],
start,
top_spacer_px: startRow * itemHeight,
};
}
export function staticStickerThumbnailUrl(item: Pick<StaticStickerCatalogItem, "resource_version" | "stable_id">): string {
return `/api/v1/assets/public/${encodeURIComponent(item.resource_version)}/${encodeURIComponent(item.stable_id)}?variant=thumbnail`;
}
export function staticStickerOriginalUrl(item: Pick<StaticStickerCatalogItem, "resource_version" | "stable_id">): string {
return `/api/v1/assets/public/${encodeURIComponent(item.resource_version)}/${encodeURIComponent(item.stable_id)}`;
}