bsign-customization-full 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/.env +2 -0
  2. package/components.json +21 -0
  3. package/dist/colors/anthracite-gray.webp +0 -0
  4. package/dist/colors/anthracite-gray_50x50.png +0 -0
  5. package/dist/colors/dark-wenge.webp +0 -0
  6. package/dist/colors/dark-wenge_50x50.png +0 -0
  7. package/dist/colors/indian-rosewood.webp +0 -0
  8. package/dist/colors/indian-rosewood_50x50.png +0 -0
  9. package/dist/colors/natural-wood.webp +0 -0
  10. package/dist/colors/natural-wood_50x50.png +0 -0
  11. package/dist/colors/redwood.webp +0 -0
  12. package/dist/colors/redwood_50x50.png +0 -0
  13. package/dist/colors/walnut.webp +0 -0
  14. package/dist/colors/walnut_50x50.png +0 -0
  15. package/dist/html2canvas.esm-BJ_egzt0.js +4802 -0
  16. package/dist/index-Dw5Zc1iD.js +33162 -0
  17. package/dist/index.es-Co1KNpGS.js +6681 -0
  18. package/dist/logo.png +0 -0
  19. package/dist/purify.es-CKpD2xIC.js +552 -0
  20. package/dist/sign-constructor.es.js +4 -0
  21. package/dist/sign-constructor.iife.js +171 -0
  22. package/dist/size-guide.webp +0 -0
  23. package/dist/size.webp +0 -0
  24. package/dist/templates/assets/modern/rectangle-black.webp +0 -0
  25. package/dist/templates/assets/modern/rectangle-white.webp +0 -0
  26. package/dist/templates/assets/modern/square-black.webp +0 -0
  27. package/dist/templates/assets/modern/square-white.webp +0 -0
  28. package/dist/templates/assets/wave.webp +0 -0
  29. package/dist/templates/jure.webp +0 -0
  30. package/dist/templates/modern.webp +0 -0
  31. package/dist/templates/sherwood.webp +0 -0
  32. package/dist/templates/wave.webp +0 -0
  33. package/eslint.config.js +23 -0
  34. package/index.html +13 -0
  35. package/modern-debug.svg +39 -0
  36. package/package.json +62 -0
  37. package/public/colors/anthracite-gray.webp +0 -0
  38. package/public/colors/anthracite-gray_50x50.png +0 -0
  39. package/public/colors/dark-wenge.webp +0 -0
  40. package/public/colors/dark-wenge_50x50.png +0 -0
  41. package/public/colors/indian-rosewood.webp +0 -0
  42. package/public/colors/indian-rosewood_50x50.png +0 -0
  43. package/public/colors/natural-wood.webp +0 -0
  44. package/public/colors/natural-wood_50x50.png +0 -0
  45. package/public/colors/redwood.webp +0 -0
  46. package/public/colors/redwood_50x50.png +0 -0
  47. package/public/colors/walnut.webp +0 -0
  48. package/public/colors/walnut_50x50.png +0 -0
  49. package/public/logo.png +0 -0
  50. package/public/size-guide.webp +0 -0
  51. package/public/size.webp +0 -0
  52. package/public/templates/assets/modern/rectangle-black.webp +0 -0
  53. package/public/templates/assets/modern/rectangle-white.webp +0 -0
  54. package/public/templates/assets/modern/square-black.webp +0 -0
  55. package/public/templates/assets/modern/square-white.webp +0 -0
  56. package/public/templates/assets/wave.webp +0 -0
  57. package/public/templates/jure.webp +0 -0
  58. package/public/templates/modern.webp +0 -0
  59. package/public/templates/sherwood.webp +0 -0
  60. package/public/templates/wave.webp +0 -0
  61. package/src/App.css +43 -0
  62. package/src/AppDemo2.tsx +257 -0
  63. package/src/components/cart-panel.tsx +170 -0
  64. package/src/components/cart-preview.tsx +356 -0
  65. package/src/components/cart-view.tsx +113 -0
  66. package/src/components/constructure-menu.tsx +37 -0
  67. package/src/components/header.tsx +214 -0
  68. package/src/components/heading.tsx +28 -0
  69. package/src/components/icons.tsx +54 -0
  70. package/src/components/import-file-modal.tsx +252 -0
  71. package/src/components/layers/grid-view.tsx +29 -0
  72. package/src/components/layers/image-layer.tsx +128 -0
  73. package/src/components/layers/layer-forms/material-form.tsx +53 -0
  74. package/src/components/layers/layer-forms/size-form.tsx +69 -0
  75. package/src/components/layers/layer-forms/template-form.tsx +39 -0
  76. package/src/components/layers/layer-forms/text-form.tsx +477 -0
  77. package/src/components/layers/layers-container.tsx +259 -0
  78. package/src/components/layers/text-layer.tsx +128 -0
  79. package/src/components/movable-item.tsx +228 -0
  80. package/src/components/preview.tsx +258 -0
  81. package/src/components/resize-button.tsx +83 -0
  82. package/src/components/size-guide-modal.tsx +47 -0
  83. package/src/components/size-guide.tsx +98 -0
  84. package/src/components/ui/button-group.tsx +83 -0
  85. package/src/components/ui/button.tsx +60 -0
  86. package/src/components/ui/checkbox.tsx +30 -0
  87. package/src/components/ui/dialog.tsx +151 -0
  88. package/src/components/ui/input-group.tsx +168 -0
  89. package/src/components/ui/input.tsx +21 -0
  90. package/src/components/ui/label.tsx +22 -0
  91. package/src/components/ui/popover.tsx +54 -0
  92. package/src/components/ui/progress.tsx +28 -0
  93. package/src/components/ui/radio-group.tsx +43 -0
  94. package/src/components/ui/scroll-area.tsx +56 -0
  95. package/src/components/ui/select.tsx +191 -0
  96. package/src/components/ui/separator.tsx +25 -0
  97. package/src/components/ui/sheet.tsx +141 -0
  98. package/src/components/ui/slider.tsx +61 -0
  99. package/src/components/ui/spinner.tsx +15 -0
  100. package/src/components/ui/textarea.tsx +18 -0
  101. package/src/components/ui/toggle-group.tsx +73 -0
  102. package/src/components/ui/toggle.tsx +45 -0
  103. package/src/components/ui/tooltip.tsx +67 -0
  104. package/src/fonts/BEBASNEUE-REGULAR.TTF +0 -0
  105. package/src/fonts/Braille-Regular.ttf +0 -0
  106. package/src/fonts/GOTHICB.TTF +0 -0
  107. package/src/hooks/use-mobile.ts +23 -0
  108. package/src/hooks/use-resize-constraints.ts +62 -0
  109. package/src/index.css +238 -0
  110. package/src/index.tsx +141 -0
  111. package/src/lib/cart-proposal-pdf.ts +350 -0
  112. package/src/lib/config-font.tsx +109 -0
  113. package/src/lib/config.ts +730 -0
  114. package/src/lib/pricing.ts +61 -0
  115. package/src/lib/type-checks.ts +47 -0
  116. package/src/lib/utils.ts +146 -0
  117. package/src/lib/widget-context.tsx +9 -0
  118. package/src/main.tsx +11 -0
  119. package/src/store/cart-store.ts +78 -0
  120. package/src/store/layers-store.ts +337 -0
  121. package/src/vite-env.d.ts +1 -0
  122. package/test/preview.html +37 -0
  123. package/tsconfig.app.json +33 -0
  124. package/tsconfig.json +13 -0
  125. package/tsconfig.node.json +25 -0
  126. package/vite.config.ts +38 -0
