diff options
Diffstat (limited to 'apps/web/src/stickers/providers/shapes.ts')
| -rw-r--r-- | apps/web/src/stickers/providers/shapes.ts | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/apps/web/src/stickers/providers/shapes.ts b/apps/web/src/stickers/providers/shapes.ts new file mode 100644 index 00000000..e3137425 --- /dev/null +++ b/apps/web/src/stickers/providers/shapes.ts @@ -0,0 +1,206 @@ +import { buildGraphicPreviewUrl, buildDefaultGraphicInstance, graphicsRegistry, registerDefaultGraphics } from "@/graphics"; +import type { ParamValues } from "@/params"; +import { buildStickerId, parseStickerId } from "../sticker-id"; +import type { + StickerBrowseResult, + StickerItem, + StickerProvider, + StickerSearchResult, +} from "../types"; + +const SHAPES_PROVIDER_ID = "shapes"; + +type ShapeGraphicPreset = { + shapeKey: string; + name: string; + definitionId: string; + params?: ParamValues; +}; + +const LEGACY_SHAPE_PRESETS: Record<string, ShapeGraphicPreset> = { + square: { shapeKey: "square", name: "Square", definitionId: "rectangle" }, + circle: { shapeKey: "circle", name: "Circle", definitionId: "ellipse" }, + triangle: { + shapeKey: "triangle", + name: "Triangle", + definitionId: "polygon", + params: { sides: 3 }, + }, + hexagon: { + shapeKey: "hexagon", + name: "Hexagon", + definitionId: "polygon", + params: { sides: 6 }, + }, + diamond: { + shapeKey: "diamond", + name: "Diamond", + definitionId: "polygon", + params: { sides: 4 }, + }, + star: { shapeKey: "star", name: "Star", definitionId: "star" }, +}; + +function getShapePresets(): ShapeGraphicPreset[] { + registerDefaultGraphics(); + return graphicsRegistry.getAll().map((definition) => ({ + shapeKey: definition.id, + name: definition.name, + definitionId: definition.id, + })); +} + +function getShapePreset({ + shapeKey, +}: { + shapeKey: string; +}): ShapeGraphicPreset | null { + return ( + getShapePresets().find((preset) => preset.shapeKey === shapeKey) ?? + LEGACY_SHAPE_PRESETS[shapeKey] ?? + null + ); +} + +function getShapeParams({ + preset, +}: { + preset: ShapeGraphicPreset; +}): ParamValues { + return { + ...buildDefaultGraphicInstance({ definitionId: preset.definitionId }).params, + ...preset.params, + }; +} + +export function parseShapeStickerId({ + stickerId, +}: { + stickerId: string; +}): ShapeGraphicPreset | null { + try { + const { providerValue } = parseStickerId({ stickerId }); + return getShapePreset({ shapeKey: providerValue }); + } catch { + return null; + } +} + +function buildShapeUrl({ shapeKey }: { shapeKey: string }): string { + const preset = getShapePreset({ shapeKey }); + if (!preset) { + return buildGraphicPreviewUrl({ definitionId: "rectangle" }); + } + return buildGraphicPreviewUrl({ + definitionId: preset.definitionId, + params: getShapeParams({ preset }), + }); +} + +function toStickerItem({ + preset, +}: { + preset: ShapeGraphicPreset; +}): StickerItem { + return { + id: buildStickerId({ + providerId: SHAPES_PROVIDER_ID, + providerValue: preset.shapeKey, + }), + provider: SHAPES_PROVIDER_ID, + name: preset.name, + previewUrl: buildShapeUrl({ shapeKey: preset.shapeKey }), + metadata: { + definitionId: preset.definitionId, + params: preset.params ?? {}, + }, + }; +} + +function filterShapesByQuery({ query }: { query: string }): ShapeGraphicPreset[] { + const normalizedQuery = query.trim().toLowerCase(); + const presets = getShapePresets(); + if (!normalizedQuery) { + return presets; + } + + return presets.filter((preset) => { + const definition = graphicsRegistry.get(preset.definitionId); + return ( + preset.name.toLowerCase().includes(normalizedQuery) || + definition.keywords.some((keyword) => + keyword.toLowerCase().includes(normalizedQuery), + ) + ); + }); +} + +function paginateShapes({ + shapes, + options, +}: { + shapes: ShapeGraphicPreset[]; + options?: { page?: number; limit?: number }; +}): { items: ShapeGraphicPreset[]; hasMore: boolean; total: number } { + const page = Math.max(1, options?.page ?? 1); + const limit = Math.max(1, options?.limit ?? getShapePresets().length); + const startIndex = (page - 1) * limit; + const endIndex = startIndex + limit; + const pagedItems = shapes.slice(startIndex, endIndex); + return { + items: pagedItems, + hasMore: endIndex < shapes.length, + total: shapes.length, + }; +} + +export const shapesProvider: StickerProvider = { + id: SHAPES_PROVIDER_ID, + async search({ + query, + options, + }: { + query: string; + options?: { limit?: number }; + }): Promise<StickerSearchResult> { + const filteredShapes = filterShapesByQuery({ query }); + const paged = paginateShapes({ + shapes: filteredShapes, + options: { page: 1, limit: options?.limit ?? getShapePresets().length }, + }); + return { + items: paged.items.map((preset) => toStickerItem({ preset })), + total: paged.total, + hasMore: paged.hasMore, + }; + }, + async browse({ + options, + }: { + options?: { page?: number; limit?: number }; + }): Promise<StickerBrowseResult> { + const paged = paginateShapes({ + shapes: getShapePresets(), + options, + }); + return { + sections: [ + { + id: "all", + items: paged.items.map((preset) => toStickerItem({ preset })), + hasMore: paged.hasMore, + layout: "grid", + }, + ], + }; + }, + resolveUrl({ + stickerId, + }: { + stickerId: string; + options?: { width?: number; height?: number }; + }): string { + const preset = parseShapeStickerId({ stickerId }); + return buildShapeUrl({ shapeKey: preset?.shapeKey ?? "rectangle" }); + }, +}; |
