summaryrefslogtreecommitdiff
path: root/apps/web/src/stickers/providers/shapes.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/stickers/providers/shapes.ts')
-rw-r--r--apps/web/src/stickers/providers/shapes.ts206
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" });
+ },
+};