summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/stickers/providers/shapes.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-25 16:47:22 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-29 15:48:22 +0200
commit8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch)
treee9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/lib/stickers/providers/shapes.ts
parent39ea298a9ccba3104c549c0ca079c231981811f5 (diff)
downloadopencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/lib/stickers/providers/shapes.ts')
-rw-r--r--apps/web/src/lib/stickers/providers/shapes.ts160
1 files changed, 124 insertions, 36 deletions
diff --git a/apps/web/src/lib/stickers/providers/shapes.ts b/apps/web/src/lib/stickers/providers/shapes.ts
index 81873dcb..a23f14a7 100644
--- a/apps/web/src/lib/stickers/providers/shapes.ts
+++ b/apps/web/src/lib/stickers/providers/shapes.ts
@@ -1,5 +1,8 @@
+import { buildGraphicPreviewUrl, buildDefaultGraphicInstance, graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics";
+import type { ParamValues } from "@/lib/params";
import { buildStickerId, parseStickerId } from "../sticker-id";
import type {
+ StickerBrowseResult,
StickerItem,
StickerProvider,
StickerSearchResult,
@@ -7,60 +10,140 @@ import type {
const SHAPES_PROVIDER_ID = "shapes";
-const SHAPES = [
- { key: "circle", name: "Circle" },
- { key: "square", name: "Square" },
- { key: "triangle", name: "Triangle" },
- { key: "star", name: "Star" },
- { key: "hexagon", name: "Hexagon" },
- { key: "diamond", name: "Diamond" },
-] as const;
+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 {
- return `/shapes/${shapeKey}.svg`;
+ const preset = getShapePreset({ shapeKey });
+ if (!preset) {
+ return buildGraphicPreviewUrl({ definitionId: "rectangle" });
+ }
+ return buildGraphicPreviewUrl({
+ definitionId: preset.definitionId,
+ params: getShapeParams({ preset }),
+ });
}
function toStickerItem({
- shape,
+ preset,
}: {
- shape: (typeof SHAPES)[number];
+ preset: ShapeGraphicPreset;
}): StickerItem {
return {
id: buildStickerId({
providerId: SHAPES_PROVIDER_ID,
- providerValue: shape.key,
+ providerValue: preset.shapeKey,
}),
provider: SHAPES_PROVIDER_ID,
- name: shape.name,
- previewUrl: buildShapeUrl({ shapeKey: shape.key }),
- metadata: { shape: shape.key },
+ name: preset.name,
+ previewUrl: buildShapeUrl({ shapeKey: preset.shapeKey }),
+ metadata: {
+ definitionId: preset.definitionId,
+ params: preset.params ?? {},
+ },
};
}
-function filterShapesByQuery({
- query,
-}: {
- query: string;
-}): Array<(typeof SHAPES)[number]> {
+function filterShapesByQuery({ query }: { query: string }): ShapeGraphicPreset[] {
const normalizedQuery = query.trim().toLowerCase();
+ const presets = getShapePresets();
if (!normalizedQuery) {
- return [...SHAPES];
+ return presets;
}
- return SHAPES.filter((shape) =>
- shape.name.toLowerCase().includes(normalizedQuery),
- );
+ 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: Array<(typeof SHAPES)[number]>;
+ shapes: ShapeGraphicPreset[];
options?: { page?: number; limit?: number };
-}): { items: Array<(typeof SHAPES)[number]>; hasMore: boolean; total: number } {
+}): { items: ShapeGraphicPreset[]; hasMore: boolean; total: number } {
const page = Math.max(1, options?.page ?? 1);
- const limit = Math.max(1, options?.limit ?? SHAPES.length);
+ const limit = Math.max(1, options?.limit ?? getShapePresets().length);
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const pagedItems = shapes.slice(startIndex, endIndex);
@@ -83,10 +166,10 @@ export const shapesProvider: StickerProvider = {
const filteredShapes = filterShapesByQuery({ query });
const paged = paginateShapes({
shapes: filteredShapes,
- options: { page: 1, limit: options?.limit ?? SHAPES.length },
+ options: { page: 1, limit: options?.limit ?? getShapePresets().length },
});
return {
- items: paged.items.map((shape) => toStickerItem({ shape })),
+ items: paged.items.map((preset) => toStickerItem({ preset })),
total: paged.total,
hasMore: paged.hasMore,
};
@@ -95,15 +178,20 @@ export const shapesProvider: StickerProvider = {
options,
}: {
options?: { page?: number; limit?: number };
- }): Promise<StickerSearchResult> {
+ }): Promise<StickerBrowseResult> {
const paged = paginateShapes({
- shapes: [...SHAPES],
+ shapes: getShapePresets(),
options,
});
return {
- items: paged.items.map((shape) => toStickerItem({ shape })),
- total: paged.total,
- hasMore: paged.hasMore,
+ sections: [
+ {
+ id: "all",
+ items: paged.items.map((preset) => toStickerItem({ preset })),
+ hasMore: paged.hasMore,
+ layout: "grid",
+ },
+ ],
};
},
resolveUrl({
@@ -112,7 +200,7 @@ export const shapesProvider: StickerProvider = {
stickerId: string;
options?: { width?: number; height?: number };
}): string {
- const { providerValue } = parseStickerId({ stickerId });
- return buildShapeUrl({ shapeKey: providerValue });
+ const preset = parseShapeStickerId({ stickerId });
+ return buildShapeUrl({ shapeKey: preset?.shapeKey ?? "rectangle" });
},
};