@snowcone-app/canvas 0.1.4 → 0.1.5
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.
- package/README.md +101 -326
- package/dist/api/stable.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -89
- package/dist/index.mjs.map +1 -1
- package/dist/rendering/serialize-for-server.d.ts +13 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,356 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @snowcone-app/canvas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Embeddable design editor for print-on-demand and product customization.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`<SnowconeCanvas />` is a self-contained React component that lets your shoppers
|
|
6
|
+
compose the artwork that goes onto a product — text, images, transforms, and
|
|
7
|
+
effects across one or more print areas. It's the same editor that powers Edit and
|
|
8
|
+
Remix on [snowcone.app](https://snowcone.app). Drop it in, get the design state and
|
|
9
|
+
print-ready exports back out.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
- ✅ **Multiple Transform Types** - 8+ text effects (straight, circle, arch, wave, etc.)
|
|
9
|
-
- ✅ **Advanced Layer Effects** - Strokes, masks, distress, knockout compositing
|
|
10
|
-
- ✅ **Multi-Artboard Workflow** - Manage multiple designs in one session
|
|
11
|
-
- ✅ **Professional Export** - Transparent PNG export with Web Worker performance
|
|
12
|
-
- ✅ **Maintainable** - Clear separation of concerns, modular architecture
|
|
13
|
-
- ✅ **Extensible** - Adding new transforms and effects is straightforward
|
|
11
|
+
📖 **Full documentation: [developers.snowcone.app/canvas](https://developers.snowcone.app/canvas)**
|
|
14
12
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
This repo uses a **hybrid worktree workflow** for parallel feature development:
|
|
18
|
-
|
|
19
|
-
### Hybrid Approach
|
|
20
|
-
- **Generic directory names**: `worktree-1`, `worktree-2`, `worktree-3`, `worktree-4`
|
|
21
|
-
- **Descriptive git branches**: `feature/shadows`, `bug/export-crash`, etc.
|
|
22
|
-
- **Auto-sync on commit**: Every commit automatically rebases on main to prevent drift
|
|
23
|
-
- **Fast context switching**: Reuse the same worktree for multiple features
|
|
24
|
-
|
|
25
|
-
### Quick Start
|
|
13
|
+
## Installation
|
|
26
14
|
|
|
27
15
|
```bash
|
|
28
|
-
|
|
29
|
-
/
|
|
30
|
-
|
|
31
|
-
# 2. Navigate to any worktree and start a feature
|
|
32
|
-
cd ../snowcone-canvas-worktree-1
|
|
33
|
-
/feature-start shadows
|
|
34
|
-
|
|
35
|
-
# 3. Work and commit (auto-syncs with main after every commit!)
|
|
36
|
-
/commit Add shadow rendering to text elements
|
|
37
|
-
|
|
38
|
-
# 4. Merge to main when stable
|
|
39
|
-
/feature-merge
|
|
40
|
-
|
|
41
|
-
# 5. Start next feature (same worktree, new branch)
|
|
42
|
-
/feature-start export-formats
|
|
16
|
+
npm install @snowcone-app/canvas
|
|
17
|
+
# or: pnpm add @snowcone-app/canvas / yarn add @snowcone-app/canvas
|
|
43
18
|
```
|
|
44
19
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| `/worktree-cleanup` | Clean up all worktrees for merged branches |
|
|
66
|
-
|
|
67
|
-
### Key Benefits
|
|
68
|
-
|
|
69
|
-
- ✅ **Merge early, merge often** - Auto-sync on every commit prevents conflicts
|
|
70
|
-
- ✅ **Parallel development** - 4 worktrees = 4 features in progress
|
|
71
|
-
- ✅ **Fast switching** - Reuse worktrees instead of creating new ones
|
|
72
|
-
- ✅ **Clean git history** - Descriptive branch names, generic directories
|
|
73
|
-
- ✅ **Conflict prevention** - Handle conflicts immediately, not at merge time
|
|
74
|
-
|
|
75
|
-
## Architecture
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
custom-canvas/
|
|
79
|
-
├── src/
|
|
80
|
-
│ ├── core/
|
|
81
|
-
│ │ ├── TextShape.js - Base class for all text shapes
|
|
82
|
-
│ │ ├── TransformHandles.js - Resize & rotation handle system
|
|
83
|
-
│ │ └── GeometryUtils.js - Transform math (rotation matrices, etc.)
|
|
84
|
-
│ │
|
|
85
|
-
│ ├── transforms/
|
|
86
|
-
│ │ ├── CustomTransform.js - ✅ Straight text
|
|
87
|
-
│ │ ├── CircleTransform.js - ✅ Circular text path
|
|
88
|
-
│ │ ├── ArchTransform.js - ✅ Curved arch text
|
|
89
|
-
│ │ ├── WaveTransform.js - 🚧 TODO
|
|
90
|
-
│ │ ├── RiseTransform.js - 🚧 TODO
|
|
91
|
-
│ │ ├── FlagTransform.js - 🚧 TODO
|
|
92
|
-
│ │ ├── AngleTransform.js - 🚧 TODO
|
|
93
|
-
│ │ └── DistortTransform.js - 🚧 TODO
|
|
94
|
-
│ │
|
|
95
|
-
│ ├── components/
|
|
96
|
-
│ │ └── CanvasEditor.jsx - Main canvas component
|
|
97
|
-
│ │
|
|
98
|
-
│ ├── App.jsx - Demo app with transform picker
|
|
99
|
-
│ └── main.jsx - Entry point
|
|
100
|
-
│
|
|
101
|
-
└── DESIGN.md - Detailed architecture documentation
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Core Design Principles
|
|
105
|
-
|
|
106
|
-
### 1. Fixed-Corner Resize
|
|
107
|
-
|
|
108
|
-
When dragging a corner handle, the **diagonally opposite corner stays fixed** in world space:
|
|
109
|
-
|
|
110
|
-
- Drag top-left → bottom-right stays put
|
|
111
|
-
- Drag top-right → bottom-left stays put
|
|
112
|
-
- Works correctly even when rotated
|
|
113
|
-
|
|
114
|
-
**Implementation:** `GeometryUtils.js:calculateFixedCornerPosition()`
|
|
115
|
-
|
|
116
|
-
- Uses rotation matrix math to keep opposite corner stationary
|
|
117
|
-
- Transform pipeline: local coords → rotate → translate to world coords
|
|
118
|
-
|
|
119
|
-
### 2. Rotation Handle Position
|
|
120
|
-
|
|
121
|
-
- **Distance**: 50px below the bottom edge (world coordinates)
|
|
122
|
-
- **Alignment**: Centered horizontally on bounding box
|
|
123
|
-
- **Visual consistency**: Distance compensated for scale
|
|
124
|
-
|
|
125
|
-
**Implementation:** `GeometryUtils.js:calculateRotationHandlePosition()`
|
|
126
|
-
|
|
127
|
-
### 3. Uniform Scaling
|
|
128
|
-
|
|
129
|
-
- Corner handles apply uniform scale (no skewing)
|
|
130
|
-
- Side handles (only on straight text) change width only
|
|
131
|
-
- Scale immediately baked into dimensions (no accumulated transforms)
|
|
132
|
-
|
|
133
|
-
## Transform Types
|
|
134
|
-
|
|
135
|
-
### Implemented ✅
|
|
136
|
-
|
|
137
|
-
1. **CustomTransform** (`CustomTransform.js`)
|
|
138
|
-
|
|
139
|
-
- Straight text in a rectangular container
|
|
140
|
-
- Supports both corner handles (uniform scale) and side handles (width only)
|
|
141
|
-
- Word wrapping
|
|
142
|
-
|
|
143
|
-
2. **CircleTransform** (`CircleTransform.js`)
|
|
144
|
-
|
|
145
|
-
- Text follows a circular path
|
|
146
|
-
- Characters positioned along the circle arc
|
|
147
|
-
- Centered at top of circle
|
|
148
|
-
- Ported from existing `EditableTextPath.jsx` circle mode
|
|
149
|
-
|
|
150
|
-
3. **ArchTransform** (`ArchTransform.js`)
|
|
151
|
-
- Text curves upward in an arc
|
|
152
|
-
- Parabolic curve shape
|
|
153
|
-
- Uniform scaling
|
|
154
|
-
|
|
155
|
-
### To Be Implemented 🚧
|
|
156
|
-
|
|
157
|
-
4. **WaveTransform** - Sine wave pattern
|
|
158
|
-
5. **RiseTransform** - Rising curve effect
|
|
159
|
-
6. **FlagTransform** - Wavy flag effect
|
|
160
|
-
7. **AngleTransform** - Diagonal/angled text
|
|
161
|
-
8. **DistortTransform** - Perspective distortion
|
|
162
|
-
|
|
163
|
-
## Getting Started
|
|
164
|
-
|
|
165
|
-
### Installation
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
cd custom-canvas
|
|
169
|
-
npm install
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Development
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
npm run dev
|
|
20
|
+
`react` and `react-dom` (>=18) are peer dependencies.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { SnowconeCanvas } from '@snowcone-app/canvas';
|
|
26
|
+
import '@snowcone-app/canvas/style.css';
|
|
27
|
+
|
|
28
|
+
export default function Customizer() {
|
|
29
|
+
return (
|
|
30
|
+
<SnowconeCanvas
|
|
31
|
+
// One artboard per print area. The name is the export key.
|
|
32
|
+
artboards={[{ name: 'Front', width: 1200, height: 1200 }]}
|
|
33
|
+
// Optional: drop the shopper's art in to start from.
|
|
34
|
+
imageConfig={{ src: 'https://cdn.example.com/art.png', scaleMode: 'contain' }}
|
|
35
|
+
// Fires on every edit — persist this JSON to reload the design later.
|
|
36
|
+
onChange={(state) => saveDraft(state)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
176
40
|
```
|
|
177
41
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
42
|
+
That renders a working editor out of the box (toolbar, layers, text/image/effects).
|
|
43
|
+
Use the `kit` prop to switch presets (`'pro-studio'` default, `'compact-customizer'`,
|
|
44
|
+
`'embed-only'`) or pass `layoutConfig` to go canvas-only.
|
|
45
|
+
|
|
46
|
+
## Common recipes
|
|
47
|
+
|
|
48
|
+
### Multiple print areas (placements)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<SnowconeCanvas
|
|
52
|
+
artboards={[
|
|
53
|
+
{ name: 'Front', width: 1200, height: 1200 },
|
|
54
|
+
{ name: 'Back', width: 1200, height: 1200 },
|
|
55
|
+
// clipShape masks content to a shape (e.g. a circular badge).
|
|
56
|
+
{ name: 'Pocket', width: 400, height: 400, clipShape: 'circle' },
|
|
57
|
+
]}
|
|
58
|
+
activeArtboard="Front"
|
|
59
|
+
onArtboardChange={(name) => console.log('now editing', name)}
|
|
60
|
+
/>
|
|
184
61
|
```
|
|
185
62
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
```javascript
|
|
189
|
-
import { CustomTransform, CircleTransform } from "./transforms";
|
|
190
|
-
|
|
191
|
-
// Create a straight text shape
|
|
192
|
-
const straightText = new CustomTransform({
|
|
193
|
-
text: "Hello World",
|
|
194
|
-
x: 100,
|
|
195
|
-
y: 100,
|
|
196
|
-
fontSize: 24,
|
|
197
|
-
color: "#333",
|
|
198
|
-
transformData: { width: 200 },
|
|
199
|
-
});
|
|
63
|
+
### Save and restore a design
|
|
200
64
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
y: 200,
|
|
206
|
-
fontSize: 20,
|
|
207
|
-
color: "#0066cc",
|
|
208
|
-
transformData: { radius: 80, scale: 1 },
|
|
209
|
-
});
|
|
65
|
+
```tsx
|
|
66
|
+
// SAVE — onChange hands you a CanvasState (elements + artboards). Persist the
|
|
67
|
+
// JSON. Do NOT save a flattened PNG as the state — you'll lose the layers.
|
|
68
|
+
const [state, setState] = useState<CanvasState | null>(null);
|
|
210
69
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
70
|
+
<SnowconeCanvas
|
|
71
|
+
artboards={artboards}
|
|
72
|
+
onChange={setState}
|
|
73
|
+
// RELOAD — feed the saved elements back in to restore an editable design.
|
|
74
|
+
initialElements={savedState?.elements}
|
|
75
|
+
/>
|
|
215
76
|
```
|
|
216
77
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
// Transform-specific data
|
|
232
|
-
this.transformData.amplitude = config.transformData?.amplitude || 20;
|
|
233
|
-
this.transformData.frequency = config.transformData?.frequency || 2;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
getBoundingBox() {
|
|
237
|
-
// Return {x, y, width, height} in world coordinates
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
render(ctx, isSelected = false) {
|
|
241
|
-
// Draw the transformed text on canvas
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
resize(anchor, newWidth, newHeight, startData) {
|
|
245
|
-
// Handle resize transformation
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
getEnabledAnchors() {
|
|
249
|
-
// Return array of enabled anchor names
|
|
250
|
-
return ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
78
|
+
### Print-ready exports
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<SnowconeCanvas
|
|
82
|
+
artboards={[{ name: 'Front', width: 1200, height: 1200 }]}
|
|
83
|
+
exportConfig={{
|
|
84
|
+
autoExportConfig: { enabled: true, debounceMs: 200 },
|
|
85
|
+
format: 'blob', // 'blob' for uploads, 'dataUrl' for inline previews
|
|
86
|
+
exportAll: true, // export every artboard, not just the active one
|
|
87
|
+
scale: 2, // resolution multiplier (capped by maxSize)
|
|
88
|
+
}}
|
|
89
|
+
// Keyed by artboard name: { Front: Blob, Back: Blob }
|
|
90
|
+
onExport={(exports) => uploadToBackend(exports)}
|
|
91
|
+
/>
|
|
253
92
|
```
|
|
254
93
|
|
|
255
|
-
|
|
256
|
-
4. Add to `TRANSFORM_TYPES` array in `App.jsx`
|
|
257
|
-
|
|
258
|
-
## Key Components
|
|
259
|
-
|
|
260
|
-
### TextShape (Base Class)
|
|
261
|
-
|
|
262
|
-
- Common interface for all text shapes
|
|
263
|
-
- Properties: text, position, rotation, fontSize, color
|
|
264
|
-
- Methods: getBoundingBox(), hitTest(), render(), resize()
|
|
265
|
-
|
|
266
|
-
### TransformHandles
|
|
267
|
-
|
|
268
|
-
- Manages 8 resize handles (4 corners + 4 sides)
|
|
269
|
-
- Manages rotation handle
|
|
270
|
-
- Hit detection
|
|
271
|
-
- Rendering with consistent style
|
|
272
|
-
|
|
273
|
-
### CanvasEditor
|
|
274
|
-
|
|
275
|
-
- Main React component
|
|
276
|
-
- Canvas rendering loop
|
|
277
|
-
- Mouse interaction (drag, resize, rotate)
|
|
278
|
-
- Selection management
|
|
279
|
-
|
|
280
|
-
### GeometryUtils
|
|
281
|
-
|
|
282
|
-
- `calculateFixedCornerPosition()` - Keep opposite corner fixed during resize
|
|
283
|
-
- `calculateRotationHandlePosition()` - Position rotation handle
|
|
284
|
-
- `calculateResizeHandles()` - Calculate all 8 handle positions
|
|
285
|
-
- `hitTestRect()` - Hit test for rotated rectangles
|
|
286
|
-
- `hitTestCircle()` - Hit test for circles
|
|
287
|
-
- `measureTextWidth()` - Measure text using canvas API
|
|
288
|
-
|
|
289
|
-
## Transform Handle Behavior
|
|
290
|
-
|
|
291
|
-
### Corner Handles
|
|
292
|
-
|
|
293
|
-
- **What they do**: Uniform scale (fontSize and dimensions both change)
|
|
294
|
-
- **Fixed point**: Diagonally opposite corner stays in place
|
|
295
|
-
- **Enabled on**: All transform types
|
|
296
|
-
|
|
297
|
-
### Side Handles
|
|
298
|
-
|
|
299
|
-
- **What they do**: Width only (fontSize stays constant)
|
|
300
|
-
- **Fixed point**: Opposite side edge
|
|
301
|
-
- **Enabled on**: Straight text only (CustomTransform)
|
|
302
|
-
|
|
303
|
-
### Rotation Handle
|
|
304
|
-
|
|
305
|
-
- **What it does**: Rotates around center of bounding box
|
|
306
|
-
- **Position**: 50px below bottom edge, centered
|
|
307
|
-
- **Visual feedback**: Shows angle tooltip while rotating
|
|
308
|
-
|
|
309
|
-
## Architecture Highlights
|
|
310
|
-
|
|
311
|
-
| Aspect | Implementation |
|
|
312
|
-
| --------------------- | --------------------------------------------------- |
|
|
313
|
-
| **Code Organization** | ~200 lines per file, modular components |
|
|
314
|
-
| **Transform System** | Direct Canvas API control with rotation matrix math |
|
|
315
|
-
| **Maintainability** | Clean separation of concerns, type-safe |
|
|
316
|
-
| **Extensibility** | Easy to add transforms (extend base class) |
|
|
317
|
-
| **Performance** | Direct canvas API + Web Worker exports |
|
|
318
|
-
| **Effects Pipeline** | Strokes, masks, distress, knockout compositing |
|
|
319
|
-
|
|
320
|
-
## Next Steps
|
|
321
|
-
|
|
322
|
-
1. **Implement Remaining Transforms**
|
|
94
|
+
Exports always produce transparent PNGs/WebP — the preview background is never baked in.
|
|
323
95
|
|
|
324
|
-
|
|
325
|
-
- RiseTransform (rising curve)
|
|
326
|
-
- FlagTransform (wavy flag)
|
|
327
|
-
- AngleTransform (diagonal)
|
|
328
|
-
- DistortTransform (perspective)
|
|
96
|
+
## API at a glance
|
|
329
97
|
|
|
330
|
-
|
|
98
|
+
`<SnowconeCanvas />` is the single entry point. Key props:
|
|
331
99
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
100
|
+
| Prop | Type | Purpose |
|
|
101
|
+
|------|------|---------|
|
|
102
|
+
| `artboards` | `ArtboardConfig[]` | One entry per print area; `name` is the export key |
|
|
103
|
+
| `initialElements` | `AnyElementConfig[]` | Restore a previously saved design |
|
|
104
|
+
| `imageConfig` | `ImageConfig` | Initial image: `src`, `alignment`, `scale`, `scaleMode` |
|
|
105
|
+
| `exportConfig` | `ExportConfig` | Auto-export, format, scale, max size |
|
|
106
|
+
| `layoutConfig` | `LayoutConfig` | Sizing, border radius, toggle toolbar/layers |
|
|
107
|
+
| `kit` | `'pro-studio' \| 'compact-customizer' \| 'embed-only' \| KitDefinition` | Editor preset |
|
|
108
|
+
| `onChange` | `(state: CanvasState) => void` | Persist design state on edit |
|
|
109
|
+
| `onExport` | `(exports: Record<string, string \| Blob>) => void` | Receive rendered placements |
|
|
110
|
+
| `onArtboardChange` | `(name: string) => void` | Active placement changed |
|
|
335
111
|
|
|
336
|
-
|
|
112
|
+
Need lower-level control? `@snowcone-app/canvas` also exports React hooks
|
|
113
|
+
(`useEditor`, `useArtboards`, `useLayers`, `useExport`) and state helpers
|
|
114
|
+
(`serializeState`, `deserializeState`). See the docs for the full reference.
|
|
337
115
|
|
|
338
|
-
|
|
339
|
-
- Duplicate shape
|
|
340
|
-
- Undo/redo
|
|
116
|
+
All exports are fully typed — TypeScript definitions ship with the package.
|
|
341
117
|
|
|
342
|
-
|
|
343
|
-
- Save shapes to JSON
|
|
344
|
-
- Load shapes from JSON
|
|
345
|
-
- Export as image
|
|
118
|
+
## Requirements
|
|
346
119
|
|
|
347
|
-
|
|
120
|
+
- React 18 or 19
|
|
121
|
+
- A bundler that handles ESM and CSS imports (Vite, Next.js, Remix, etc.)
|
|
122
|
+
- Modern browsers: Chrome 69+, Firefox 105+, Safari 16.4+, Edge 79+
|
|
348
123
|
|
|
349
|
-
|
|
124
|
+
## Links
|
|
350
125
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
126
|
+
- 📖 [Documentation](https://developers.snowcone.app/canvas)
|
|
127
|
+
- 🐛 [Issues](https://github.com/snowcone-app/snowcone-monorepo/issues)
|
|
128
|
+
- 🌐 [snowcone.app](https://snowcone.app)
|
|
354
129
|
|
|
355
130
|
## License
|
|
356
131
|
|
package/dist/api/stable.d.ts
CHANGED
|
@@ -40,5 +40,6 @@ export type { ArtboardConfig as ArtboardElementConfig, TransformType, AnyElement
|
|
|
40
40
|
export { ErrorBoundary } from '../components/embed/ErrorBoundary.js';
|
|
41
41
|
export type { ErrorBoundaryProps } from '../components/embed/ErrorBoundary.js';
|
|
42
42
|
export { serializeState, deserializeState, validateState, migrateState, } from '../state/index.js';
|
|
43
|
-
export { serializeStateForServer } from '../rendering/serialize-for-server.js';
|
|
43
|
+
export { serializeStateForServer, CANVAS_STATE_SCHEMA_VERSION, } from '../rendering/serialize-for-server.js';
|
|
44
|
+
export type { ServerRenderRequest } from '../rendering/serialize-for-server.js';
|
|
44
45
|
export type { CanvasStateV1, SerializedArtboard, SerializedElement, DeserializeResult, ValidationResult, } from '../state/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const I=require("./compose-Bo108juW.cjs"),U=require("./HybridHistoryManager-BXD93pp8.cjs"),C=require("react/jsx-runtime"),o=require("react"),B=require("./ThemeContext-4mJ_y0Me.cjs"),le=require("./ElementFactory-B7UOaJSD.cjs"),de=require("./CanvasStateV1-D5GzvmnY.cjs"),it=U.createLogger("ErrorBoundary");class ze extends o.Component{constructor(e){super(e),this.resetError=()=>{this.setState({hasError:!1,error:null})},this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,r){if(it.error("Caught error:",e,r),this.props.onError&&this.props.onError(e,r),this.props.onCanvasError){const a={category:"unknown",message:e.message,originalError:e,recoverable:!1};this.props.onCanvasError(a)}}render(){return this.state.hasError&&this.state.error?this.props.renderError?this.props.renderError(this.state.error,this.resetError):this.props.fallback?typeof this.props.fallback=="function"?this.props.fallback(this.state.error,this.resetError):this.props.fallback:C.jsxs("div",{style:{padding:"24px",border:"2px solid var(--danger)",borderRadius:"8px",backgroundColor:"color-mix(in oklch, var(--danger) 10%, var(--background))",color:"var(--danger)",fontFamily:"system-ui, sans-serif"},children:[C.jsx("h2",{style:{margin:"0 0 16px 0",fontSize:"18px",fontWeight:600},children:"Something went wrong"}),C.jsx("pre",{style:{margin:"0 0 16px 0",padding:"12px",backgroundColor:"color-mix(in oklch, var(--danger) 15%, var(--background))",borderRadius:"4px",fontSize:"14px",overflow:"auto",maxHeight:"200px"},children:this.state.error.message}),C.jsx("button",{onClick:this.resetError,style:{padding:"8px 16px",backgroundColor:"var(--danger)",color:"var(--danger-foreground)",border:"none",borderRadius:"4px",fontSize:"14px",fontWeight:500,cursor:"pointer"},children:"Try again"})]}):this.props.children}}const ct=U.createLogger("useAutoExport");function Oe(t){const{config:e,historyManager:r,artboards:a,elements:n,onExport:l,onExportScheduled:s,isCanvasReady:c=!1}=t,d=o.useRef(null),b=o.useRef(!1),S=o.useRef({totalExports:0,skippedExports:0,lastExportTime:null,avgExportInterval:0}),[L,k]=o.useState(S.current),y=o.useRef(l);y.current=l,o.useEffect(()=>{const h=new I.AutoExportManager({...I.DEFAULT_AUTO_EXPORT_CONFIG,...e});return h.onExport(async()=>{try{await y.current(),S.current=h.getStats()}catch(R){ct.error("[useAutoExport] Export failed:",R)}}),d.current=h,()=>{h.destroy()}},[]),o.useEffect(()=>{c&&b.current&&d.current&&(b.current=!1,d.current.forceExport())},[c]),o.useEffect(()=>{d.current&&e&&d.current.updateConfig(e)},[e==null?void 0:e.enabled,e==null?void 0:e.debounceMs,e==null?void 0:e.maxWaitMs]),o.useEffect(()=>{d.current&&s&&d.current.onExportScheduled(s)},[s]),o.useEffect(()=>{if(!r||!d.current)return;const h=r.onCommandExecuted(()=>{if(d.current){if(!c){b.current=!0;return}d.current.scheduleExport()}});return()=>{h()}},[r,c]),o.useEffect(()=>{if(d.current){if(!c){b.current=!0;return}d.current.scheduleExport()}},[n,a,c]),o.useEffect(()=>U.subscribeToImageLoads(R=>{if(!d.current||!c)return;n.some(W=>W.id===R)&&d.current.scheduleExport()}),[n,c]);const T=o.useCallback(h=>{d.current&&d.current.updateConfig(h)},[]),w=o.useCallback(async()=>{d.current&&(await d.current.forceExport(),S.current=d.current.getStats(),k(S.current))},[]),P=o.useCallback(()=>{d.current&&(d.current.resetStats(),S.current=d.current.getStats(),k(S.current))},[]),A=o.useCallback(()=>{d.current&&d.current.resetChangeDetection()},[]);return{stats:L,updateConfig:T,forceExport:w,resetStats:P,resetChangeDetection:A}}const Ye=U.createLogger("useContentReady");function lt(t){var r;const e=new Set;for(const a of t)if("fontFamily"in a&&typeof a.fontFamily=="string"&&a.fontFamily&&e.add(a.fontFamily),"getRichText"in a&&typeof a.getRichText=="function")try{const n=a.getRichText();if(n!=null&&n.spans)for(const l of n.spans)(r=l.style)!=null&&r.fontFamily&&e.add(l.style.fontFamily)}catch{}return Array.from(e)}async function dt(t){if(t.length===0||typeof document>"u"||!document.fonts)return;const e=[];for(const r of t){const a=`16px "${r}"`;e.push(document.fonts.load(a))}await Promise.all(e),document.fonts.ready&&await Promise.race([document.fonts.ready,new Promise(r=>setTimeout(r,250))]),Ye.debug(`Awaited ${e.length} font(s)`)}function ut(){return new Promise(t=>requestAnimationFrame(()=>t()))}function $e(t){const{isCanvasReady:e,elements:r,hasInitialElements:a,initialElementsLoaded:n}=t,[l,s]=o.useState(!1),c=o.useRef(!1);return o.useEffect(()=>{if(l||!e||a&&!n||a&&r.length===0||c.current)return;c.current=!0;let d=!1;return(async()=>{try{const b=lt(r);if(b.length>0&&(await dt(b),d)||(await ut(),d))return;s(!0)}catch(b){Ye.error("Error during content readiness check:",b),d||s(!0)}finally{c.current=!1}})(),()=>{d=!0,c.current=!1}},[e,r,a,n,l]),l}const ft={"far-top":"t",top:"t",center:"c",bottom:"b","far-bottom":"b","far-left":"l",left:"l",right:"r","far-right":"r",tl:"tl",t:"t",tr:"tr",l:"l",c:"c",r:"r",bl:"bl",b:"b",br:"br"};function mt(t){return t&&ft[t]||"c"}function ht(t,e){const r=e.scaleMode||"cover",a=(r==="contain"?e.marginTop:0)||0,n=(r==="contain"?e.marginRight:0)||0,l=(r==="contain"?e.marginBottom:0)||0,s=(r==="contain"?e.marginLeft:0)||0,c=e.width-s-n,d=e.height-a-l,b=c/t.width,S=d/t.height,L=r==="contain"?Math.min(b,S):Math.max(b,S),k=e.scale||1,y=L*k,T=t.width*y,w=t.height*y,P=e.align||"c";let A=0,h=0;const R=T-c,E=w-d;switch(P){case"tl":A=0,h=0;break;case"t":A=-R/2,h=0;break;case"tr":A=-R,h=0;break;case"l":A=0,h=-E/2;break;case"c":default:A=-R/2,h=-E/2;break;case"r":A=-R,h=-E/2;break;case"bl":A=0,h=-E;break;case"b":A=-R/2,h=-E;break;case"br":A=-R,h=-E;break}A+=s,h+=a;const W=e.offsetX||0,F=e.offsetY||0,Y=R,_=E;return A+=W*Y,h+=F*_,A=Math.round(A),h=Math.round(h),{scale:y,width:Math.round(T),height:Math.round(w),x:A,y:h,debug:{coverScale:L,userScale:k,overflow:{x:R,y:E},alignment:P,appliedOffset:{x:W*Y,y:F*_}}}}const gt={timeout:3e4,retries:1,retryDelay:1e3,validateUrl:!0};function pt(t){try{const e=new URL(t);return["http:","https:","data:","blob:"].includes(e.protocol)}catch{return!1}}async function bt(t,e={}){const r={...gt,...e};if(r.validateUrl&&!pt(t))return{success:!1,error:new Error(`Invalid image URL: ${t}`)};let a;for(let n=0;n<=r.retries;n++)try{return await xt(t,r.timeout)}catch(l){a=l instanceof Error?l:new Error(String(l)),n<r.retries&&await vt(r.retryDelay)}return{success:!1,error:a||new Error("Failed to load image after retries")}}function xt(t,e){return new Promise(r=>{const a=new Image;a.crossOrigin="anonymous";let n=null,l=!1;const s=()=>{n&&(clearTimeout(n),n=null)},c=()=>{if(l)return;l=!0,s();const b=a.naturalWidth||a.width,S=a.naturalHeight||a.height;r({success:!0,element:a,width:b,height:S,aspectRatio:S>0?b/S:1})},d=b=>{l||(l=!0,s(),r({success:!1,error:new Error(b)}))};a.onload=c,a.onerror=()=>{d(`Failed to load image: ${t}`)},n=setTimeout(()=>{d(`Image load timed out after ${e}ms: ${t}`)},e),a.src=t})}async function Et(t,e,r={},a={}){var k,y,T,w,P,A,h;const n=await bt(t,a);if(!n.success||!n.width||!n.height)return{success:!1,error:n.error||new Error("Failed to get image dimensions")};const l={width:e.artboard.width,height:e.artboard.height,scale:e.scale,align:mt(e.alignment),offsetX:e.offsetX,offsetY:e.offsetY,scaleMode:e.scaleMode,marginTop:e.marginTop,marginRight:e.marginRight,marginBottom:e.marginBottom,marginLeft:e.marginLeft},s=ht({width:n.width,height:n.height},l),c=e.artboard.x+s.x,d=e.artboard.y+s.y,b=c+s.width/2,S=d+s.height/2;return{success:!0,element:new U.ImageElement({...r,x:b,y:S,imageUrl:t,imageAspectRatio:n.aspectRatio,preserveDimensions:!0,transformData:{type:"image",width:s.width,height:s.height,cropX:((k=r.transformData)==null?void 0:k.cropX)??0,cropY:((y=r.transformData)==null?void 0:y.cropY)??0,cropWidth:((T=r.transformData)==null?void 0:T.cropWidth)??1,cropHeight:((w=r.transformData)==null?void 0:w.cropHeight)??1,flipHorizontal:((P=r.transformData)==null?void 0:P.flipHorizontal)??!1,flipVertical:((A=r.transformData)==null?void 0:A.flipVertical)??!1,borderRadius:((h=r.transformData)==null?void 0:h.borderRadius)??0}}),placement:{artworkSize:{width:n.width,height:n.height},finalSize:{width:s.width,height:s.height},position:{x:b,y:S},scale:s.scale}}}function vt(t){return new Promise(e=>setTimeout(e,t))}const V=U.createLogger("SnowconeCanvas");function Ee(t,e,r,a){if(a<=0||!isFinite(a))return r;const n=t*r,l=e*r;if(Math.max(n,l)<=a)return r;const c=Math.max(t,e),d=a/c;return Math.min(d,r)}const Xe=o.forwardRef((t,e)=>{const{exportConfig:r,imageConfig:a,layoutConfig:n,kit:l,artboards:s,activeArtboard:c,onArtboardChange:d,initialElements:b,onChange:S,onSelectionChange:L,autoExportInterval:k,onExport:y,onExportScheduled:T,onExportStatus:w,onExportReady:P,onImageLoad:A,onImageError:h,onError:R,onReady:E,className:W,style:F,enableShortcuts:Y,overlay:_}=t,we=k,Z=t.autoExportConfig??(r==null?void 0:r.autoExportConfig),q=t.autoExportFormat??(r==null?void 0:r.format)??"dataUrl",ue=t.autoExportAll??(r==null?void 0:r.exportAll)??!1,X=t.exportScale??(r==null?void 0:r.scale)??2,j=t.maxExportSize??(r==null?void 0:r.maxSize)??4e3,ee=t.exportImageFormat??(r==null?void 0:r.imageFormat)??"png",te=t.exportImageQuality??(r==null?void 0:r.imageQuality)??.92,z=t.initialImage??(a==null?void 0:a.src),ye=t.initialImageAlignment??(a==null?void 0:a.alignment)??"center",Ae=t.initialImageScale??(a==null?void 0:a.scale)??1,Se=t.initialImageScaleMode??(a==null?void 0:a.scaleMode)??"cover",Ke=t.width??(n==null?void 0:n.width)??1200,Ve=t.height??(n==null?void 0:n.height)??1200,Re=t.viewPadding??(n==null?void 0:n.viewPadding)??.9,Ce=t.artboardBorderRadius??(n==null?void 0:n.artboardBorderRadius)??0,Ie=t.fixedMargin??(n==null?void 0:n.fixedMargin),ke=t.maxHeight??(n==null?void 0:n.maxHeight),Me=t.showRotationHandle??(n==null?void 0:n.showRotationHandle)??!0,qe=t.hideCanvas??(n==null?void 0:n.hideCanvas)??!1,Te=t.canvasWrapperClassName??(n==null?void 0:n.canvasWrapperClassName),Fe=t.canvasWrapperStyle??(n==null?void 0:n.canvasWrapperStyle),De=t.canvasCutouts??(n==null?void 0:n.canvasCutouts),fe=t.pieceGuides,Le=t.pieceFocus,Pe=Y??!0,re=o.useMemo(()=>I.resolveKit(l??"pro-studio"),[l]);o.useEffect(()=>{if(U.process$1.env.NODE_ENV==="development"&&re){const i=I.validateKit(re);i.valid||V.warn("Kit validation warnings:",i.errors),i.warnings.length>0&&V.warn("Kit validation warnings:",i.warnings)}},[re]);const ne=o.useRef(R);ne.current=R;const N=o.useCallback(i=>{var u;V.error(`[${i.category}] ${i.message}`,i.originalError),(u=ne.current)==null||u.call(ne,i)},[]);o.useEffect(()=>(le.CanvasRenderer.onRenderError=N,()=>{le.CanvasRenderer.onRenderError===N&&(le.CanvasRenderer.onRenderError=null)}),[N]);const{elements:K,setElements:ae,selectedId:He,artboardManager:M,refreshArtboards:Ge,historyManager:Qe,isCanvasReady:me,setCanvasReady:oe}=B.useEditor(),{createArtboard:Ze,selectArtboard:Je,artboards:H}=I.useArtboards(),{exportArtboard:G,exportAllArtboards:se,exportArtboardAsBlob:Q,exportAllArtboardsAsBlobs:ie}=I.useExport();o.useImperativeHandle(e,()=>({exportArtboards:async(i={})=>{const u=i.format||q,g=i.scale||X,p=i.all??!1;let m=g;if(j>0&&isFinite(j))for(const v of H){const x=Ee(v.width,v.height,g,j);m=Math.min(m,x)}const f={scale:m,format:ee,quality:te};if(p){const v=u==="blob"?await ie(f):await se(f),x={};for(const[D,O]of Object.entries(v)){const $=H.find(J=>J.id===D);$&&(x[$.name]=O)}return x}else{const v=M.getActiveArtboard();if(!v)throw new Error("[SnowconeCanvas] No active artboard found");if(u==="blob"){const x=await Q(v.id,f);return{[v.name]:x}}else{const x=await G(v.id,f);return{[v.name]:x}}}}}),[G,se,Q,ie,q,X,j,ee,te,M,H]),o.useEffect(()=>{P&&P(async()=>{const u=M.getActiveArtboard();if(!u)throw new Error("[SnowconeCanvas] No active artboard found");const g=Ee(u.width,u.height,X,j),p=q==="blob"?await Q(u.id,{scale:g}):await G(u.id,{scale:g});return{[u.name]:p}})},[P,G,Q,M,q,X,j]);const[Be,ce]=o.useState("idle"),[We,he]=o.useState(null),ge=o.useRef(void 0),pe=o.useRef(!1),Ne=o.useRef(!1),[_e,et]=o.useState(!1),tt=o.useRef(0),rt=o.useRef(0),be=o.useRef(!1);o.useEffect(()=>{if(pe.current)return;pe.current=!0;const i=s||[{name:"Design",width:Ke,height:Ve}],u=M.getAllArtboards();if(u.length>0){const g=i[0],p=u[0],m=fe?"transparent":void 0;p.name=g.name,p.width=g.width,p.height=g.height,p.clipShape=g.clipShape,g.backgroundColor?p.backgroundColor=g.backgroundColor:m&&(p.backgroundColor=m);for(let f=1;f<i.length;f++)Ze(i[f].width,i[f].height,{name:i[f].name,backgroundColor:i[f].backgroundColor??m,clipShape:i[f].clipShape})}Ge()},[]),o.useEffect(()=>{if(!(!b||b.length===0)&&!Ne.current&&pe.current){Ne.current=!0,et(!0);try{const i=le.ElementFactory.createManyFromJSON(b),u=M.getAllArtboards();if(u.length>0){const p=u[0];for(const m of i)M.addElementToArtboard(m.id,p.id)}ae(i);const g=new Set;for(const p of i){const m=p.fontFamily;typeof m=="string"&&m.trim().length>0&&g.add(m)}if(g.size>0&&typeof document<"u"){const p=document.fonts??null,m=Array.from(g).map(f=>{const v=`font-${f.replace(/\s+/g,"-").toLowerCase()}`;let x=document.getElementById(v);return x||(x=document.createElement("link"),x.id=v,x.rel="stylesheet",x.href=`https://fonts.googleapis.com/css2?family=${f.replace(/\s+/g,"+")}:wght@400;700&display=swap`,document.head.appendChild(x)),new Promise(D=>{if(x.sheet){D(f);return}const O=()=>D(f);x.addEventListener("load",O,{once:!0}),x.addEventListener("error",O,{once:!0}),setTimeout(O,4e3)})});Promise.all(m).then(f=>{if(p)return Promise.all(f.flatMap(v=>[p.load(`400 16px "${v}"`).catch(()=>{}),p.load(`700 16px "${v}"`).catch(()=>{})]))}).then(()=>{ae(f=>f.slice())})}}catch(i){V.error("Failed to load initial elements:",i);const u=i instanceof Error?i:new Error(String(i));N({category:"import",message:`Failed to load initial elements: ${u.message}`,originalError:u,recoverable:!0})}}},[b,M,ae,N]),o.useEffect(()=>{if(!c){be.current=!0;return}const i=H.find(u=>u.name===c);if(i){const u=M.getActiveArtboard();(u==null?void 0:u.name)!==c&&Je(i.id),be.current=!0}},[c,H]),o.useEffect(()=>{if(!z||ge.current===z)return;if(K.length>0){ge.current=z;return}ge.current=z,(async()=>{ce("loading"),he(null);try{const u=M.getAllArtboards();if(u.length===0)throw new Error("No artboards available");const g=[];for(const p of u){const m=s==null?void 0:s.find(x=>x.name===p.name),f=(m==null?void 0:m.scaleMode)||Se,v=await Et(z,{artboard:{width:p.width,height:p.height,x:p.x,y:p.y},alignment:(m==null?void 0:m.fitAlign)||ye,scale:Ae,scaleMode:f,marginTop:m==null?void 0:m.fitMarginTop,marginRight:m==null?void 0:m.fitMarginRight,marginBottom:m==null?void 0:m.fitMarginBottom,marginLeft:m==null?void 0:m.fitMarginLeft});v.success&&v.element?(M.addElementToArtboard(v.element.id,p.id),g.push(v.element)):V.error("Failed to load image for artboard:",p.name,v.error)}if(g.length>0)ae(p=>[...p,...g]),ce("success"),A==null||A(z);else{const p=new Error("Failed to load image into any artboard");ce("error"),he(p),h==null||h(z,p),N({category:"image",message:p.message,originalError:p,recoverable:!0})}}catch(u){const g=u instanceof Error?u:new Error(String(u));ce("error"),he(g),h==null||h(z,g),N({category:"image",message:g.message,originalError:g,recoverable:!0})}})()},[z,ye,Ae,Se]),o.useEffect(()=>{L==null||L(He)},[He,L]),o.useEffect(()=>{if(!S)return;const i=M.getActiveArtboard(),u={elements:K.map(g=>g.toJSON()),artboards:H.map(g=>({name:g.name,width:g.width,height:g.height,clipShape:g.clipShape,backgroundColor:g.backgroundColor})),activeArtboard:(i==null?void 0:i.name)||"Design"};S(u)},[K,H,S]),o.useEffect(()=>{if(!d||!be.current)return;const i=M.getActiveArtboard();i&&d(i.name)},[M.getActiveArtboardId()]);const nt=o.useCallback(async()=>{if(!y&&!w)return;const i=++rt.current,u=Date.now();let g=X;if(j>0&&isFinite(j))for(const f of H){const v=Ee(f.width,f.height,X,j);g=Math.min(g,v)}const p=M.getActiveArtboard(),m=(p==null?void 0:p.id)??"unknown";w==null||w({status:"rendering",artboardId:m}),tt.current=u;try{let f;const v={format:ee,scale:g,quality:te};if(q==="blob")if(ue){const x=await ie(v);f={};for(const[D,O]of Object.entries(x)){const $=H.find(J=>J.id===D);$&&(f[$.name]=O)}}else{const x=M.getActiveArtboard();if(!x)return;const D=await Q(x.id,v);f={[x.name]:D}}else if(ue){const x=await se(v);f={};for(const[D,O]of Object.entries(x)){const $=H.find(J=>J.id===D);$&&(f[$.name]=O)}}else{const x=M.getActiveArtboard();if(!x)return;const D=await G(x.id,v);f={[x.name]:D}}y==null||y(f),w==null||w({status:"complete",artboardId:m,result:f})}catch(f){const v=f instanceof Error?f.message:String(f);if(v.includes("Canvas ref not available"))return;const x=Date.now()-u;V.error("Export failed (export #"+i+"):",{duration:`${x}ms`,error:v});const D=f instanceof Error?f:new Error(v);w==null||w({status:"error",artboardId:m,error:D}),N({category:"export",message:D.message,originalError:D,artboardId:m,recoverable:!0})}},[y,w,ue,q,X,j,ee,te,G,se,Q,ie,H,M,K,Z,N]);o.useEffect(()=>{we!==void 0&&we>0&&V.warn("autoExportInterval is deprecated and ignored. Use autoExportConfig={{ enabled: true, debounceMs: 100, maxWaitMs: 1000 }} instead.")},[]),o.useEffect(()=>{},[K]);const at=o.useCallback(()=>{if(T==null||T(),w){const i=M.getActiveArtboard();w({status:"scheduled",artboardId:(i==null?void 0:i.id)??"unknown"})}},[T,w,M]),ot=!!b&&b.length>0,xe=$e({isCanvasReady:me,elements:K,hasInitialElements:ot,initialElementsLoaded:_e}),je=o.useRef(!1);o.useEffect(()=>{xe&&!je.current&&(je.current=!0,E==null||E())},[xe,E]),Oe({config:Z?{enabled:Z.enabled??!0,debounceMs:Z.debounceMs??100,maxWaitMs:Z.maxWaitMs??1e3}:{enabled:!1},historyManager:Qe,artboards:H,elements:K,onExport:nt,onExportScheduled:at,isCanvasReady:xe}),o.useEffect(()=>{if(H.length>0&&!me){const i=requestAnimationFrame(()=>{oe(!0)});return()=>cancelAnimationFrame(i)}},[H.length,me,oe]),o.useEffect(()=>()=>{oe(!1)},[oe]);const st=o.useCallback(i=>{N({category:"unknown",message:i.message,originalError:i,recoverable:!1})},[N]);return Be==="loading"?C.jsx("div",{className:W,style:{...F,display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"var(--surface, #f5f5f5)"},children:C.jsx(I.Spinner,{size:48})}):Be==="error"&&We?C.jsx("div",{className:W,style:{...F,display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"var(--surface, #f5f5f5)",color:"var(--danger, #dc2626)",padding:"1rem"},children:C.jsxs("div",{className:"text-center",children:[C.jsx("div",{className:"font-medium",children:"Failed to load image"}),C.jsx("div",{className:"text-sm mt-1 opacity-70",children:We.message})]})}):C.jsx(I.KitProvider,{kit:re,children:C.jsx("div",{className:`app-modern ${W||""}`,style:{...F,position:"relative"},children:C.jsxs(ze,{onError:st,fallback:(i,u)=>C.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"24px",minHeight:"200px",backgroundColor:"var(--surface, #f5f5f5)",color:"var(--foreground, #333)",fontFamily:"system-ui, sans-serif",textAlign:"center"},children:[C.jsx("div",{style:{fontSize:"18px",fontWeight:600,marginBottom:"8px"},children:"Something went wrong"}),C.jsx("div",{style:{fontSize:"14px",opacity:.7,marginBottom:"16px",maxWidth:"400px"},children:i.message}),C.jsx("button",{onClick:u,style:{padding:"8px 20px",backgroundColor:"var(--primary, #333)",color:"var(--primary-foreground, #fff)",border:"none",borderRadius:"6px",fontSize:"14px",fontWeight:500,cursor:"pointer"},children:"Try again"})]}),children:[!qe&&(Te||Fe?C.jsx("div",{className:Te,style:Fe,children:C.jsx(I.Canvas,{style:{width:"100%"},fitPadding:Re,artboardBorderRadius:Ce,fixedMargin:Ie,maxHeight:ke,showRotationHandle:Me,enableShortcuts:Pe,canvasCutouts:De,pieceGuides:fe,pieceFocus:Le})}):C.jsx(I.Canvas,{style:{width:"100%"},fitPadding:Re,artboardBorderRadius:Ce,fixedMargin:Ie,maxHeight:ke,showRotationHandle:Me,enableShortcuts:Pe,canvasCutouts:De,pieceGuides:fe,pieceFocus:Le})),_]})})})});Xe.displayName="SnowconeCanvasInner";const ve=o.forwardRef((t,e)=>{var s;const{inheritTheme:r,externalProvider:a}=t,n=t.viewPadding??((s=t.layoutConfig)==null?void 0:s.viewPadding),l=C.jsx(Xe,{ref:e,...t});return a?C.jsx(B.ThemeProvider,{defaultTheme:"light",passive:r,children:l}):C.jsx(B.ThemeProvider,{defaultTheme:"light",passive:r,children:C.jsx(B.EditorProvider,{viewPadding:n,children:l})})});ve.displayName="SnowconeCanvas";const Ue=U.createLogger("useCommands");function wt(){const t=B.useEditor(),{undo:e,redo:r,canUndo:a,canRedo:n,executeElementUpdate:l,executeAddElement:s,executeRemoveElement:c,executeReorderElement:d,executeCommandBatch:b,executeCreateArtboard:S,executeDeleteArtboard:L,executeUpdateArtboard:k}=t,y=o.useCallback(()=>{Ue.warn("[useCommands] clearHistory is not yet implemented in EditorContext")},[]),T=o.useCallback(w=>{Ue.warn("[useCommands] clearArtboardHistory is not yet implemented in EditorContext")},[]);return{undo:e,redo:r,canUndo:a,canRedo:n,clearHistory:y,clearArtboardHistory:T,executeElementUpdate:l,executeAddElement:s,executeRemoveElement:c,executeReorderElement:d,executeCreateArtboard:S,executeDeleteArtboard:L,executeUpdateArtboard:k,executeCommandBatch:b}}function yt(){const{selectedElement:t}=B.useEditor();return t??null}function At(){const{isCanvasReady:t}=B.useEditor();return t}const St=()=>{},Rt=[];function Ct(t){const{elementStore:e}=B.useElementsContext(),{executeElementUpdate:r}=B.useCommandContext(),a=o.useMemo(()=>e.getAllByName(t).filter(s=>s instanceof U.TextElement),[e,t]),n=o.useCallback(l=>{for(const s of a){const c=s.clone();c.setText(l),r(s,c)}},[a,r]);return a.length===0?{text:"",setText:St,element:null,elements:Rt,isConnected:!1}:{text:a[0].getText(),setText:n,element:a[0],elements:a,isConnected:!0}}const It=()=>{},kt=[];function Mt(t,e,r){const a=t/e;if(r>=a){const n=e,l=n*r,s=t/l;return{width:l,height:n,cropX:(1-s)/2,cropY:0,cropWidth:s,cropHeight:1}}else{const n=t,l=n/r,s=e/l;return{width:n,height:l,cropX:0,cropY:(1-s)/2,cropWidth:1,cropHeight:s}}}function Tt(t,e){const r=(e==null?void 0:e.fit)??"cover",{elementStore:a,setElements:n}=B.useElementsContext(),{executeElementUpdate:l}=B.useCommandContext(),s=o.useMemo(()=>a.getAllByName(t).filter(y=>y instanceof U.ImageElement),[a,t]),c=o.useRef(new Map);for(const k of s)if(!c.current.has(k.id)){const y=k.transformData;c.current.set(k.id,{width:y.width*y.cropWidth,height:y.height*y.cropHeight})}const d=o.useRef(s);d.current=s;const b=o.useRef(l);b.current=l;const S=o.useRef(n);S.current=n;const L=o.useCallback(k=>{const y=d.current,T=b.current;S.current;for(const w of y){const P=c.current.get(w.id);if(!P)continue;const{width:A,height:h}=P,R=w.clone();R.imageLoaded=!1,R.imageElement=null,R.isCropping=!1,R.imageUrl=k,r==="cover"?(R.preserveDimensions=!0,R.onLoadCallback=E=>{const W=E.imageAspectRatio||1,F=Mt(A,h,W);E.transformData.width=F.width,E.transformData.height=F.height,E.transformData.cropX=F.cropX,E.transformData.cropY=F.cropY,E.transformData.cropWidth=F.cropWidth,E.transformData.cropHeight=F.cropHeight,T(w,E)}):(R.preserveDimensions=!1,R.onLoadCallback=E=>{const W=E.transformData.width,F=E.transformData.height,Y=Math.min(A/W,h/F,1);Y<1&&(E.transformData.width=W*Y,E.transformData.height=F*Y),E.transformData.cropX=0,E.transformData.cropY=0,E.transformData.cropWidth=1,E.transformData.cropHeight=1,T(w,E)}),R.loadImage(k)}},[r]);return s.length===0?{imageUrl:"",setImageUrl:It,element:null,elements:kt,isConnected:!1}:{imageUrl:s[0].imageUrl,setImageUrl:L,element:s[0],elements:s,isConnected:!0}}function Ft(t){const{elementStore:e}=B.useElementsContext();return t?e.getByName(t)??null:null}function Dt(){const{zoom:t,panOffset:e,zoomIn:r,zoomOut:a,zoomToFit:n,resetView:l,setZoom:s,setPanOffset:c}=B.useViewportContext();return{zoom:t,panOffset:e,zoomIn:r,zoomOut:a,zoomToFit:n,resetView:l,setZoom:s,setPanOffset:c}}function Lt(t){var a;const e=((a=t.artboards)==null?void 0:a[0])||{name:"Front",width:800,height:800},r=(t.elements||[]).map(Pt);return{artboards:[{id:"artboard-1",name:e.name||"Front",x:0,y:0,width:e.width||800,height:e.height||800,backgroundColor:e.backgroundColor||"transparent",exportBackground:!1,elements:r,distressTexture:e.distressTexture,imageMask:e.imageMask}]}}function Pt(t){const e=t.type||t.transformType;return e==="image"?Ht(t):{...t,type:e}}function Ht(t){var b;const e=t.transformData||{},r=t.masks&&t.masks.length>0,a=e.width||0,n=e.height||0,l=r?a:a*(e.cropWidth??1),s=r?n:n*(e.cropHeight??1),c={id:t.id,type:"image",x:t.x||0,y:t.y||0,width:l,height:s,rotation:t.rotation||0,imageUrl:t.imageUrl,imageAspectRatio:t.imageAspectRatio};return!r&&(e.cropX!==0||e.cropY!==0||e.cropWidth!==1||e.cropHeight!==1)&&(c.cropX=e.cropX,c.cropY=e.cropY,c.cropWidth=e.cropWidth,c.cropHeight=e.cropHeight,c.needsCropPixelConversion=!0),e.flipHorizontal&&(c.flipHorizontal=e.flipHorizontal),e.flipVertical&&(c.flipVertical=e.flipVertical),e.borderRadius&&(c.borderRadius=e.borderRadius),t.opacity!==void 0&&(c.opacity=t.opacity),t.distressEffect&&(c.distressEffect=t.distressEffect),((b=t.masks)==null?void 0:b.length)>0&&(c.masks=t.masks),t.blendMode&&(c.blendMode=t.blendMode),t.knockoutParts&&(c.knockoutParts=t.knockoutParts),t.stroke&&(c.stroke=t.stroke),c}I.ensureIconsRegistered();exports.ALL_CAPABILITIES=I.ALL_CAPABILITIES;exports.COMPACT_CUSTOMIZER=I.COMPACT_CUSTOMIZER;exports.EMBED_ONLY=I.EMBED_ONLY;exports.MINIMAL_CAPABILITIES=I.MINIMAL_CAPABILITIES;exports.PRO_STUDIO=I.PRO_STUDIO;exports.createKit=I.createKit;exports.extendKit=I.extendKit;exports.resolveKit=I.resolveKit;exports.useArtboards=I.useArtboards;exports.useExport=I.useExport;exports.useLayers=I.useLayers;exports.validateKit=I.validateKit;exports.useEditor=B.useEditor;exports.deserializeState=de.deserializeState;exports.migrateState=de.migrateState;exports.serializeState=de.serializeState;exports.validateState=de.validateState;exports.ErrorBoundary=ze;exports.SnowconeCanvas=ve;exports.default=ve;exports.serializeStateForServer=Lt;exports.useAutoExport=Oe;exports.useCanvasReady=At;exports.useCommands=wt;exports.useContentReady=$e;exports.useElementByName=Ft;exports.useImageBinding=Tt;exports.useSelectedElement=yt;exports.useTextBinding=Ct;exports.useViewport=Dt;
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const I=require("./compose-Bo108juW.cjs"),U=require("./HybridHistoryManager-BXD93pp8.cjs"),C=require("react/jsx-runtime"),o=require("react"),B=require("./ThemeContext-4mJ_y0Me.cjs"),le=require("./ElementFactory-B7UOaJSD.cjs"),de=require("./CanvasStateV1-D5GzvmnY.cjs"),ct=U.createLogger("ErrorBoundary");class ze extends o.Component{constructor(e){super(e),this.resetError=()=>{this.setState({hasError:!1,error:null})},this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,r){if(ct.error("Caught error:",e,r),this.props.onError&&this.props.onError(e,r),this.props.onCanvasError){const a={category:"unknown",message:e.message,originalError:e,recoverable:!1};this.props.onCanvasError(a)}}render(){return this.state.hasError&&this.state.error?this.props.renderError?this.props.renderError(this.state.error,this.resetError):this.props.fallback?typeof this.props.fallback=="function"?this.props.fallback(this.state.error,this.resetError):this.props.fallback:C.jsxs("div",{style:{padding:"24px",border:"2px solid var(--danger)",borderRadius:"8px",backgroundColor:"color-mix(in oklch, var(--danger) 10%, var(--background))",color:"var(--danger)",fontFamily:"system-ui, sans-serif"},children:[C.jsx("h2",{style:{margin:"0 0 16px 0",fontSize:"18px",fontWeight:600},children:"Something went wrong"}),C.jsx("pre",{style:{margin:"0 0 16px 0",padding:"12px",backgroundColor:"color-mix(in oklch, var(--danger) 15%, var(--background))",borderRadius:"4px",fontSize:"14px",overflow:"auto",maxHeight:"200px"},children:this.state.error.message}),C.jsx("button",{onClick:this.resetError,style:{padding:"8px 16px",backgroundColor:"var(--danger)",color:"var(--danger-foreground)",border:"none",borderRadius:"4px",fontSize:"14px",fontWeight:500,cursor:"pointer"},children:"Try again"})]}):this.props.children}}const lt=U.createLogger("useAutoExport");function Oe(t){const{config:e,historyManager:r,artboards:a,elements:n,onExport:l,onExportScheduled:s,isCanvasReady:c=!1}=t,d=o.useRef(null),b=o.useRef(!1),S=o.useRef({totalExports:0,skippedExports:0,lastExportTime:null,avgExportInterval:0}),[L,k]=o.useState(S.current),y=o.useRef(l);y.current=l,o.useEffect(()=>{const h=new I.AutoExportManager({...I.DEFAULT_AUTO_EXPORT_CONFIG,...e});return h.onExport(async()=>{try{await y.current(),S.current=h.getStats()}catch(R){lt.error("[useAutoExport] Export failed:",R)}}),d.current=h,()=>{h.destroy()}},[]),o.useEffect(()=>{c&&b.current&&d.current&&(b.current=!1,d.current.forceExport())},[c]),o.useEffect(()=>{d.current&&e&&d.current.updateConfig(e)},[e==null?void 0:e.enabled,e==null?void 0:e.debounceMs,e==null?void 0:e.maxWaitMs]),o.useEffect(()=>{d.current&&s&&d.current.onExportScheduled(s)},[s]),o.useEffect(()=>{if(!r||!d.current)return;const h=r.onCommandExecuted(()=>{if(d.current){if(!c){b.current=!0;return}d.current.scheduleExport()}});return()=>{h()}},[r,c]),o.useEffect(()=>{if(d.current){if(!c){b.current=!0;return}d.current.scheduleExport()}},[n,a,c]),o.useEffect(()=>U.subscribeToImageLoads(R=>{if(!d.current||!c)return;n.some(N=>N.id===R)&&d.current.scheduleExport()}),[n,c]);const T=o.useCallback(h=>{d.current&&d.current.updateConfig(h)},[]),w=o.useCallback(async()=>{d.current&&(await d.current.forceExport(),S.current=d.current.getStats(),k(S.current))},[]),P=o.useCallback(()=>{d.current&&(d.current.resetStats(),S.current=d.current.getStats(),k(S.current))},[]),A=o.useCallback(()=>{d.current&&d.current.resetChangeDetection()},[]);return{stats:L,updateConfig:T,forceExport:w,resetStats:P,resetChangeDetection:A}}const Ye=U.createLogger("useContentReady");function dt(t){var r;const e=new Set;for(const a of t)if("fontFamily"in a&&typeof a.fontFamily=="string"&&a.fontFamily&&e.add(a.fontFamily),"getRichText"in a&&typeof a.getRichText=="function")try{const n=a.getRichText();if(n!=null&&n.spans)for(const l of n.spans)(r=l.style)!=null&&r.fontFamily&&e.add(l.style.fontFamily)}catch{}return Array.from(e)}async function ut(t){if(t.length===0||typeof document>"u"||!document.fonts)return;const e=[];for(const r of t){const a=`16px "${r}"`;e.push(document.fonts.load(a))}await Promise.all(e),document.fonts.ready&&await Promise.race([document.fonts.ready,new Promise(r=>setTimeout(r,250))]),Ye.debug(`Awaited ${e.length} font(s)`)}function ft(){return new Promise(t=>requestAnimationFrame(()=>t()))}function $e(t){const{isCanvasReady:e,elements:r,hasInitialElements:a,initialElementsLoaded:n}=t,[l,s]=o.useState(!1),c=o.useRef(!1);return o.useEffect(()=>{if(l||!e||a&&!n||a&&r.length===0||c.current)return;c.current=!0;let d=!1;return(async()=>{try{const b=dt(r);if(b.length>0&&(await ut(b),d)||(await ft(),d))return;s(!0)}catch(b){Ye.error("Error during content readiness check:",b),d||s(!0)}finally{c.current=!1}})(),()=>{d=!0,c.current=!1}},[e,r,a,n,l]),l}const mt={"far-top":"t",top:"t",center:"c",bottom:"b","far-bottom":"b","far-left":"l",left:"l",right:"r","far-right":"r",tl:"tl",t:"t",tr:"tr",l:"l",c:"c",r:"r",bl:"bl",b:"b",br:"br"};function ht(t){return t&&mt[t]||"c"}function gt(t,e){const r=e.scaleMode||"cover",a=(r==="contain"?e.marginTop:0)||0,n=(r==="contain"?e.marginRight:0)||0,l=(r==="contain"?e.marginBottom:0)||0,s=(r==="contain"?e.marginLeft:0)||0,c=e.width-s-n,d=e.height-a-l,b=c/t.width,S=d/t.height,L=r==="contain"?Math.min(b,S):Math.max(b,S),k=e.scale||1,y=L*k,T=t.width*y,w=t.height*y,P=e.align||"c";let A=0,h=0;const R=T-c,x=w-d;switch(P){case"tl":A=0,h=0;break;case"t":A=-R/2,h=0;break;case"tr":A=-R,h=0;break;case"l":A=0,h=-x/2;break;case"c":default:A=-R/2,h=-x/2;break;case"r":A=-R,h=-x/2;break;case"bl":A=0,h=-x;break;case"b":A=-R/2,h=-x;break;case"br":A=-R,h=-x;break}A+=s,h+=a;const N=e.offsetX||0,F=e.offsetY||0,Y=R,_=x;return A+=N*Y,h+=F*_,A=Math.round(A),h=Math.round(h),{scale:y,width:Math.round(T),height:Math.round(w),x:A,y:h,debug:{coverScale:L,userScale:k,overflow:{x:R,y:x},alignment:P,appliedOffset:{x:N*Y,y:F*_}}}}const pt={timeout:3e4,retries:1,retryDelay:1e3,validateUrl:!0};function bt(t){try{const e=new URL(t);return["http:","https:","data:","blob:"].includes(e.protocol)}catch{return!1}}async function Et(t,e={}){const r={...pt,...e};if(r.validateUrl&&!bt(t))return{success:!1,error:new Error(`Invalid image URL: ${t}`)};let a;for(let n=0;n<=r.retries;n++)try{return await xt(t,r.timeout)}catch(l){a=l instanceof Error?l:new Error(String(l)),n<r.retries&&await wt(r.retryDelay)}return{success:!1,error:a||new Error("Failed to load image after retries")}}function xt(t,e){return new Promise(r=>{const a=new Image;a.crossOrigin="anonymous";let n=null,l=!1;const s=()=>{n&&(clearTimeout(n),n=null)},c=()=>{if(l)return;l=!0,s();const b=a.naturalWidth||a.width,S=a.naturalHeight||a.height;r({success:!0,element:a,width:b,height:S,aspectRatio:S>0?b/S:1})},d=b=>{l||(l=!0,s(),r({success:!1,error:new Error(b)}))};a.onload=c,a.onerror=()=>{d(`Failed to load image: ${t}`)},n=setTimeout(()=>{d(`Image load timed out after ${e}ms: ${t}`)},e),a.src=t})}async function vt(t,e,r={},a={}){var k,y,T,w,P,A,h;const n=await Et(t,a);if(!n.success||!n.width||!n.height)return{success:!1,error:n.error||new Error("Failed to get image dimensions")};const l={width:e.artboard.width,height:e.artboard.height,scale:e.scale,align:ht(e.alignment),offsetX:e.offsetX,offsetY:e.offsetY,scaleMode:e.scaleMode,marginTop:e.marginTop,marginRight:e.marginRight,marginBottom:e.marginBottom,marginLeft:e.marginLeft},s=gt({width:n.width,height:n.height},l),c=e.artboard.x+s.x,d=e.artboard.y+s.y,b=c+s.width/2,S=d+s.height/2;return{success:!0,element:new U.ImageElement({...r,x:b,y:S,imageUrl:t,imageAspectRatio:n.aspectRatio,preserveDimensions:!0,transformData:{type:"image",width:s.width,height:s.height,cropX:((k=r.transformData)==null?void 0:k.cropX)??0,cropY:((y=r.transformData)==null?void 0:y.cropY)??0,cropWidth:((T=r.transformData)==null?void 0:T.cropWidth)??1,cropHeight:((w=r.transformData)==null?void 0:w.cropHeight)??1,flipHorizontal:((P=r.transformData)==null?void 0:P.flipHorizontal)??!1,flipVertical:((A=r.transformData)==null?void 0:A.flipVertical)??!1,borderRadius:((h=r.transformData)==null?void 0:h.borderRadius)??0}}),placement:{artworkSize:{width:n.width,height:n.height},finalSize:{width:s.width,height:s.height},position:{x:b,y:S},scale:s.scale}}}function wt(t){return new Promise(e=>setTimeout(e,t))}const K=U.createLogger("SnowconeCanvas");function xe(t,e,r,a){if(a<=0||!isFinite(a))return r;const n=t*r,l=e*r;if(Math.max(n,l)<=a)return r;const c=Math.max(t,e),d=a/c;return Math.min(d,r)}const Xe=o.forwardRef((t,e)=>{const{exportConfig:r,imageConfig:a,layoutConfig:n,kit:l,artboards:s,activeArtboard:c,onArtboardChange:d,initialElements:b,onChange:S,onSelectionChange:L,autoExportInterval:k,onExport:y,onExportScheduled:T,onExportStatus:w,onExportReady:P,onImageLoad:A,onImageError:h,onError:R,onReady:x,className:N,style:F,enableShortcuts:Y,overlay:_}=t,we=k,Z=t.autoExportConfig??(r==null?void 0:r.autoExportConfig),q=t.autoExportFormat??(r==null?void 0:r.format)??"dataUrl",ue=t.autoExportAll??(r==null?void 0:r.exportAll)??!1,X=t.exportScale??(r==null?void 0:r.scale)??2,j=t.maxExportSize??(r==null?void 0:r.maxSize)??4e3,ee=t.exportImageFormat??(r==null?void 0:r.imageFormat)??"png",te=t.exportImageQuality??(r==null?void 0:r.imageQuality)??.92,z=t.initialImage??(a==null?void 0:a.src),ye=t.initialImageAlignment??(a==null?void 0:a.alignment)??"center",Ae=t.initialImageScale??(a==null?void 0:a.scale)??1,Se=t.initialImageScaleMode??(a==null?void 0:a.scaleMode)??"cover",Ke=t.width??(n==null?void 0:n.width)??1200,qe=t.height??(n==null?void 0:n.height)??1200,Re=t.viewPadding??(n==null?void 0:n.viewPadding)??.9,Ce=t.artboardBorderRadius??(n==null?void 0:n.artboardBorderRadius)??0,Ie=t.fixedMargin??(n==null?void 0:n.fixedMargin),ke=t.maxHeight??(n==null?void 0:n.maxHeight),Me=t.showRotationHandle??(n==null?void 0:n.showRotationHandle)??!0,Ge=t.hideCanvas??(n==null?void 0:n.hideCanvas)??!1,Te=t.canvasWrapperClassName??(n==null?void 0:n.canvasWrapperClassName),Fe=t.canvasWrapperStyle??(n==null?void 0:n.canvasWrapperStyle),De=t.canvasCutouts??(n==null?void 0:n.canvasCutouts),fe=t.pieceGuides,Le=t.pieceFocus,Pe=Y??!0,re=o.useMemo(()=>I.resolveKit(l??"pro-studio"),[l]);o.useEffect(()=>{if(U.process$1.env.NODE_ENV==="development"&&re){const i=I.validateKit(re);i.valid||K.warn("Kit validation warnings:",i.errors),i.warnings.length>0&&K.warn("Kit validation warnings:",i.warnings)}},[re]);const ne=o.useRef(R);ne.current=R;const W=o.useCallback(i=>{var u;K.error(`[${i.category}] ${i.message}`,i.originalError),(u=ne.current)==null||u.call(ne,i)},[]);o.useEffect(()=>(le.CanvasRenderer.onRenderError=W,()=>{le.CanvasRenderer.onRenderError===W&&(le.CanvasRenderer.onRenderError=null)}),[W]);const{elements:V,setElements:ae,selectedId:He,artboardManager:M,refreshArtboards:Qe,historyManager:Ze,isCanvasReady:me,setCanvasReady:oe}=B.useEditor(),{createArtboard:Je,selectArtboard:_e,artboards:H}=I.useArtboards(),{exportArtboard:G,exportAllArtboards:se,exportArtboardAsBlob:Q,exportAllArtboardsAsBlobs:ie}=I.useExport();o.useImperativeHandle(e,()=>({exportArtboards:async(i={})=>{const u=i.format||q,g=i.scale||X,p=i.all??!1;let m=g;if(j>0&&isFinite(j))for(const v of H){const E=xe(v.width,v.height,g,j);m=Math.min(m,E)}const f={scale:m,format:ee,quality:te};if(p){const v=u==="blob"?await ie(f):await se(f),E={};for(const[D,O]of Object.entries(v)){const $=H.find(J=>J.id===D);$&&(E[$.name]=O)}return E}else{const v=M.getActiveArtboard();if(!v)throw new Error("[SnowconeCanvas] No active artboard found");if(u==="blob"){const E=await Q(v.id,f);return{[v.name]:E}}else{const E=await G(v.id,f);return{[v.name]:E}}}}}),[G,se,Q,ie,q,X,j,ee,te,M,H]),o.useEffect(()=>{P&&P(async()=>{const u=M.getActiveArtboard();if(!u)throw new Error("[SnowconeCanvas] No active artboard found");const g=xe(u.width,u.height,X,j),p=q==="blob"?await Q(u.id,{scale:g}):await G(u.id,{scale:g});return{[u.name]:p}})},[P,G,Q,M,q,X,j]);const[Be,ce]=o.useState("idle"),[Ne,he]=o.useState(null),ge=o.useRef(void 0),pe=o.useRef(!1),We=o.useRef(!1),[et,tt]=o.useState(!1),rt=o.useRef(0),nt=o.useRef(0),be=o.useRef(!1);o.useEffect(()=>{if(pe.current)return;pe.current=!0;const i=s||[{name:"Design",width:Ke,height:qe}],u=M.getAllArtboards();if(u.length>0){const g=i[0],p=u[0],m=fe?"transparent":void 0;p.name=g.name,p.width=g.width,p.height=g.height,p.clipShape=g.clipShape,g.backgroundColor?p.backgroundColor=g.backgroundColor:m&&(p.backgroundColor=m);for(let f=1;f<i.length;f++)Je(i[f].width,i[f].height,{name:i[f].name,backgroundColor:i[f].backgroundColor??m,clipShape:i[f].clipShape})}Qe()},[]),o.useEffect(()=>{if(!(!b||b.length===0)&&!We.current&&pe.current){We.current=!0,tt(!0);try{const i=le.ElementFactory.createManyFromJSON(b),u=M.getAllArtboards();if(u.length>0){const p=u[0];for(const m of i)M.addElementToArtboard(m.id,p.id)}ae(i);const g=new Set;for(const p of i){const m=p.fontFamily;typeof m=="string"&&m.trim().length>0&&g.add(m)}if(g.size>0&&typeof document<"u"){const p=document.fonts??null,m=Array.from(g).map(f=>{const v=`font-${f.replace(/\s+/g,"-").toLowerCase()}`;let E=document.getElementById(v);return E||(E=document.createElement("link"),E.id=v,E.rel="stylesheet",E.href=`https://fonts.googleapis.com/css2?family=${f.replace(/\s+/g,"+")}:wght@400;700&display=swap`,document.head.appendChild(E)),new Promise(D=>{if(E.sheet){D(f);return}const O=()=>D(f);E.addEventListener("load",O,{once:!0}),E.addEventListener("error",O,{once:!0}),setTimeout(O,4e3)})});Promise.all(m).then(f=>{if(p)return Promise.all(f.flatMap(v=>[p.load(`400 16px "${v}"`).catch(()=>{}),p.load(`700 16px "${v}"`).catch(()=>{})]))}).then(()=>{ae(f=>f.slice())})}}catch(i){K.error("Failed to load initial elements:",i);const u=i instanceof Error?i:new Error(String(i));W({category:"import",message:`Failed to load initial elements: ${u.message}`,originalError:u,recoverable:!0})}}},[b,M,ae,W]),o.useEffect(()=>{if(!c){be.current=!0;return}const i=H.find(u=>u.name===c);if(i){const u=M.getActiveArtboard();(u==null?void 0:u.name)!==c&&_e(i.id),be.current=!0}},[c,H]),o.useEffect(()=>{if(!z||ge.current===z)return;if(V.length>0){ge.current=z;return}ge.current=z,(async()=>{ce("loading"),he(null);try{const u=M.getAllArtboards();if(u.length===0)throw new Error("No artboards available");const g=[];for(const p of u){const m=s==null?void 0:s.find(E=>E.name===p.name),f=(m==null?void 0:m.scaleMode)||Se,v=await vt(z,{artboard:{width:p.width,height:p.height,x:p.x,y:p.y},alignment:(m==null?void 0:m.fitAlign)||ye,scale:Ae,scaleMode:f,marginTop:m==null?void 0:m.fitMarginTop,marginRight:m==null?void 0:m.fitMarginRight,marginBottom:m==null?void 0:m.fitMarginBottom,marginLeft:m==null?void 0:m.fitMarginLeft});v.success&&v.element?(M.addElementToArtboard(v.element.id,p.id),g.push(v.element)):K.error("Failed to load image for artboard:",p.name,v.error)}if(g.length>0)ae(p=>[...p,...g]),ce("success"),A==null||A(z);else{const p=new Error("Failed to load image into any artboard");ce("error"),he(p),h==null||h(z,p),W({category:"image",message:p.message,originalError:p,recoverable:!0})}}catch(u){const g=u instanceof Error?u:new Error(String(u));ce("error"),he(g),h==null||h(z,g),W({category:"image",message:g.message,originalError:g,recoverable:!0})}})()},[z,ye,Ae,Se]),o.useEffect(()=>{L==null||L(He)},[He,L]),o.useEffect(()=>{if(!S)return;const i=M.getActiveArtboard(),u={elements:V.map(g=>g.toJSON()),artboards:H.map(g=>({name:g.name,width:g.width,height:g.height,clipShape:g.clipShape,backgroundColor:g.backgroundColor})),activeArtboard:(i==null?void 0:i.name)||"Design"};S(u)},[V,H,S]),o.useEffect(()=>{if(!d||!be.current)return;const i=M.getActiveArtboard();i&&d(i.name)},[M.getActiveArtboardId()]);const at=o.useCallback(async()=>{if(!y&&!w)return;const i=++nt.current,u=Date.now();let g=X;if(j>0&&isFinite(j))for(const f of H){const v=xe(f.width,f.height,X,j);g=Math.min(g,v)}const p=M.getActiveArtboard(),m=(p==null?void 0:p.id)??"unknown";w==null||w({status:"rendering",artboardId:m}),rt.current=u;try{let f;const v={format:ee,scale:g,quality:te};if(q==="blob")if(ue){const E=await ie(v);f={};for(const[D,O]of Object.entries(E)){const $=H.find(J=>J.id===D);$&&(f[$.name]=O)}}else{const E=M.getActiveArtboard();if(!E)return;const D=await Q(E.id,v);f={[E.name]:D}}else if(ue){const E=await se(v);f={};for(const[D,O]of Object.entries(E)){const $=H.find(J=>J.id===D);$&&(f[$.name]=O)}}else{const E=M.getActiveArtboard();if(!E)return;const D=await G(E.id,v);f={[E.name]:D}}y==null||y(f),w==null||w({status:"complete",artboardId:m,result:f})}catch(f){const v=f instanceof Error?f.message:String(f);if(v.includes("Canvas ref not available"))return;const E=Date.now()-u;K.error("Export failed (export #"+i+"):",{duration:`${E}ms`,error:v});const D=f instanceof Error?f:new Error(v);w==null||w({status:"error",artboardId:m,error:D}),W({category:"export",message:D.message,originalError:D,artboardId:m,recoverable:!0})}},[y,w,ue,q,X,j,ee,te,G,se,Q,ie,H,M,V,Z,W]);o.useEffect(()=>{we!==void 0&&we>0&&K.warn("autoExportInterval is deprecated and ignored. Use autoExportConfig={{ enabled: true, debounceMs: 100, maxWaitMs: 1000 }} instead.")},[]),o.useEffect(()=>{},[V]);const ot=o.useCallback(()=>{if(T==null||T(),w){const i=M.getActiveArtboard();w({status:"scheduled",artboardId:(i==null?void 0:i.id)??"unknown"})}},[T,w,M]),st=!!b&&b.length>0,Ee=$e({isCanvasReady:me,elements:V,hasInitialElements:st,initialElementsLoaded:et}),je=o.useRef(!1);o.useEffect(()=>{Ee&&!je.current&&(je.current=!0,x==null||x())},[Ee,x]),Oe({config:Z?{enabled:Z.enabled??!0,debounceMs:Z.debounceMs??100,maxWaitMs:Z.maxWaitMs??1e3}:{enabled:!1},historyManager:Ze,artboards:H,elements:V,onExport:at,onExportScheduled:ot,isCanvasReady:Ee}),o.useEffect(()=>{if(H.length>0&&!me){const i=requestAnimationFrame(()=>{oe(!0)});return()=>cancelAnimationFrame(i)}},[H.length,me,oe]),o.useEffect(()=>()=>{oe(!1)},[oe]);const it=o.useCallback(i=>{W({category:"unknown",message:i.message,originalError:i,recoverable:!1})},[W]);return Be==="loading"?C.jsx("div",{className:N,style:{...F,display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"var(--surface, #f5f5f5)"},children:C.jsx(I.Spinner,{size:48})}):Be==="error"&&Ne?C.jsx("div",{className:N,style:{...F,display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"var(--surface, #f5f5f5)",color:"var(--danger, #dc2626)",padding:"1rem"},children:C.jsxs("div",{className:"text-center",children:[C.jsx("div",{className:"font-medium",children:"Failed to load image"}),C.jsx("div",{className:"text-sm mt-1 opacity-70",children:Ne.message})]})}):C.jsx(I.KitProvider,{kit:re,children:C.jsx("div",{className:`app-modern ${N||""}`,style:{...F,position:"relative"},children:C.jsxs(ze,{onError:it,fallback:(i,u)=>C.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"24px",minHeight:"200px",backgroundColor:"var(--surface, #f5f5f5)",color:"var(--foreground, #333)",fontFamily:"system-ui, sans-serif",textAlign:"center"},children:[C.jsx("div",{style:{fontSize:"18px",fontWeight:600,marginBottom:"8px"},children:"Something went wrong"}),C.jsx("div",{style:{fontSize:"14px",opacity:.7,marginBottom:"16px",maxWidth:"400px"},children:i.message}),C.jsx("button",{onClick:u,style:{padding:"8px 20px",backgroundColor:"var(--primary, #333)",color:"var(--primary-foreground, #fff)",border:"none",borderRadius:"6px",fontSize:"14px",fontWeight:500,cursor:"pointer"},children:"Try again"})]}),children:[!Ge&&(Te||Fe?C.jsx("div",{className:Te,style:Fe,children:C.jsx(I.Canvas,{style:{width:"100%"},fitPadding:Re,artboardBorderRadius:Ce,fixedMargin:Ie,maxHeight:ke,showRotationHandle:Me,enableShortcuts:Pe,canvasCutouts:De,pieceGuides:fe,pieceFocus:Le})}):C.jsx(I.Canvas,{style:{width:"100%"},fitPadding:Re,artboardBorderRadius:Ce,fixedMargin:Ie,maxHeight:ke,showRotationHandle:Me,enableShortcuts:Pe,canvasCutouts:De,pieceGuides:fe,pieceFocus:Le})),_]})})})});Xe.displayName="SnowconeCanvasInner";const ve=o.forwardRef((t,e)=>{var s;const{inheritTheme:r,externalProvider:a}=t,n=t.viewPadding??((s=t.layoutConfig)==null?void 0:s.viewPadding),l=C.jsx(Xe,{ref:e,...t});return a?C.jsx(B.ThemeProvider,{defaultTheme:"light",passive:r,children:l}):C.jsx(B.ThemeProvider,{defaultTheme:"light",passive:r,children:C.jsx(B.EditorProvider,{viewPadding:n,children:l})})});ve.displayName="SnowconeCanvas";const Ue=U.createLogger("useCommands");function yt(){const t=B.useEditor(),{undo:e,redo:r,canUndo:a,canRedo:n,executeElementUpdate:l,executeAddElement:s,executeRemoveElement:c,executeReorderElement:d,executeCommandBatch:b,executeCreateArtboard:S,executeDeleteArtboard:L,executeUpdateArtboard:k}=t,y=o.useCallback(()=>{Ue.warn("[useCommands] clearHistory is not yet implemented in EditorContext")},[]),T=o.useCallback(w=>{Ue.warn("[useCommands] clearArtboardHistory is not yet implemented in EditorContext")},[]);return{undo:e,redo:r,canUndo:a,canRedo:n,clearHistory:y,clearArtboardHistory:T,executeElementUpdate:l,executeAddElement:s,executeRemoveElement:c,executeReorderElement:d,executeCreateArtboard:S,executeDeleteArtboard:L,executeUpdateArtboard:k,executeCommandBatch:b}}function At(){const{selectedElement:t}=B.useEditor();return t??null}function St(){const{isCanvasReady:t}=B.useEditor();return t}const Rt=()=>{},Ct=[];function It(t){const{elementStore:e}=B.useElementsContext(),{executeElementUpdate:r}=B.useCommandContext(),a=o.useMemo(()=>e.getAllByName(t).filter(s=>s instanceof U.TextElement),[e,t]),n=o.useCallback(l=>{for(const s of a){const c=s.clone();c.setText(l),r(s,c)}},[a,r]);return a.length===0?{text:"",setText:Rt,element:null,elements:Ct,isConnected:!1}:{text:a[0].getText(),setText:n,element:a[0],elements:a,isConnected:!0}}const kt=()=>{},Mt=[];function Tt(t,e,r){const a=t/e;if(r>=a){const n=e,l=n*r,s=t/l;return{width:l,height:n,cropX:(1-s)/2,cropY:0,cropWidth:s,cropHeight:1}}else{const n=t,l=n/r,s=e/l;return{width:n,height:l,cropX:0,cropY:(1-s)/2,cropWidth:1,cropHeight:s}}}function Ft(t,e){const r=(e==null?void 0:e.fit)??"cover",{elementStore:a,setElements:n}=B.useElementsContext(),{executeElementUpdate:l}=B.useCommandContext(),s=o.useMemo(()=>a.getAllByName(t).filter(y=>y instanceof U.ImageElement),[a,t]),c=o.useRef(new Map);for(const k of s)if(!c.current.has(k.id)){const y=k.transformData;c.current.set(k.id,{width:y.width*y.cropWidth,height:y.height*y.cropHeight})}const d=o.useRef(s);d.current=s;const b=o.useRef(l);b.current=l;const S=o.useRef(n);S.current=n;const L=o.useCallback(k=>{const y=d.current,T=b.current;S.current;for(const w of y){const P=c.current.get(w.id);if(!P)continue;const{width:A,height:h}=P,R=w.clone();R.imageLoaded=!1,R.imageElement=null,R.isCropping=!1,R.imageUrl=k,r==="cover"?(R.preserveDimensions=!0,R.onLoadCallback=x=>{const N=x.imageAspectRatio||1,F=Tt(A,h,N);x.transformData.width=F.width,x.transformData.height=F.height,x.transformData.cropX=F.cropX,x.transformData.cropY=F.cropY,x.transformData.cropWidth=F.cropWidth,x.transformData.cropHeight=F.cropHeight,T(w,x)}):(R.preserveDimensions=!1,R.onLoadCallback=x=>{const N=x.transformData.width,F=x.transformData.height,Y=Math.min(A/N,h/F,1);Y<1&&(x.transformData.width=N*Y,x.transformData.height=F*Y),x.transformData.cropX=0,x.transformData.cropY=0,x.transformData.cropWidth=1,x.transformData.cropHeight=1,T(w,x)}),R.loadImage(k)}},[r]);return s.length===0?{imageUrl:"",setImageUrl:kt,element:null,elements:Mt,isConnected:!1}:{imageUrl:s[0].imageUrl,setImageUrl:L,element:s[0],elements:s,isConnected:!0}}function Dt(t){const{elementStore:e}=B.useElementsContext();return t?e.getByName(t)??null:null}function Lt(){const{zoom:t,panOffset:e,zoomIn:r,zoomOut:a,zoomToFit:n,resetView:l,setZoom:s,setPanOffset:c}=B.useViewportContext();return{zoom:t,panOffset:e,zoomIn:r,zoomOut:a,zoomToFit:n,resetView:l,setZoom:s,setPanOffset:c}}const Ve=1;function Pt(t){var a;const e=((a=t.artboards)==null?void 0:a[0])||{name:"Front",width:800,height:800},r=(t.elements||[]).map(Ht);return{schemaVersion:Ve,artboards:[{id:"artboard-1",name:e.name||"Front",x:0,y:0,width:e.width||800,height:e.height||800,backgroundColor:e.backgroundColor||"transparent",exportBackground:!1,elements:r,distressTexture:e.distressTexture,imageMask:e.imageMask}]}}function Ht(t){const e=t.type||t.transformType;return e==="image"?Bt(t):{...t,type:e}}function Bt(t){var b;const e=t.transformData||{},r=t.masks&&t.masks.length>0,a=e.width||0,n=e.height||0,l=r?a:a*(e.cropWidth??1),s=r?n:n*(e.cropHeight??1),c={id:t.id,type:"image",x:t.x||0,y:t.y||0,width:l,height:s,rotation:t.rotation||0,imageUrl:t.imageUrl,imageAspectRatio:t.imageAspectRatio};return!r&&(e.cropX!==0||e.cropY!==0||e.cropWidth!==1||e.cropHeight!==1)&&(c.cropX=e.cropX,c.cropY=e.cropY,c.cropWidth=e.cropWidth,c.cropHeight=e.cropHeight,c.needsCropPixelConversion=!0),e.flipHorizontal&&(c.flipHorizontal=e.flipHorizontal),e.flipVertical&&(c.flipVertical=e.flipVertical),e.borderRadius&&(c.borderRadius=e.borderRadius),t.opacity!==void 0&&(c.opacity=t.opacity),t.distressEffect&&(c.distressEffect=t.distressEffect),((b=t.masks)==null?void 0:b.length)>0&&(c.masks=t.masks),t.blendMode&&(c.blendMode=t.blendMode),t.knockoutParts&&(c.knockoutParts=t.knockoutParts),t.stroke&&(c.stroke=t.stroke),c}I.ensureIconsRegistered();exports.ALL_CAPABILITIES=I.ALL_CAPABILITIES;exports.COMPACT_CUSTOMIZER=I.COMPACT_CUSTOMIZER;exports.EMBED_ONLY=I.EMBED_ONLY;exports.MINIMAL_CAPABILITIES=I.MINIMAL_CAPABILITIES;exports.PRO_STUDIO=I.PRO_STUDIO;exports.createKit=I.createKit;exports.extendKit=I.extendKit;exports.resolveKit=I.resolveKit;exports.useArtboards=I.useArtboards;exports.useExport=I.useExport;exports.useLayers=I.useLayers;exports.validateKit=I.validateKit;exports.useEditor=B.useEditor;exports.deserializeState=de.deserializeState;exports.migrateState=de.migrateState;exports.serializeState=de.serializeState;exports.validateState=de.validateState;exports.CANVAS_STATE_SCHEMA_VERSION=Ve;exports.ErrorBoundary=ze;exports.SnowconeCanvas=ve;exports.default=ve;exports.serializeStateForServer=Pt;exports.useAutoExport=Oe;exports.useCanvasReady=St;exports.useCommands=yt;exports.useContentReady=$e;exports.useElementByName=Dt;exports.useImageBinding=Ft;exports.useSelectedElement=At;exports.useTextBinding=It;exports.useViewport=Lt;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|