@@ -0,0 +1,730 @@
1
+ import type { LayrProps, TLayer } from "../components/preview";
2
+ import { BASE_FONT_SIZE } from "./config-font";
3
+
4
+ export const prodApiUrl = "https://constructor-api-production.up.railway.app/v1/product/generate";
5
+
6
+ export type TSize = {
7
+ id: string;
8
+ label: string;
9
+ brailleScale: number;
10
+ baseTextScale: number;
11
+ price: number;
12
+ cm: { w: number; h: number };
13
+ inchs: { w: number; h: number };
14
+ };
15
+
16
+ export type TemplateMaterial = {
17
+ id: string;
18
+ label: string;
19
+ textColor: string;
20
+ previewImage?: string;
21
+ previewColor?: string;
22
+ backgroundByShape: Record<string, string>;
23
+ getBackgroundSrc?: (params: {
24
+ shapeId: string;
25
+ size: TSize;
26
+ }) => string;
27
+ };
28
+
29
+ export type TemplateShape = {
30
+ id: string;
31
+ label: string;
32
+ description?: string;
33
+ previewImage?: string;
34
+ sizes: TSize[];
35
+ };
36
+
37
+ export type TemplateTextFont = "font-bebasneue" | "font-gothicb";
38
+
39
+ export type TemplateDefinition = {
40
+ id: string;
41
+ name: string;
42
+ image: string;
43
+ description?: string;
44
+ textFont: TemplateTextFont;
45
+ defaultShapeId: string;
46
+ defaultMaterialId: string;
47
+ defaultSizeId: string;
48
+ shapes: TemplateShape[];
49
+ materials: TemplateMaterial[];
50
+ createLayers: (context: {
51
+ size: TSize;
52
+ shape: TemplateShape;
53
+ material: TemplateMaterial;
54
+ }) => TLayer<LayrProps>[];
55
+ };
56
+
57
+ const CSS_INCH_IN_PX = 96;
58
+ const MM_IN_CM = 10;
59
+
60
+ export const MAX_SIGN_WIDTH_MM = 300;
61
+ export const MAX_SIGN_HEIGHT_MM = 190;
62
+
63
+ export const getSizeLimitScale = (size: Pick<TSize, "cm">): number => {
64
+ const widthMm = size.cm.w * MM_IN_CM;
65
+ const heightMm = size.cm.h * MM_IN_CM;
66
+ const widthScale =
67
+ Number.isFinite(widthMm) && widthMm > 0 && Number.isFinite(MAX_SIGN_WIDTH_MM) && MAX_SIGN_WIDTH_MM > 0
68
+ ? MAX_SIGN_WIDTH_MM / widthMm
69
+ : 1;
70
+ const heightScale =
71
+ Number.isFinite(heightMm) &&
72
+ heightMm > 0 &&
73
+ Number.isFinite(MAX_SIGN_HEIGHT_MM) &&
74
+ MAX_SIGN_HEIGHT_MM > 0
75
+ ? MAX_SIGN_HEIGHT_MM / heightMm
76
+ : 1;
77
+ const sizeLimitScale = Math.min(widthScale, heightScale);
78
+
79
+ if (!Number.isFinite(sizeLimitScale) || sizeLimitScale <= 0) {
80
+ return 1;
81
+ }
82
+
83
+ return Math.min(1, sizeLimitScale);
84
+ };
85
+
86
+ const MODERN_BACKGROUND_ASSETS = {
87
+ rectangle: {
88
+ black: "/templates/assets/modern/rectangle-black.webp",
89
+ white: "/templates/assets/modern/rectangle-white.webp",
90
+ },
91
+ square: {
92
+ black: "/templates/assets/modern/square-black.webp",
93
+ white: "/templates/assets/modern/square-white.webp",
94
+ },
95
+ } as const;
96
+
97
+ type ModernShapeVariant = keyof typeof MODERN_BACKGROUND_ASSETS;
98
+ type ModernMaterialVariant = keyof (typeof MODERN_BACKGROUND_ASSETS)["rectangle"];
99
+
100
+ const getModernBackground = ({
101
+ shape,
102
+ material,
103
+ }: {
104
+ shape: ModernShapeVariant;
105
+ material: ModernMaterialVariant;
106
+ }): string => MODERN_BACKGROUND_ASSETS[shape][material];
107
+
108
+ const SQUARE_SIZES: TSize[] = [
109
+ {
110
+ id: "3.5x3.5",
111
+ label: '3.5 x 3.5" / 90 x 90 mm',
112
+ brailleScale: 1,
113
+ baseTextScale: 1,
114
+ price: 25,
115
+ cm: { w: 9, h: 9 },
116
+ inchs: { w: 3.5, h: 3.5 },
117
+ },
118
+ {
119
+ id: "5x5",
120
+ label: '5 x 5" / 127 x 127 mm',
121
+ brailleScale: 0.8,
122
+ baseTextScale: 0.9,
123
+ price: 31,
124
+ cm: { w: 12.7, h: 12.7 },
125
+ inchs: { w: 5, h: 5 },
126
+ },
127
+ {
128
+ id: "6.3x6.3",
129
+ label: '6.3 x 6.3" / 160 x 160 mm',
130
+ brailleScale: 0.7,
131
+ baseTextScale: 0.8,
132
+ price: 39,
133
+ cm: { w: 16, h: 16 },
134
+ inchs: { w: 6.3, h: 6.3 },
135
+ },
136
+ {
137
+ id: "7.9x7.9",
138
+ label: '7.9 x 7.9" / 200 x 200 mm',
139
+ brailleScale: 0.6,
140
+ baseTextScale: 0.7,
141
+ price: 40,
142
+ cm: { w: 20, h: 20 },
143
+ inchs: { w: 7.9, h: 7.9 },
144
+ },
145
+ ];
146
+
147
+ const MODERN_SQUARE_SIZES: TSize[] = [
148
+ {
149
+ id: "3.5x3.5",
150
+ label: '3.5 x 3.5" / 90 x 90 mm',
151
+ brailleScale: 1,
152
+ baseTextScale: 1,
153
+ price: 25,
154
+ cm: { w: 9, h: 9 },
155
+ inchs: { w: 3.5, h: 3.5 },
156
+ },
157
+ {
158
+ id: "4.7x4.7",
159
+ label: '4.7 x 4.7" / 120 x 120 mm',
160
+ brailleScale: 0.8,
161
+ baseTextScale: 0.9,
162
+ price: 31,
163
+ cm: { w: 12, h: 12 },
164
+ inchs: { w: 4.7, h: 4.7 },
165
+ },
166
+ {
167
+ id: "6.3x6.3",
168
+ label: '6.3 x 6.3" / 160 x 160 mm',
169
+ brailleScale: 0.7,
170
+ baseTextScale: 0.8,
171
+ price: 39,
172
+ cm: { w: 16, h: 16 },
173
+ inchs: { w: 6.3, h: 6.3 },
174
+ },
175
+ {
176
+ id: "7.9x7.9",
177
+ label: '7.9 x 7.9" / 200 x 200 mm',
178
+ brailleScale: 0.6,
179
+ baseTextScale: 0.7,
180
+ price: 40,
181
+ cm: { w: 20, h: 20 },
182
+ inchs: { w: 7.9, h: 7.9 },
183
+ },
184
+ ];
185
+
186
+ const MODERN_RECTANGLE_SIZES: TSize[] = [
187
+ {
188
+ id: "14.5x7.8",
189
+ label: '14.5 x 7.8" / 370 x 200 mm',
190
+ brailleScale: 0.6,
191
+ baseTextScale: 0.8,
192
+ price: 40,
193
+ cm: { w: 37, h: 20 },
194
+ inchs: { w: 14.5, h: 7.8 },
195
+ },
196
+ ];
197
+
198
+ const WAVE_MATERIALS = [
199
+ {
200
+ id: "anthracite-gray",
201
+ label: "Anthracite Gray",
202
+ previewImage: "/colors/anthracite-gray_50x50.png",
203
+ image: "/colors/anthracite-gray.webp",
204
+ },
205
+ {
206
+ id: "indian-rosewood",
207
+ label: "Indian Rosewood",
208
+ previewImage: "/colors/indian-rosewood_50x50.png",
209
+ image: "/colors/indian-rosewood.webp",
210
+ },
211
+ {
212
+ id: "natural-wood",
213
+ label: "Natural Wood",
214
+ previewImage: "/colors/natural-wood_50x50.png",
215
+ image: "/colors/natural-wood.webp",
216
+ },
217
+ {
218
+ id: "walnut",
219
+ label: "Walnut",
220
+ previewImage: "/colors/walnut_50x50.png",
221
+ image: "/colors/walnut.webp",
222
+ },
223
+ {
224
+ id: "redwood",
225
+ label: "Redwood",
226
+ previewImage: "/colors/redwood_50x50.png",
227
+ image: "/colors/redwood.webp",
228
+ },
229
+ {
230
+ id: "dark-wenge",
231
+ label: "Dark Wenge",
232
+ previewImage: "/colors/dark-wenge_50x50.png",
233
+ image: "/colors/dark-wenge.webp",
234
+ },
235
+ ] as const;
236
+
237
+ export const colors: Array<{
238
+ id: string;
239
+ label: string;
240
+ smallImage: string;
241
+ image?: string;
242
+ }> = WAVE_MATERIALS.map((material) => ({
243
+ id: material.id,
244
+ label: material.label,
245
+ smallImage: material.previewImage,
246
+ image: material.image,
247
+ }));
248
+
249
+ const getSignCenter = (size: TSize) => ({
250
+ x: Math.round((size.inchs.w * CSS_INCH_IN_PX) / 2),
251
+ y: Math.round((size.inchs.h * CSS_INCH_IN_PX) / 2),
252
+ });
253
+
254
+ const getMaterialBackground = ({
255
+ material,
256
+ shapeId,
257
+ size,
258
+ }: {
259
+ material: TemplateMaterial;
260
+ shapeId: string;
261
+ size: TSize;
262
+ }): string => {
263
+ if (material.getBackgroundSrc) {
264
+ return material.getBackgroundSrc({ shapeId, size });
265
+ }
266
+
267
+ return material.backgroundByShape[shapeId];
268
+ };
269
+
270
+ const createTextLayers = ({
271
+ size,
272
+ textColor,
273
+ text = "Kitchen",
274
+ }: {
275
+ size: TSize;
276
+ textColor: string;
277
+ text?: string;
278
+ }): TLayer<LayrProps>[] => {
279
+ const center = getSignCenter(size);
280
+ const textY = Math.round(center.y - 38);
281
+ const brailleY = textY + 78;
282
+
283
+ return [
284
+ {
285
+ type: "text",
286
+ props: {
287
+ text,
288
+ fontSize: BASE_FONT_SIZE,
289
+ movable: false,
290
+ scale: size.baseTextScale,
291
+ coordinates: { x: center.x, y: textY },
292
+ braille: true,
293
+ color: textColor,
294
+ },
295
+ },
296
+ {
297
+ type: "text",
298
+ subtype: "braille",
299
+ props: {
300
+ text,
301
+ fontSize: 12,
302
+ movable: false,
303
+ scale: size.brailleScale,
304
+ coordinates: { x: center.x, y: brailleY },
305
+ color: textColor,
306
+ },
307
+ },
308
+ ];
309
+ };
310
+
311
+ const modernTemplate: TemplateDefinition = {
312
+ id: "modern",
313
+ name: "Modern",
314
+ image: getModernBackground({
315
+ shape: "square",
316
+ material: "black",
317
+ }),
318
+ textFont: "font-gothicb",
319
+ defaultShapeId: "square",
320
+ defaultMaterialId: "black",
321
+ defaultSizeId: "6.3x6.3",
322
+ shapes: [
323
+ {
324
+ id: "square",
325
+ label: "Square",
326
+ previewImage: getModernBackground({
327
+ shape: "square",
328
+ material: "black",
329
+ }),
330
+ sizes: MODERN_SQUARE_SIZES,
331
+ },
332
+ {
333
+ id: "rectangle",
334
+ label: "Rectangle",
335
+ previewImage: getModernBackground({
336
+ shape: "rectangle",
337
+ material: "black",
338
+ }),
339
+ sizes: MODERN_RECTANGLE_SIZES,
340
+ },
341
+ ],
342
+ materials: [
343
+ {
344
+ id: "black",
345
+ label: "Black",
346
+ textColor: "#F5F7FA",
347
+ previewImage: getModernBackground({
348
+ shape: "square",
349
+ material: "black",
350
+ }),
351
+ previewColor: "#111111",
352
+ backgroundByShape: {
353
+ rectangle: getModernBackground({
354
+ shape: "rectangle",
355
+ material: "black",
356
+ }),
357
+ square: getModernBackground({
358
+ shape: "square",
359
+ material: "black",
360
+ }),
361
+ },
362
+ },
363
+ {
364
+ id: "white",
365
+ label: "White",
366
+ textColor: "#111111",
367
+ previewImage: getModernBackground({
368
+ shape: "square",
369
+ material: "white",
370
+ }),
371
+ previewColor: "#F4F6F8",
372
+ backgroundByShape: {
373
+ rectangle: getModernBackground({
374
+ shape: "rectangle",
375
+ material: "white",
376
+ }),
377
+ square: getModernBackground({
378
+ shape: "square",
379
+ material: "white",
380
+ }),
381
+ },
382
+ },
383
+ ],
384
+ createLayers: ({ size, shape, material }) => [
385
+ {
386
+ type: "background",
387
+ props: {
388
+ imageSrc: getMaterialBackground({
389
+ material,
390
+ shapeId: shape.id,
391
+ size,
392
+ }),
393
+ coordinates: { x: 0, y: 0 },
394
+ },
395
+ },
396
+ ...createTextLayers({ size, textColor: material.textColor }),
397
+ ],
398
+ };
399
+
400
+ const waveTemplate: TemplateDefinition = {
401
+ id: "wave",
402
+ name: "Wave",
403
+ image: "/templates/wave.webp",
404
+ textFont: "font-bebasneue",
405
+ defaultShapeId: "square",
406
+ defaultMaterialId: "anthracite-gray",
407
+ defaultSizeId: "3.5x3.5",
408
+ shapes: [
409
+ {
410
+ id: "square",
411
+ label: "Square",
412
+ sizes: SQUARE_SIZES,
413
+ previewImage: "/templates/wave.webp",
414
+ },
415
+ ],
416
+ materials: WAVE_MATERIALS.map((material) => ({
417
+ id: material.id,
418
+ label: material.label,
419
+ textColor: "#111111",
420
+ previewImage: material.previewImage,
421
+ backgroundByShape: {
422
+ square: material.image,
423
+ },
424
+ })),
425
+ createLayers: ({ material, shape, size }) => {
426
+ const center = getSignCenter(size);
427
+
428
+ return [
429
+ {
430
+ type: "background",
431
+ props: {
432
+ imageSrc: getMaterialBackground({
433
+ material,
434
+ shapeId: shape.id,
435
+ size,
436
+ }),
437
+ coordinates: { x: 0, y: 0 },
438
+ },
439
+ },
440
+ {
441
+ type: "image",
442
+ props: {
443
+ imageSrc: "/templates/assets/wave.webp",
444
+ coordinates: {
445
+ "3.5x3.5": { x: -35, y: 90 },
446
+ "5x5": { x: -35, y: 100 },
447
+ "6.3x6.3": { x: -35, y: 120 },
448
+ "7.9x7.9": { x: -35, y: 140 },
449
+ },
450
+ },
451
+ },
452
+ {
453
+ type: "text",
454
+ props: {
455
+ text: "Kitchen",
456
+ fontSize: BASE_FONT_SIZE,
457
+ movable: false,
458
+ scale: size.baseTextScale,
459
+ coordinates: { x: center.x, y: center.y - 10 },
460
+ braille: true,
461
+ color: material.textColor,
462
+ },
463
+ },
464
+ {
465
+ type: "text",
466
+ subtype: "braille",
467
+ props: {
468
+ text: "Kitchen",
469
+ fontSize: 12,
470
+ coordinates: { x: center.x, y: center.y + 70 },
471
+ movable: false,
472
+ scale: size.brailleScale,
473
+ color: material.textColor,
474
+ },
475
+ },
476
+ ];
477
+ },
478
+ };
479
+
480
+ export const templateCatalog: TemplateDefinition[] = [modernTemplate, waveTemplate];
481
+ export const DEFAULT_TEMPLATE_ID = modernTemplate.id;
482
+
483
+ const getFallbackTemplate = () => templateCatalog[0];
484
+
485
+ export const getTemplateById = (templateId?: string): TemplateDefinition =>
486
+ templateCatalog.find((template) => template.id === templateId) ?? getFallbackTemplate();
487
+
488
+ export const getTemplateTextFont = (templateId?: string): TemplateTextFont =>
489
+ getTemplateById(templateId).textFont;
490
+
491
+ export const getTemplateShape = ({
492
+ template,
493
+ shapeId,
494
+ }: {
495
+ template: TemplateDefinition;
496
+ shapeId?: string;
497
+ }): TemplateShape =>
498
+ template.shapes.find((shape) => shape.id === shapeId) ??
499
+ template.shapes.find((shape) => shape.id === template.defaultShapeId) ??
500
+ template.shapes[0];
501
+
502
+ const getTemplateShapeBySizeId = ({
503
+ template,
504
+ sizeId,
505
+ }: {
506
+ template: TemplateDefinition;
507
+ sizeId: string;
508
+ }): TemplateShape | undefined =>
509
+ template.shapes.find((shape) => shape.sizes.some((size) => size.id === sizeId));
510
+
511
+ export const getTemplateMaterial = ({
512
+ template,
513
+ materialId,
514
+ }: {
515
+ template: TemplateDefinition;
516
+ materialId?: string;
517
+ }): TemplateMaterial =>
518
+ template.materials.find((material) => material.id === materialId) ??
519
+ template.materials.find((material) => material.id === template.defaultMaterialId) ??
520
+ template.materials[0];
521
+
522
+ export const getTemplateSize = ({
523
+ shape,
524
+ preferredSizeId,
525
+ template,
526
+ }: {
527
+ template: TemplateDefinition;
528
+ shape: TemplateShape;
529
+ preferredSizeId?: string;
530
+ }): TSize =>
531
+ shape.sizes.find((size) => size.id === preferredSizeId) ??
532
+ shape.sizes.find((size) => size.id === template.defaultSizeId) ??
533
+ shape.sizes[0];
534
+
535
+ export const getTemplateSizes = ({
536
+ templateId,
537
+ shapeId,
538
+ }: {
539
+ templateId?: string;
540
+ shapeId?: string;
541
+ }): TSize[] => {
542
+ const template = getTemplateById(templateId);
543
+
544
+ if (shapeId) {
545
+ const shape = getTemplateShape({ template, shapeId });
546
+ return shape.sizes;
547
+ }
548
+
549
+ const sizes: TSize[] = [];
550
+ const seenSizeIds = new Set<string>();
551
+
552
+ template.shapes.forEach((shape) => {
553
+ shape.sizes.forEach((size) => {
554
+ if (!seenSizeIds.has(size.id)) {
555
+ seenSizeIds.add(size.id);
556
+ sizes.push(size);
557
+ }
558
+ });
559
+ });
560
+
561
+ return sizes;
562
+ };
563
+
564
+ export const getTemplateShapePreview = ({
565
+ templateId,
566
+ shapeId,
567
+ materialId,
568
+ preferredSizeId,
569
+ }: {
570
+ templateId?: string;
571
+ shapeId?: string;
572
+ materialId?: string;
573
+ preferredSizeId?: string;
574
+ }): string | undefined => {
575
+ const { shape, material, size } = resolveTemplateSelection({
576
+ templateId,
577
+ shapeId,
578
+ materialId,
579
+ preferredSizeId,
580
+ });
581
+
582
+ return getMaterialBackground({
583
+ material,
584
+ shapeId: shape.id,
585
+ size,
586
+ }) ?? shape.previewImage;
587
+ };
588
+
589
+ export const resolveTemplateSelection = ({
590
+ templateId,
591
+ shapeId,
592
+ materialId,
593
+ preferredSizeId,
594
+ }: {
595
+ templateId?: string;
596
+ shapeId?: string;
597
+ materialId?: string;
598
+ preferredSizeId?: string;
599
+ }) => {
600
+ const template = getTemplateById(templateId);
601
+ const shapeByPreferredSize = preferredSizeId
602
+ ? getTemplateShapeBySizeId({ template, sizeId: preferredSizeId })
603
+ : undefined;
604
+ const shape = getTemplateShape({ template, shapeId: shapeByPreferredSize?.id ?? shapeId });
605
+ const material = getTemplateMaterial({ template, materialId });
606
+ const size = getTemplateSize({ template, shape, preferredSizeId });
607
+
608
+ return {
609
+ template,
610
+ shape,
611
+ material,
612
+ size,
613
+ };
614
+ };
615
+
616
+ export const createTemplateLayers = ({
617
+ templateId,
618
+ shapeId,
619
+ materialId,
620
+ preferredSizeId,
621
+ }: {
622
+ templateId?: string;
623
+ shapeId?: string;
624
+ materialId?: string;
625
+ preferredSizeId?: string;
626
+ }): TLayer<LayrProps>[] => {
627
+ const { template, shape, material, size } = resolveTemplateSelection({
628
+ templateId,
629
+ shapeId,
630
+ materialId,
631
+ preferredSizeId,
632
+ });
633
+
634
+ return template.createLayers({ size, shape, material });
635
+ };
636
+
637
+ export const getDefaultTemplateOptions = ({
638
+ templateId = DEFAULT_TEMPLATE_ID,
639
+ }: {
640
+ templateId?: string;
641
+ } = {}) => {
642
+ const { template, shape, material, size } = resolveTemplateSelection({
643
+ templateId,
644
+ });
645
+
646
+ return {
647
+ selectedTemplateId: template.id,
648
+ selectedShapeId: shape.id,
649
+ selectedMaterialId: material.id,
650
+ selectedSize: size,
651
+ };
652
+ };
653
+
654
+ export const applyTemplateVisualsToLayers = ({
655
+ layers,
656
+ templateId,
657
+ shapeId,
658
+ materialId,
659
+ preferredSizeId,
660
+ }: {
661
+ layers: TLayer<LayrProps>[];
662
+ templateId?: string;
663
+ shapeId?: string;
664
+ materialId?: string;
665
+ preferredSizeId?: string;
666
+ }): TLayer<LayrProps>[] => {
667
+ const { material, shape, size } = resolveTemplateSelection({
668
+ templateId,
669
+ shapeId,
670
+ materialId,
671
+ preferredSizeId,
672
+ });
673
+
674
+ const backgroundImage = getMaterialBackground({
675
+ material,
676
+ shapeId: shape.id,
677
+ size,
678
+ });
679
+ const textColor = material.textColor;
680
+
681
+ return layers.map((layer) => {
682
+ if (layer.type === "background") {
683
+ return {
684
+ ...layer,
685
+ props: {
686
+ ...layer.props,
687
+ imageSrc: backgroundImage,
688
+ },
689
+ };
690
+ }
691
+
692
+ if (layer.type === "text") {
693
+ return {
694
+ ...layer,
695
+ props: {
696
+ ...layer.props,
697
+ color: textColor,
698
+ },
699
+ };
700
+ }
701
+
702
+ return layer;
703
+ });
704
+ };
705
+
706
+ export const templates: Array<{
707
+ id: string;
708
+ name: string;
709
+ image: string;
710
+ layers: TLayer<LayrProps>[];
711
+ }> = templateCatalog.map((template) => {
712
+ const defaults = getDefaultTemplateOptions({ templateId: template.id });
713
+ return {
714
+ id: template.id,
715
+ name: template.name,
716
+ image: template.image,
717
+ layers: createTemplateLayers({
718
+ templateId: defaults.selectedTemplateId,
719
+ shapeId: defaults.selectedShapeId,
720
+ materialId: defaults.selectedMaterialId,
721
+ preferredSizeId: defaults.selectedSize.id,
722
+ }),
723
+ };
724
+ });
725
+
726
+ export const sizes: TSize[] = SQUARE_SIZES;
727
+
728
+ export const constructureSidebar = ["Designer", "Quantity"];
729
+
730
+ export const icons = ["https://cdn-icons-png.flaticon.com/512/25/25694.png"];