@turnix-co/konva-editor 2.0.1 → 2.0.2
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 +197 -407
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,66 +1,31 @@
|
|
|
1
1
|
# @turnix-co/konva-editor
|
|
2
2
|
|
|
3
|
-
A powerful
|
|
3
|
+
A powerful multi-slide canvas editor built with React and Konva. Create interactive presentations, whiteboards, and educational content with drawing, media, and recording capabilities.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@turnix-co/konva-editor)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
|
-
## 📦 Installation
|
|
10
|
-
|
|
11
|
-
### For Team Members
|
|
12
|
-
|
|
13
|
-
**Step 1:** Install the package from GitHub Packages:
|
|
14
|
-
```bash
|
|
15
|
-
npm install @turnix-co/konva-editor --registry=https://npm.pkg.github.com --legacy-peer-deps
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Step 2:** Install peer dependencies:
|
|
19
9
|
```bash
|
|
20
|
-
npm install
|
|
10
|
+
npm install @turnix-co/konva-editor
|
|
21
11
|
```
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
### Setup Authentication
|
|
26
|
-
|
|
27
|
-
Contact your team lead for authentication credentials (.npmrc file).
|
|
28
|
-
|
|
29
|
-
## ⚠️ CRITICAL SETUP REQUIREMENTS
|
|
30
|
-
|
|
31
|
-
**Before you start**, understand these requirements or features WON'T work:
|
|
32
|
-
|
|
33
|
-
1. **Layout Container Required**: Components MUST be wrapped in a div with:
|
|
34
|
-
- `width: '100vw'` and `height: '100vh'`
|
|
35
|
-
- `display: 'flex'` and `flexDirection: 'column'`
|
|
36
|
-
- Canvas wrapper needs `flex: 1` and `position: 'relative'`
|
|
13
|
+
### Peer Dependencies
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
- Create a `stageRef` with `useRef<Konva.Stage | null>(null)`
|
|
40
|
-
- Pass it to both `<Toolbar stageRef={stageRef} />` and `<Canvas onStageReady={...} />`
|
|
15
|
+
Install the required peer dependencies:
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
❌ **Common Mistakes** (these will break video recording):
|
|
45
|
-
```tsx
|
|
46
|
-
// ❌ WRONG - No layout wrapper, no stageRef
|
|
47
|
-
<ReduxProvider store={store}>
|
|
48
|
-
<Toolbar />
|
|
49
|
-
<Canvas />
|
|
50
|
-
<SlideNavigation />
|
|
51
|
-
</ReduxProvider>
|
|
17
|
+
```bash
|
|
18
|
+
npm install konva react-konva react-konva-utils
|
|
52
19
|
```
|
|
53
20
|
|
|
54
|
-
|
|
21
|
+
> **Note:** Konva has platform-specific code (browser vs Node.js). Installing it separately ensures Next.js correctly resolves the browser version and avoids SSR errors.
|
|
55
22
|
|
|
56
|
-
##
|
|
23
|
+
## Quick Start
|
|
57
24
|
|
|
58
25
|
### 1. Import the CSS styles
|
|
59
26
|
|
|
60
27
|
**IMPORTANT:** You must import the package's CSS file for components to display properly.
|
|
61
28
|
|
|
62
|
-
In your root layout or main CSS file (e.g., `app/layout.tsx` or `app/globals.css`):
|
|
63
|
-
|
|
64
29
|
```tsx
|
|
65
30
|
// In your layout.tsx or _app.tsx
|
|
66
31
|
import '@turnix-co/konva-editor/styles.css';
|
|
@@ -71,11 +36,7 @@ Or in your CSS file:
|
|
|
71
36
|
@import '@turnix-co/konva-editor/styles.css';
|
|
72
37
|
```
|
|
73
38
|
|
|
74
|
-
### 2. Create your editor page
|
|
75
|
-
|
|
76
|
-
**IMPORTANT:** To enable IndexedDB persistence (auto-save), you must call the `useSlidesPersistence()` hook.
|
|
77
|
-
|
|
78
|
-
**CRITICAL FOR VIDEO RECORDING:** You MUST wrap components in a properly styled container for video recording to work correctly!
|
|
39
|
+
### 2. Create your editor page
|
|
79
40
|
|
|
80
41
|
```tsx
|
|
81
42
|
'use client';
|
|
@@ -94,7 +55,7 @@ import type Konva from 'konva';
|
|
|
94
55
|
|
|
95
56
|
// Wrapper component to load persisted slides
|
|
96
57
|
function PersistenceLoader({ children }: { children: React.ReactNode }) {
|
|
97
|
-
useSlidesPersistence(); //
|
|
58
|
+
useSlidesPersistence(); // Loads slides from IndexedDB on mount
|
|
98
59
|
return <>{children}</>;
|
|
99
60
|
}
|
|
100
61
|
|
|
@@ -104,17 +65,15 @@ export default function EditorPage() {
|
|
|
104
65
|
return (
|
|
105
66
|
<ReduxProvider store={store}>
|
|
106
67
|
<PersistenceLoader>
|
|
107
|
-
{/* CRITICAL: This wrapper is REQUIRED for video recording to work */}
|
|
108
68
|
<div style={{
|
|
109
69
|
width: '100vw',
|
|
110
70
|
height: '100vh',
|
|
111
71
|
display: 'flex',
|
|
112
72
|
flexDirection: 'column',
|
|
113
|
-
overflow: 'hidden'
|
|
73
|
+
overflow: 'hidden'
|
|
114
74
|
}}>
|
|
115
75
|
<Toolbar stageRef={stageRef} />
|
|
116
76
|
|
|
117
|
-
{/* CRITICAL: Canvas wrapper needs flex: 1 and position: relative */}
|
|
118
77
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
119
78
|
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
120
79
|
</div>
|
|
@@ -127,153 +86,70 @@ export default function EditorPage() {
|
|
|
127
86
|
}
|
|
128
87
|
```
|
|
129
88
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
- **
|
|
134
|
-
- **
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
If you want to publish slides to your backend:
|
|
149
|
-
|
|
150
|
-
```tsx
|
|
151
|
-
'use client';
|
|
152
|
-
|
|
153
|
-
import { useRef } from 'react';
|
|
154
|
-
import {
|
|
155
|
-
Canvas,
|
|
156
|
-
Toolbar,
|
|
157
|
-
SlideNavigation,
|
|
158
|
-
PublishButton,
|
|
159
|
-
ReduxProvider,
|
|
160
|
-
store,
|
|
161
|
-
useSlidesPersistence,
|
|
162
|
-
type PublishProgress,
|
|
163
|
-
type PublishResponse,
|
|
164
|
-
type Slide
|
|
165
|
-
} from '@turnix-co/konva-editor';
|
|
166
|
-
import '@turnix-co/konva-editor/styles.css';
|
|
167
|
-
import type Konva from 'konva';
|
|
168
|
-
|
|
169
|
-
// Wrapper component for persistence
|
|
170
|
-
function PersistenceLoader({ children }: { children: React.ReactNode }) {
|
|
171
|
-
useSlidesPersistence();
|
|
172
|
-
return <>{children}</>;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export default function EditorPage() {
|
|
176
|
-
const stageRef = useRef<Konva.Stage | null>(null);
|
|
177
|
-
|
|
178
|
-
// Your custom publish logic
|
|
179
|
-
const handlePublish = async (
|
|
180
|
-
slides: Slide[],
|
|
181
|
-
onProgress?: (progress: PublishProgress) => void
|
|
182
|
-
): Promise<PublishResponse> => {
|
|
183
|
-
// Update progress
|
|
184
|
-
onProgress?.({
|
|
185
|
-
current: 0,
|
|
186
|
-
total: slides.length,
|
|
187
|
-
status: 'Preparing slides...'
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Call your API endpoint
|
|
191
|
-
const response = await fetch('/api/publish', {
|
|
192
|
-
method: 'POST',
|
|
193
|
-
headers: { 'Content-Type': 'application/json' },
|
|
194
|
-
body: JSON.stringify({ slides })
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const data = await response.json();
|
|
198
|
-
|
|
199
|
-
onProgress?.({
|
|
200
|
-
current: slides.length,
|
|
201
|
-
total: slides.length,
|
|
202
|
-
status: 'Published successfully!'
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
success: true,
|
|
207
|
-
message: 'Slides published successfully!',
|
|
208
|
-
projectId: data.id,
|
|
209
|
-
url: data.url
|
|
210
|
-
};
|
|
211
|
-
};
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
- **Multi-slide Support** - Create presentations with up to 20 slides
|
|
92
|
+
- **Freehand Drawing** - Smooth pen and brush tools with customizable colors and sizes
|
|
93
|
+
- **Shapes** - Rectangle, circle, triangle, arrow, and line shapes
|
|
94
|
+
- **Text** - Rich text with formatting options
|
|
95
|
+
- **Images** - Drag & drop, resize, rotate with context menu
|
|
96
|
+
- **Videos** - Upload and playback with thumbnail previews
|
|
97
|
+
- **Flashcards** - Interactive flashcard elements
|
|
98
|
+
- **Photo Frames** - Decorative frames with annotation support
|
|
99
|
+
- **Quiz Elements** - Multiple choice, true/false, short answer, long answer, fill-in-the-blanks
|
|
100
|
+
- **Audio Recording** - Record voice notes for individual objects
|
|
101
|
+
- **Screen Recording** - Record entire canvas with audio
|
|
102
|
+
- **Undo/Redo** - Full history management
|
|
103
|
+
- **Auto-save** - Automatic persistence with IndexedDB + Redux
|
|
104
|
+
- **Export** - Export slides as images
|
|
105
|
+
|
|
106
|
+
## Components
|
|
212
107
|
|
|
213
|
-
|
|
214
|
-
<ReduxProvider store={store}>
|
|
215
|
-
<PersistenceLoader>
|
|
216
|
-
{/* CRITICAL: This wrapper is REQUIRED */}
|
|
217
|
-
<div style={{
|
|
218
|
-
width: '100vw',
|
|
219
|
-
height: '100vh',
|
|
220
|
-
display: 'flex',
|
|
221
|
-
flexDirection: 'column',
|
|
222
|
-
overflow: 'hidden'
|
|
223
|
-
}}>
|
|
224
|
-
<Toolbar stageRef={stageRef} />
|
|
108
|
+
### Core Components
|
|
225
109
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
110
|
+
| Component | Description |
|
|
111
|
+
|-----------|-------------|
|
|
112
|
+
| `Canvas` | Main canvas component with drawing, images, videos, and text |
|
|
113
|
+
| `Toolbar` | Toolbar with drawing tools, colors, and actions |
|
|
114
|
+
| `SlideNavigation` | Multi-slide navigation and management |
|
|
115
|
+
| `ScreenRecorder` | Screen recording functionality |
|
|
116
|
+
| `PublishButton` | Customizable publish button with progress tracking |
|
|
117
|
+
| `EditorRoot` | Root wrapper that scopes styles (optional) |
|
|
229
118
|
|
|
230
|
-
|
|
119
|
+
### Redux Setup
|
|
231
120
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
</PersistenceLoader>
|
|
239
|
-
</ReduxProvider>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
```
|
|
121
|
+
| Export | Description |
|
|
122
|
+
|--------|-------------|
|
|
123
|
+
| `ReduxProvider` | Redux provider (from react-redux) |
|
|
124
|
+
| `store` | Pre-configured Redux store |
|
|
125
|
+
| `useDispatch` | Typed dispatch hook |
|
|
126
|
+
| `useSelector` | Typed selector hook |
|
|
243
127
|
|
|
244
|
-
##
|
|
128
|
+
## Critical Setup Requirements
|
|
245
129
|
|
|
246
|
-
|
|
130
|
+
For all features to work correctly, ensure:
|
|
247
131
|
|
|
248
|
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
- **`<PublishButton />`** - Customizable publish button with progress tracking
|
|
132
|
+
1. **Layout Container**: Components MUST be wrapped in a div with:
|
|
133
|
+
- `width: '100vw'` and `height: '100vh'`
|
|
134
|
+
- `display: 'flex'` and `flexDirection: 'column'`
|
|
135
|
+
- Canvas wrapper needs `flex: 1` and `position: 'relative'`
|
|
253
136
|
|
|
254
|
-
|
|
137
|
+
2. **stageRef Required for Recording**: You MUST:
|
|
138
|
+
- Create a `stageRef` with `useRef<Konva.Stage | null>(null)`
|
|
139
|
+
- Pass it to both `<Toolbar stageRef={stageRef} />` and `<Canvas onStageReady={...} />`
|
|
255
140
|
|
|
256
|
-
|
|
257
|
-
- **`store`** - Pre-configured Redux store
|
|
141
|
+
3. **CSS Import Required**: Import the package's CSS in your root layout
|
|
258
142
|
|
|
259
|
-
##
|
|
143
|
+
## Recording Feature
|
|
260
144
|
|
|
261
|
-
The Toolbar
|
|
145
|
+
The Toolbar includes a built-in recording button. To enable it:
|
|
262
146
|
|
|
263
147
|
```tsx
|
|
264
148
|
'use client';
|
|
265
149
|
|
|
266
|
-
import {
|
|
267
|
-
import {
|
|
268
|
-
|
|
269
|
-
Toolbar,
|
|
270
|
-
SlideNavigation,
|
|
271
|
-
ReduxProvider,
|
|
272
|
-
store,
|
|
273
|
-
type CanvasProps,
|
|
274
|
-
type ToolbarProps
|
|
275
|
-
} from '@turnix-co/konva-editor';
|
|
276
|
-
import Konva from 'konva';
|
|
150
|
+
import { useRef } from 'react';
|
|
151
|
+
import { Canvas, Toolbar, SlideNavigation, ReduxProvider, store } from '@turnix-co/konva-editor';
|
|
152
|
+
import type Konva from 'konva';
|
|
277
153
|
|
|
278
154
|
export default function EditorPage() {
|
|
279
155
|
const stageRef = useRef<Konva.Stage | null>(null);
|
|
@@ -281,18 +157,10 @@ export default function EditorPage() {
|
|
|
281
157
|
return (
|
|
282
158
|
<ReduxProvider store={store}>
|
|
283
159
|
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
284
|
-
{/* Pass stageRef to Toolbar to enable recording */}
|
|
285
160
|
<Toolbar stageRef={stageRef} />
|
|
286
|
-
|
|
287
161
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
288
|
-
{
|
|
289
|
-
<Canvas
|
|
290
|
-
onStageReady={(ref) => {
|
|
291
|
-
stageRef.current = ref.current;
|
|
292
|
-
}}
|
|
293
|
-
/>
|
|
162
|
+
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
294
163
|
</div>
|
|
295
|
-
|
|
296
164
|
<SlideNavigation />
|
|
297
165
|
</div>
|
|
298
166
|
</ReduxProvider>
|
|
@@ -300,259 +168,181 @@ export default function EditorPage() {
|
|
|
300
168
|
}
|
|
301
169
|
```
|
|
302
170
|
|
|
303
|
-
|
|
304
|
-
1. Create a `stageRef` using `useRef<Konva.Stage | null>(null)`
|
|
305
|
-
2. Pass the `stageRef` to the `<Toolbar>` component
|
|
306
|
-
3. Use the `onStageReady` callback on `<Canvas>` to populate the stageRef
|
|
307
|
-
4. **IMPORTANT**: Wrap Canvas in a container with `flex: 1` and `position: 'relative'`
|
|
308
|
-
5. **IMPORTANT**: The parent container MUST have `width: '100vw'` and `height: '100vh'` for recorded videos to display in full screen
|
|
309
|
-
6. The recording button in the Toolbar will now work correctly
|
|
310
|
-
|
|
311
|
-
Alternatively, you can handle recording externally:
|
|
312
|
-
|
|
313
|
-
```tsx
|
|
314
|
-
'use client';
|
|
315
|
-
|
|
316
|
-
import { useState, useRef } from 'react';
|
|
317
|
-
import {
|
|
318
|
-
Canvas,
|
|
319
|
-
Toolbar,
|
|
320
|
-
ScreenRecorder,
|
|
321
|
-
ReduxProvider,
|
|
322
|
-
store
|
|
323
|
-
} from '@turnix-co/konva-editor';
|
|
324
|
-
import Konva from 'konva';
|
|
325
|
-
|
|
326
|
-
export default function EditorPage() {
|
|
327
|
-
const stageRef = useRef<Konva.Stage | null>(null);
|
|
328
|
-
const [showRecorder, setShowRecorder] = useState(false);
|
|
329
|
-
|
|
330
|
-
const handleRecordingComplete = (videoBlob: Blob, thumbnailDataUrl: string) => {
|
|
331
|
-
// Handle the recorded video
|
|
332
|
-
console.log('Recording completed', videoBlob);
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
return (
|
|
336
|
-
<ReduxProvider store={store}>
|
|
337
|
-
<div style={{ width: '100vw', height: '100vh' }}>
|
|
338
|
-
{/* Toolbar with custom recording handler */}
|
|
339
|
-
<Toolbar
|
|
340
|
-
onScreenRecord={() => setShowRecorder(true)}
|
|
341
|
-
/>
|
|
342
|
-
|
|
343
|
-
<Canvas
|
|
344
|
-
onStageReady={(ref) => {
|
|
345
|
-
stageRef.current = ref.current;
|
|
346
|
-
}}
|
|
347
|
-
/>
|
|
348
|
-
|
|
349
|
-
{/* External ScreenRecorder component */}
|
|
350
|
-
{showRecorder && (
|
|
351
|
-
<ScreenRecorder
|
|
352
|
-
onClose={() => setShowRecorder(false)}
|
|
353
|
-
stageRef={stageRef}
|
|
354
|
-
onRecordingComplete={handleRecordingComplete}
|
|
355
|
-
/>
|
|
356
|
-
)}
|
|
357
|
-
</div>
|
|
358
|
-
</ReduxProvider>
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
## 🎨 Features
|
|
364
|
-
|
|
365
|
-
- ✏️ **Drawing tools** - Pen and eraser with customizable colors and sizes
|
|
366
|
-
- 🖼️ **Image support** - Drag & drop, resize, rotate with context menu
|
|
367
|
-
- 🎥 **Video support** - Upload and playback with thumbnail previews
|
|
368
|
-
- 📝 **Text elements** - Add and edit text on canvas
|
|
369
|
-
- 🎙️ **Audio recording** - Record voice notes for individual objects
|
|
370
|
-
- 📹 **Screen recording** - Record entire canvas with audio
|
|
371
|
-
- 📊 **Multi-slide presentations** - Create up to 20 slides
|
|
372
|
-
- ↩️ **Undo/Redo** - Full history management
|
|
373
|
-
- 💾 **Auto-save** - Automatic persistence with IndexedDB + Redux
|
|
374
|
-
- 📤 **Export** - Export slides as images
|
|
375
|
-
- 🚀 **Publishing** - Customizable publish button with progress tracking
|
|
171
|
+
## Publishing
|
|
376
172
|
|
|
377
|
-
|
|
173
|
+
### Auto-save (Built-in)
|
|
378
174
|
|
|
379
|
-
|
|
175
|
+
The package includes automatic data persistence via IndexedDB + Redux:
|
|
176
|
+
- Slides are automatically saved to browser storage on every change
|
|
177
|
+
- Slides are automatically loaded when the page refreshes
|
|
178
|
+
- Works completely offline - no server required
|
|
380
179
|
|
|
381
|
-
|
|
382
|
-
import { useDispatch, addImage, setTool } from '@turnix/konva-editor';
|
|
383
|
-
|
|
384
|
-
function CustomControls() {
|
|
385
|
-
const dispatch = useDispatch();
|
|
386
|
-
|
|
387
|
-
const handleAddImage = () => {
|
|
388
|
-
dispatch(addImage({
|
|
389
|
-
id: 'custom-id',
|
|
390
|
-
src: 'https://example.com/image.jpg',
|
|
391
|
-
x: 100,
|
|
392
|
-
y: 100,
|
|
393
|
-
width: 200,
|
|
394
|
-
height: 200,
|
|
395
|
-
}));
|
|
396
|
-
};
|
|
180
|
+
### Custom Publishing
|
|
397
181
|
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### TypeScript Support
|
|
403
|
-
|
|
404
|
-
Full TypeScript support with exported types:
|
|
182
|
+
Add a publish button to save to your backend:
|
|
405
183
|
|
|
406
184
|
```tsx
|
|
407
|
-
import
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
```tsx
|
|
415
|
-
interface PublishButtonProps {
|
|
416
|
-
// Custom publish function (required)
|
|
417
|
-
onPublish?: (
|
|
418
|
-
slides: Slide[],
|
|
419
|
-
onProgress?: (progress: PublishProgress) => void
|
|
420
|
-
) => Promise<PublishResponse>;
|
|
421
|
-
|
|
422
|
-
// Custom button label (optional, default: "Publish Slides")
|
|
423
|
-
label?: string;
|
|
424
|
-
|
|
425
|
-
// Custom className for positioning/styling (optional)
|
|
426
|
-
className?: string;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
interface PublishProgress {
|
|
430
|
-
current: number; // Current progress (e.g., 5)
|
|
431
|
-
total: number; // Total items (e.g., 10)
|
|
432
|
-
status: string; // Status message (e.g., "Uploading...")
|
|
433
|
-
}
|
|
185
|
+
import {
|
|
186
|
+
PublishButton,
|
|
187
|
+
type PublishProgress,
|
|
188
|
+
type PublishResponse,
|
|
189
|
+
type Slide
|
|
190
|
+
} from '@turnix-co/konva-editor';
|
|
434
191
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
```
|
|
192
|
+
const handlePublish = async (
|
|
193
|
+
slides: Slide[],
|
|
194
|
+
onProgress?: (progress: PublishProgress) => void
|
|
195
|
+
): Promise<PublishResponse> => {
|
|
196
|
+
onProgress?.({ current: 0, total: slides.length, status: 'Preparing...' });
|
|
442
197
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
198
|
+
const response = await fetch('/api/publish', {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: { 'Content-Type': 'application/json' },
|
|
201
|
+
body: JSON.stringify({ slides })
|
|
202
|
+
});
|
|
446
203
|
|
|
447
|
-
|
|
448
|
-
const { slides } = await request.json();
|
|
204
|
+
const data = await response.json();
|
|
449
205
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
message: 'Published successfully!',
|
|
209
|
+
projectId: data.id,
|
|
210
|
+
url: data.url
|
|
211
|
+
};
|
|
212
|
+
};
|
|
457
213
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
url: `/projects/${project.id}`,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
214
|
+
// In your component:
|
|
215
|
+
<PublishButton onPublish={handlePublish} label="Save to Cloud" />
|
|
463
216
|
```
|
|
464
217
|
|
|
465
|
-
|
|
218
|
+
## Redux Actions
|
|
466
219
|
|
|
467
|
-
|
|
468
|
-
- `addImage(payload)` - Add image to canvas
|
|
469
|
-
- `addVideo(payload)` - Add video to canvas
|
|
470
|
-
- `addText(payload)` - Add text to canvas
|
|
471
|
-
- `clearCanvas()` - Clear current slide
|
|
472
|
-
- `undo()` - Undo last action
|
|
473
|
-
- `redo()` - Redo undone action
|
|
474
|
-
- `deleteElement(id)` - Delete element by ID
|
|
475
|
-
|
|
476
|
-
**Slide Actions:**
|
|
220
|
+
### Slide Management
|
|
477
221
|
- `addSlide()` - Create new slide
|
|
478
222
|
- `deleteSlide(id)` - Delete slide
|
|
479
223
|
- `setCurrentSlide(id)` - Switch to slide
|
|
480
224
|
- `duplicateSlide(id)` - Duplicate slide
|
|
225
|
+
- `reorderSlides(payload)` - Reorder slides
|
|
226
|
+
- `setBackgroundColor(color)` - Set slide background
|
|
227
|
+
- `loadSlides(slides)` - Load slides from data
|
|
228
|
+
|
|
229
|
+
### Elements
|
|
230
|
+
- `addText`, `updateText`, `deleteText`, `duplicateText`
|
|
231
|
+
- `addShape`, `updateShape`, `deleteShape`, `duplicateShape`
|
|
232
|
+
- `addImage`, `updateImage`, `deleteImage`, `duplicateImage`
|
|
233
|
+
- `addVideo`, `updateVideo`, `deleteVideo`, `duplicateVideo`
|
|
234
|
+
- `addFlashcard`, `updateFlashcard`, `deleteFlashcard`
|
|
235
|
+
- `addPhotoFrame`, `updatePhotoFrame`, `deletePhotoFrame`
|
|
236
|
+
- `addMultipleChoice`, `addTrueFalse`, `addShortAnswer`, `addLongAnswer`, `addFillInTheBlanks`
|
|
237
|
+
|
|
238
|
+
### Drawing
|
|
239
|
+
- `addLine`, `removeLine`, `updateLastLine`
|
|
240
|
+
- `setLines`, `deleteLineById`, `duplicateLine`
|
|
241
|
+
|
|
242
|
+
### Canvas Operations
|
|
243
|
+
- `clearCanvas()` - Clear current slide
|
|
244
|
+
- `undo()` - Undo last action
|
|
245
|
+
- `redo()` - Redo undone action
|
|
246
|
+
- `bringToFront(id)`, `sendToBack(id)` - Layer ordering
|
|
481
247
|
|
|
482
|
-
|
|
248
|
+
### Toolbar Actions
|
|
483
249
|
- `setTool(tool)` - Set active tool ('pen' | 'eraser' | 'text')
|
|
484
250
|
- `setPenColor(color)` - Set pen color
|
|
485
251
|
- `setStrokeWidth(width)` - Set stroke width
|
|
486
252
|
|
|
487
|
-
###
|
|
253
|
+
### Selectors
|
|
254
|
+
- `selectCurrentSlide` - Get current slide
|
|
255
|
+
- `selectAllSlides` - Get all slides
|
|
256
|
+
- `selectCurrentSlideId` - Get current slide ID
|
|
257
|
+
- `selectCanAddSlide` - Check if can add more slides
|
|
258
|
+
- `selectSlideById(id)` - Get slide by ID
|
|
488
259
|
|
|
489
|
-
|
|
490
|
-
- `exportSlideAsPDF(slideId)` - Export slide as PDF
|
|
260
|
+
## Utilities
|
|
491
261
|
|
|
492
|
-
|
|
262
|
+
### Export Slides
|
|
493
263
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
- **Konva** for canvas rendering
|
|
497
|
-
- **Redux Toolkit** for state management
|
|
498
|
-
- **IndexedDB** for persistence
|
|
499
|
-
- **TypeScript** for type safety
|
|
264
|
+
```tsx
|
|
265
|
+
import { exportSlideAsImage, exportSlideAsBlob, getSlideDataURL } from '@turnix-co/konva-editor';
|
|
500
266
|
|
|
501
|
-
|
|
267
|
+
// Export as downloadable image
|
|
268
|
+
await exportSlideAsImage(stageRef, { pixelRatio: 2 });
|
|
502
269
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
- Publish package code publicly
|
|
506
|
-
- Include in public repositories
|
|
270
|
+
// Get as Blob for upload
|
|
271
|
+
const blob = await exportSlideAsBlob(stageRef, { format: 'png' });
|
|
507
272
|
|
|
508
|
-
|
|
273
|
+
// Get as data URL
|
|
274
|
+
const dataUrl = await getSlideDataURL(stageRef);
|
|
275
|
+
```
|
|
509
276
|
|
|
510
|
-
|
|
277
|
+
### Line Simplification
|
|
511
278
|
|
|
512
|
-
|
|
279
|
+
Reduce payload size by simplifying freehand lines:
|
|
513
280
|
|
|
514
|
-
|
|
281
|
+
```tsx
|
|
282
|
+
import { simplifyLines, getCompressionStats } from '@turnix-co/konva-editor';
|
|
515
283
|
|
|
516
|
-
|
|
284
|
+
// Simplify all lines with tolerance (higher = more compression)
|
|
285
|
+
const simplified = simplifyLines(lines, 3.0);
|
|
517
286
|
|
|
518
|
-
|
|
287
|
+
// Get compression statistics
|
|
288
|
+
const stats = getCompressionStats(originalPoints, simplifiedPoints);
|
|
289
|
+
console.log(`Reduced by ${stats.reduction}`);
|
|
290
|
+
```
|
|
519
291
|
|
|
520
|
-
|
|
292
|
+
## TypeScript Support
|
|
521
293
|
|
|
522
|
-
|
|
523
|
-
```tsx
|
|
524
|
-
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
525
|
-
<Toolbar stageRef={stageRef} />
|
|
526
|
-
<div style={{ flex: 1, position: 'relative' }}>
|
|
527
|
-
<Canvas onStageReady={(ref) => stageRef.current = ref.current} />
|
|
528
|
-
</div>
|
|
529
|
-
</div>
|
|
530
|
-
```
|
|
294
|
+
Full TypeScript support with exported types:
|
|
531
295
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
296
|
+
```tsx
|
|
297
|
+
import type {
|
|
298
|
+
Slide,
|
|
299
|
+
Line,
|
|
300
|
+
TextElement,
|
|
301
|
+
Shape,
|
|
302
|
+
ImageElement,
|
|
303
|
+
VideoElement,
|
|
304
|
+
FlashcardElement,
|
|
305
|
+
PhotoFrameElement,
|
|
306
|
+
MultipleChoice,
|
|
307
|
+
TrueFalse,
|
|
308
|
+
ShortAnswer,
|
|
309
|
+
LongAnswer,
|
|
310
|
+
FillInTheBlanks,
|
|
311
|
+
CanvasProps,
|
|
312
|
+
ToolbarProps,
|
|
313
|
+
EditorRootProps,
|
|
314
|
+
ExportOptions,
|
|
315
|
+
PublishProgress,
|
|
316
|
+
PublishResponse,
|
|
317
|
+
RootState,
|
|
318
|
+
AppDispatch,
|
|
319
|
+
} from '@turnix-co/konva-editor';
|
|
320
|
+
```
|
|
536
321
|
|
|
537
|
-
|
|
538
|
-
```tsx
|
|
539
|
-
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
540
|
-
```
|
|
322
|
+
## Troubleshooting
|
|
541
323
|
|
|
542
|
-
|
|
324
|
+
### Recorded Video Not Displaying Full Screen
|
|
543
325
|
|
|
544
|
-
|
|
326
|
+
1. **Check Container Styling**: Ensure Canvas is wrapped in a container with `width: '100vw'` and `height: '100vh'`
|
|
327
|
+
2. **Verify stageRef**: Make sure `stageRef` is passed to both Toolbar and Canvas
|
|
328
|
+
3. **Use flex: 1**: Canvas container should use `flex: 1` instead of fixed dimensions
|
|
545
329
|
|
|
546
|
-
|
|
547
|
-
- **Add to Canvas** - Adds the video to the canvas (clears existing content)
|
|
548
|
-
- **Download** - Downloads the video file locally
|
|
549
|
-
- **Re-record** - Discards the recording and starts over
|
|
330
|
+
### Styles Not Applied
|
|
550
331
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
332
|
+
1. Make sure you imported `@turnix-co/konva-editor/styles.css`
|
|
333
|
+
2. Wrap components in `EditorRoot` if you have CSS conflicts
|
|
334
|
+
|
|
335
|
+
### SSR Errors
|
|
336
|
+
|
|
337
|
+
If you see SSR errors with Konva:
|
|
338
|
+
1. Make sure your component has `'use client'` directive
|
|
339
|
+
2. Install Konva peer dependencies separately: `npm install konva react-konva react-konva-utils`
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
UNLICENSED - Proprietary software by Turnix
|
|
555
344
|
|
|
556
|
-
|
|
345
|
+
## Support
|
|
557
346
|
|
|
558
|
-
|
|
347
|
+
For issues and feature requests, please visit:
|
|
348
|
+
https://github.com/turnix-co/schoopla-konva-editor/issues
|
package/package.json
CHANGED