90 lines
3.2 KiB
TypeScript
90 lines
3.2 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";
|
|
mime: "image/png";
|
|
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)}`;
|
|
}
|