@turnix-co/konva-editor 2.0.25 → 2.0.28
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 +409 -265
- package/dist/index.d.ts +106 -41
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,66 @@
|
|
|
1
1
|
# @turnix-co/konva-editor
|
|
2
2
|
|
|
3
|
-
A powerful multi-slide canvas editor built with React
|
|
3
|
+
A powerful, private multi-slide canvas editor built with React, Konva, and Redux. Features drawing tools, media support, recording capabilities, and more.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 🔒 Private Package
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This package is **private** and only available to authorized Turnix team members via GitHub Packages.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
npm install @turnix-co/konva-editor
|
|
11
|
-
```
|
|
9
|
+
## 📦 Installation
|
|
12
10
|
|
|
13
|
-
###
|
|
11
|
+
### For Team Members
|
|
14
12
|
|
|
15
|
-
Install the
|
|
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
|
+
```
|
|
16
17
|
|
|
18
|
+
**Step 2:** Install peer dependencies:
|
|
17
19
|
```bash
|
|
18
20
|
npm install konva react-konva react-konva-utils
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
**Why separate?** Konva has platform-specific code (browser vs Node.js). Installing it separately ensures Next.js correctly resolves the browser version and avoids SSR errors.
|
|
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'`
|
|
37
|
+
|
|
38
|
+
2. **stageRef Required**: For video recording, you MUST:
|
|
39
|
+
- Create a `stageRef` with `useRef<Konva.Stage | null>(null)`
|
|
40
|
+
- Pass it to both `<Toolbar stageRef={stageRef} />` and `<Canvas onStageReady={...} />`
|
|
41
|
+
|
|
42
|
+
3. **CSS Import Required**: Import the package's CSS in your root layout
|
|
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>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
✅ **Correct Setup** (see Quick Start below)
|
|
22
55
|
|
|
23
|
-
## Quick Start
|
|
56
|
+
## 🚀 Quick Start
|
|
24
57
|
|
|
25
58
|
### 1. Import the CSS styles
|
|
26
59
|
|
|
27
60
|
**IMPORTANT:** You must import the package's CSS file for components to display properly.
|
|
28
61
|
|
|
62
|
+
In your root layout or main CSS file (e.g., `app/layout.tsx` or `app/globals.css`):
|
|
63
|
+
|
|
29
64
|
```tsx
|
|
30
65
|
// In your layout.tsx or _app.tsx
|
|
31
66
|
import '@turnix-co/konva-editor/styles.css';
|
|
@@ -36,7 +71,81 @@ Or in your CSS file:
|
|
|
36
71
|
@import '@turnix-co/konva-editor/styles.css';
|
|
37
72
|
```
|
|
38
73
|
|
|
39
|
-
### 2. Create your editor page
|
|
74
|
+
### 2. Create your editor page with auto-save
|
|
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!
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
'use client';
|
|
82
|
+
|
|
83
|
+
import { useRef } from 'react';
|
|
84
|
+
import {
|
|
85
|
+
Canvas,
|
|
86
|
+
Toolbar,
|
|
87
|
+
SlideNavigation,
|
|
88
|
+
ReduxProvider,
|
|
89
|
+
store,
|
|
90
|
+
useSlidesPersistence
|
|
91
|
+
} from '@turnix-co/konva-editor';
|
|
92
|
+
import '@turnix-co/konva-editor/styles.css';
|
|
93
|
+
import type Konva from 'konva';
|
|
94
|
+
|
|
95
|
+
// Wrapper component to load persisted slides
|
|
96
|
+
function PersistenceLoader({ children }: { children: React.ReactNode }) {
|
|
97
|
+
useSlidesPersistence(); // ← This loads slides from IndexedDB on mount
|
|
98
|
+
return <>{children}</>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default function EditorPage() {
|
|
102
|
+
const stageRef = useRef<Konva.Stage | null>(null);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<ReduxProvider store={store}>
|
|
106
|
+
<PersistenceLoader>
|
|
107
|
+
{/* CRITICAL: This wrapper is REQUIRED for video recording to work */}
|
|
108
|
+
<div style={{
|
|
109
|
+
width: '100vw',
|
|
110
|
+
height: '100vh',
|
|
111
|
+
display: 'flex',
|
|
112
|
+
flexDirection: 'column',
|
|
113
|
+
overflow: 'hidden' // Prevents scrollbars
|
|
114
|
+
}}>
|
|
115
|
+
<Toolbar stageRef={stageRef} />
|
|
116
|
+
|
|
117
|
+
{/* CRITICAL: Canvas wrapper needs flex: 1 and position: relative */}
|
|
118
|
+
<div style={{ flex: 1, position: 'relative' }}>
|
|
119
|
+
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<SlideNavigation />
|
|
123
|
+
</div>
|
|
124
|
+
</PersistenceLoader>
|
|
125
|
+
</ReduxProvider>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Important:**
|
|
131
|
+
- Always add `'use client'` at the top!
|
|
132
|
+
- Call `useSlidesPersistence()` inside `ReduxProvider` to enable auto-save
|
|
133
|
+
- **MUST** have the wrapper div with `width: '100vw'` and `height: '100vh'`
|
|
134
|
+
- **MUST** pass `stageRef` to both Toolbar and Canvas for recording to work
|
|
135
|
+
|
|
136
|
+
### 3. Add Publishing (Optional)
|
|
137
|
+
|
|
138
|
+
The package includes **automatic data persistence via IndexedDB + Redux**:
|
|
139
|
+
- ✅ **Auto-save**: Slides are automatically saved to browser storage on every change
|
|
140
|
+
- ✅ **Auto-restore**: Slides are automatically loaded when the page refreshes
|
|
141
|
+
- ✅ **No backend needed**: Data persists in the user's browser
|
|
142
|
+
|
|
143
|
+
**How it works:**
|
|
144
|
+
1. `useSlidesPersistence()` hook loads slides from IndexedDB on mount
|
|
145
|
+
2. Redux middleware automatically saves changes to IndexedDB (500ms debounce)
|
|
146
|
+
3. Works completely offline - no server required!
|
|
147
|
+
|
|
148
|
+
If you want to publish slides to your backend:
|
|
40
149
|
|
|
41
150
|
```tsx
|
|
42
151
|
'use client';
|
|
@@ -46,144 +155,125 @@ import {
|
|
|
46
155
|
Canvas,
|
|
47
156
|
Toolbar,
|
|
48
157
|
SlideNavigation,
|
|
49
|
-
TopNavBar,
|
|
50
|
-
BottomToolbar,
|
|
51
158
|
PublishButton,
|
|
52
|
-
EditorRoot,
|
|
53
159
|
ReduxProvider,
|
|
54
160
|
store,
|
|
55
161
|
useSlidesPersistence,
|
|
56
|
-
type Slide,
|
|
57
162
|
type PublishProgress,
|
|
58
163
|
type PublishResponse,
|
|
164
|
+
type Slide
|
|
59
165
|
} from '@turnix-co/konva-editor';
|
|
60
166
|
import '@turnix-co/konva-editor/styles.css';
|
|
61
167
|
import type Konva from 'konva';
|
|
62
168
|
|
|
63
|
-
// Wrapper component
|
|
169
|
+
// Wrapper component for persistence
|
|
64
170
|
function PersistenceLoader({ children }: { children: React.ReactNode }) {
|
|
65
|
-
useSlidesPersistence();
|
|
171
|
+
useSlidesPersistence();
|
|
66
172
|
return <>{children}</>;
|
|
67
173
|
}
|
|
68
174
|
|
|
69
175
|
export default function EditorPage() {
|
|
70
176
|
const stageRef = useRef<Konva.Stage | null>(null);
|
|
71
177
|
|
|
178
|
+
// Your custom publish logic
|
|
72
179
|
const handlePublish = async (
|
|
73
180
|
slides: Slide[],
|
|
74
181
|
onProgress?: (progress: PublishProgress) => void
|
|
75
182
|
): Promise<PublishResponse> => {
|
|
76
|
-
//
|
|
77
|
-
|
|
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
|
+
};
|
|
78
211
|
};
|
|
79
212
|
|
|
80
213
|
return (
|
|
81
214
|
<ReduxProvider store={store}>
|
|
82
215
|
<PersistenceLoader>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>
|
|
92
|
-
{/* Header with title and publish button */}
|
|
93
|
-
<TopNavBar
|
|
94
|
-
title="My Lesson"
|
|
95
|
-
autoSaveMessage="Auto-saved just now"
|
|
96
|
-
onBack={() => window.history.back()}
|
|
97
|
-
onPublish={handlePublish}
|
|
98
|
-
/>
|
|
99
|
-
|
|
100
|
-
{/* Main toolbar */}
|
|
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
|
+
}}>
|
|
101
224
|
<Toolbar stageRef={stageRef} />
|
|
102
225
|
|
|
103
|
-
{/* Canvas area */}
|
|
104
226
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
105
227
|
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
106
|
-
|
|
107
|
-
{/* Bottom toolbar for pen size and colors (shows when drawing) */}
|
|
108
|
-
<BottomToolbar />
|
|
109
228
|
</div>
|
|
110
229
|
|
|
111
|
-
{/* Slide navigation */}
|
|
112
230
|
<SlideNavigation />
|
|
113
|
-
|
|
231
|
+
|
|
232
|
+
{/* Add PublishButton with your custom publish function */}
|
|
233
|
+
<PublishButton
|
|
234
|
+
onPublish={handlePublish}
|
|
235
|
+
label="Save to Cloud"
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
114
238
|
</PersistenceLoader>
|
|
115
239
|
</ReduxProvider>
|
|
116
240
|
);
|
|
117
241
|
}
|
|
118
242
|
```
|
|
119
243
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
- **Multi-slide Support** - Create presentations with up to 20 slides
|
|
123
|
-
- **Freehand Drawing** - Smooth pen and brush tools with customizable colors and sizes
|
|
124
|
-
- **Shapes** - Rectangle, circle, triangle, arrow, and line shapes
|
|
125
|
-
- **Text** - Rich text with formatting options
|
|
126
|
-
- **Images** - Drag & drop, resize, rotate with context menu
|
|
127
|
-
- **Videos** - Upload and playback with thumbnail previews
|
|
128
|
-
- **Flashcards** - Interactive flashcard elements
|
|
129
|
-
- **Photo Frames** - Decorative frames with annotation support
|
|
130
|
-
- **Quiz Elements** - Multiple choice, true/false, short answer, long answer, fill-in-the-blanks
|
|
131
|
-
- **Audio Recording** - Record voice notes for individual objects
|
|
132
|
-
- **Screen Recording** - Record entire canvas with audio
|
|
133
|
-
- **Undo/Redo** - Full history management
|
|
134
|
-
- **Auto-save** - Automatic persistence with IndexedDB + Redux
|
|
135
|
-
- **Export** - Export slides as images
|
|
136
|
-
|
|
137
|
-
## Components
|
|
244
|
+
## 📚 Components
|
|
138
245
|
|
|
139
246
|
### Core Components
|
|
140
247
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
| `TopNavBar` | Header with title, auto-save status, and action buttons |
|
|
147
|
-
| `BottomToolbar` | Pen size and color picker toolbar |
|
|
148
|
-
| `LayersPanel` | Layer management panel for reordering elements |
|
|
149
|
-
| `ScreenRecorder` | Screen recording functionality |
|
|
150
|
-
| `PublishButton` | Customizable publish button with progress tracking |
|
|
151
|
-
| `EditorRoot` | Root wrapper that scopes styles (required for styling) |
|
|
248
|
+
- **`<Canvas />`** - Main canvas component with drawing, images, videos, and text
|
|
249
|
+
- **`<Toolbar />`** - Toolbar with drawing tools, colors, and actions
|
|
250
|
+
- **`<SlideNavigation />`** - Multi-slide navigation and management
|
|
251
|
+
- **`<ScreenRecorder />`** - Screen recording functionality
|
|
252
|
+
- **`<PublishButton />`** - Customizable publish button with progress tracking
|
|
152
253
|
|
|
153
254
|
### Redux Setup
|
|
154
255
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
| `ReduxProvider` | Redux provider (from react-redux) |
|
|
158
|
-
| `store` | Pre-configured Redux store |
|
|
159
|
-
| `useDispatch` | Typed dispatch hook |
|
|
160
|
-
| `useSelector` | Typed selector hook |
|
|
256
|
+
- **`ReduxProvider`** - Redux provider (from react-redux)
|
|
257
|
+
- **`store`** - Pre-configured Redux store
|
|
161
258
|
|
|
162
|
-
##
|
|
259
|
+
## 🎥 Using the Recording Feature
|
|
163
260
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
1. **Layout Container**: Components MUST be wrapped in a div with:
|
|
167
|
-
- `width: '100vw'` and `height: '100vh'`
|
|
168
|
-
- `display: 'flex'` and `flexDirection: 'column'`
|
|
169
|
-
- Canvas wrapper needs `flex: 1` and `position: 'relative'`
|
|
170
|
-
|
|
171
|
-
2. **stageRef Required for Recording**: You MUST:
|
|
172
|
-
- Create a `stageRef` with `useRef<Konva.Stage | null>(null)`
|
|
173
|
-
- Pass it to both `<Toolbar stageRef={stageRef} />` and `<Canvas onStageReady={...} />`
|
|
174
|
-
|
|
175
|
-
3. **CSS Import Required**: Import the package's CSS in your root layout
|
|
176
|
-
|
|
177
|
-
## Recording Feature
|
|
178
|
-
|
|
179
|
-
The Toolbar includes a built-in recording button. To enable it:
|
|
261
|
+
The Toolbar component includes a built-in recording button. To enable it, you need to pass the stage reference from the Canvas component to the Toolbar:
|
|
180
262
|
|
|
181
263
|
```tsx
|
|
182
264
|
'use client';
|
|
183
265
|
|
|
184
|
-
import { useRef } from 'react';
|
|
185
|
-
import {
|
|
186
|
-
|
|
266
|
+
import { useState, useRef } from 'react';
|
|
267
|
+
import {
|
|
268
|
+
Canvas,
|
|
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';
|
|
187
277
|
|
|
188
278
|
export default function EditorPage() {
|
|
189
279
|
const stageRef = useRef<Konva.Stage | null>(null);
|
|
@@ -191,10 +281,18 @@ export default function EditorPage() {
|
|
|
191
281
|
return (
|
|
192
282
|
<ReduxProvider store={store}>
|
|
193
283
|
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
284
|
+
{/* Pass stageRef to Toolbar to enable recording */}
|
|
194
285
|
<Toolbar stageRef={stageRef} />
|
|
286
|
+
|
|
195
287
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
196
|
-
|
|
288
|
+
{/* Canvas provides the stageRef via onStageReady callback */}
|
|
289
|
+
<Canvas
|
|
290
|
+
onStageReady={(ref) => {
|
|
291
|
+
stageRef.current = ref.current;
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
197
294
|
</div>
|
|
295
|
+
|
|
198
296
|
<SlideNavigation />
|
|
199
297
|
</div>
|
|
200
298
|
</ReduxProvider>
|
|
@@ -202,213 +300,259 @@ export default function EditorPage() {
|
|
|
202
300
|
}
|
|
203
301
|
```
|
|
204
302
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
- Works completely offline - no server required
|
|
213
|
-
|
|
214
|
-
### Custom Publishing
|
|
303
|
+
**Key points:**
|
|
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
|
|
215
310
|
|
|
216
|
-
|
|
311
|
+
Alternatively, you can handle recording externally:
|
|
217
312
|
|
|
218
313
|
```tsx
|
|
314
|
+
'use client';
|
|
315
|
+
|
|
316
|
+
import { useState, useRef } from 'react';
|
|
219
317
|
import {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
318
|
+
Canvas,
|
|
319
|
+
Toolbar,
|
|
320
|
+
ScreenRecorder,
|
|
321
|
+
ReduxProvider,
|
|
322
|
+
store
|
|
224
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);
|
|
225
329
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
onProgress?.({ current: 0, total: slides.length, status: 'Preparing...' });
|
|
330
|
+
const handleRecordingComplete = (videoBlob: Blob, thumbnailDataUrl: string) => {
|
|
331
|
+
// Handle the recorded video
|
|
332
|
+
console.log('Recording completed', videoBlob);
|
|
333
|
+
};
|
|
231
334
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
```
|
|
237
362
|
|
|
238
|
-
|
|
363
|
+
## 🎨 Features
|
|
239
364
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
376
|
+
|
|
377
|
+
## 🔧 Advanced Usage
|
|
378
|
+
|
|
379
|
+
### Custom Actions
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
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
|
+
}));
|
|
245
396
|
};
|
|
246
|
-
};
|
|
247
397
|
|
|
248
|
-
|
|
249
|
-
|
|
398
|
+
return <button onClick={handleAddImage}>Add Image</button>;
|
|
399
|
+
}
|
|
250
400
|
```
|
|
251
401
|
|
|
252
|
-
|
|
402
|
+
### TypeScript Support
|
|
253
403
|
|
|
254
|
-
|
|
255
|
-
- `addSlide()` - Create new slide
|
|
256
|
-
- `deleteSlide(id)` - Delete slide
|
|
257
|
-
- `setCurrentSlide(id)` - Switch to slide
|
|
258
|
-
- `duplicateSlide(id)` - Duplicate slide
|
|
259
|
-
- `reorderSlides(payload)` - Reorder slides
|
|
260
|
-
- `setBackgroundColor(color)` - Set slide background
|
|
261
|
-
- `loadSlides(slides)` - Load slides from data
|
|
262
|
-
|
|
263
|
-
### Elements
|
|
264
|
-
- `addText`, `updateText`, `deleteText`, `duplicateText`
|
|
265
|
-
- `addShape`, `updateShape`, `deleteShape`, `duplicateShape`
|
|
266
|
-
- `addImage`, `updateImage`, `deleteImage`, `duplicateImage`
|
|
267
|
-
- `addVideo`, `updateVideo`, `deleteVideo`, `duplicateVideo`
|
|
268
|
-
- `addFlashcard`, `updateFlashcard`, `deleteFlashcard`
|
|
269
|
-
- `addPhotoFrame`, `updatePhotoFrame`, `deletePhotoFrame`
|
|
270
|
-
- `addMultipleChoice`, `addTrueFalse`, `addShortAnswer`, `addLongAnswer`, `addFillInTheBlanks`
|
|
271
|
-
|
|
272
|
-
### Drawing
|
|
273
|
-
- `addLine`, `removeLine`, `updateLastLine`
|
|
274
|
-
- `setLines`, `deleteLineById`, `duplicateLine`
|
|
275
|
-
|
|
276
|
-
### Canvas Operations
|
|
277
|
-
- `clearCanvas()` - Clear current slide
|
|
278
|
-
- `undo()` - Undo last action
|
|
279
|
-
- `redo()` - Redo undone action
|
|
280
|
-
- `bringToFront(id)`, `sendToBack(id)` - Layer ordering
|
|
404
|
+
Full TypeScript support with exported types:
|
|
281
405
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
406
|
+
```tsx
|
|
407
|
+
import type { RootState, ImageElement, CanvasState } from '@turnix/konva-editor';
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## 📖 API Reference
|
|
286
411
|
|
|
287
|
-
###
|
|
288
|
-
- `selectCurrentSlide` - Get current slide
|
|
289
|
-
- `selectAllSlides` - Get all slides
|
|
290
|
-
- `selectCurrentSlideId` - Get current slide ID
|
|
291
|
-
- `selectCanAddSlide` - Check if can add more slides
|
|
292
|
-
- `selectSlideById(id)` - Get slide by ID
|
|
412
|
+
### PublishButton Props
|
|
293
413
|
|
|
294
|
-
|
|
414
|
+
```tsx
|
|
415
|
+
interface PublishButtonProps {
|
|
416
|
+
// Custom publish function (required)
|
|
417
|
+
onPublish?: (
|
|
418
|
+
slides: Slide[],
|
|
419
|
+
onProgress?: (progress: PublishProgress) => void
|
|
420
|
+
) => Promise<PublishResponse>;
|
|
295
421
|
|
|
296
|
-
|
|
422
|
+
// Custom button label (optional, default: "Publish Slides")
|
|
423
|
+
label?: string;
|
|
297
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
|
+
}
|
|
434
|
+
|
|
435
|
+
interface PublishResponse {
|
|
436
|
+
success: boolean; // Whether publish succeeded
|
|
437
|
+
message: string; // Success/error message
|
|
438
|
+
projectId?: string; // Optional: ID from your backend
|
|
439
|
+
url?: string; // Optional: URL to published project
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Example API Endpoint** (`app/api/publish/route.ts`):
|
|
298
444
|
```tsx
|
|
299
|
-
import {
|
|
445
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
300
446
|
|
|
301
|
-
|
|
302
|
-
|
|
447
|
+
export async function POST(request: NextRequest) {
|
|
448
|
+
const { slides } = await request.json();
|
|
303
449
|
|
|
304
|
-
//
|
|
305
|
-
const
|
|
450
|
+
// Save to your database
|
|
451
|
+
const project = await db.project.create({
|
|
452
|
+
data: {
|
|
453
|
+
slides: JSON.stringify(slides),
|
|
454
|
+
createdAt: new Date(),
|
|
455
|
+
}
|
|
456
|
+
});
|
|
306
457
|
|
|
307
|
-
|
|
308
|
-
|
|
458
|
+
return NextResponse.json({
|
|
459
|
+
id: project.id,
|
|
460
|
+
url: `/projects/${project.id}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
309
463
|
```
|
|
310
464
|
|
|
311
|
-
###
|
|
465
|
+
### Redux Actions
|
|
466
|
+
|
|
467
|
+
**Canvas Actions:**
|
|
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:**
|
|
477
|
+
- `addSlide()` - Create new slide
|
|
478
|
+
- `deleteSlide(id)` - Delete slide
|
|
479
|
+
- `setCurrentSlide(id)` - Switch to slide
|
|
480
|
+
- `duplicateSlide(id)` - Duplicate slide
|
|
312
481
|
|
|
313
|
-
|
|
482
|
+
**Toolbar Actions:**
|
|
483
|
+
- `setTool(tool)` - Set active tool ('pen' | 'eraser' | 'text')
|
|
484
|
+
- `setPenColor(color)` - Set pen color
|
|
485
|
+
- `setStrokeWidth(width)` - Set stroke width
|
|
314
486
|
|
|
315
|
-
|
|
316
|
-
import { simplifyLines, getCompressionStats } from '@turnix-co/konva-editor';
|
|
487
|
+
### Utilities
|
|
317
488
|
|
|
318
|
-
|
|
319
|
-
|
|
489
|
+
- `exportSlideAsImage(slideId)` - Export slide as PNG
|
|
490
|
+
- `exportSlideAsPDF(slideId)` - Export slide as PDF
|
|
320
491
|
|
|
321
|
-
|
|
322
|
-
const stats = getCompressionStats(originalPoints, simplifiedPoints);
|
|
323
|
-
console.log(`Reduced by ${stats.reduction}`);
|
|
324
|
-
```
|
|
492
|
+
## 🏗️ Architecture
|
|
325
493
|
|
|
326
|
-
|
|
494
|
+
The package uses:
|
|
495
|
+
- **React 19** for UI
|
|
496
|
+
- **Konva** for canvas rendering
|
|
497
|
+
- **Redux Toolkit** for state management
|
|
498
|
+
- **IndexedDB** for persistence
|
|
499
|
+
- **TypeScript** for type safety
|
|
327
500
|
|
|
328
|
-
|
|
501
|
+
## 🔐 Security
|
|
329
502
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
FlashcardElement,
|
|
339
|
-
PhotoFrameElement,
|
|
340
|
-
MultipleChoice,
|
|
341
|
-
TrueFalse,
|
|
342
|
-
ShortAnswer,
|
|
343
|
-
LongAnswer,
|
|
344
|
-
FillInTheBlanks,
|
|
345
|
-
CanvasProps,
|
|
346
|
-
ToolbarProps,
|
|
347
|
-
EditorRootProps,
|
|
348
|
-
TopNavBarProps,
|
|
349
|
-
BottomToolbarProps,
|
|
350
|
-
ExportOptions,
|
|
351
|
-
PublishProgress,
|
|
352
|
-
PublishResponse,
|
|
353
|
-
RootState,
|
|
354
|
-
AppDispatch,
|
|
355
|
-
} from '@turnix-co/konva-editor';
|
|
356
|
-
```
|
|
503
|
+
⚠️ **This is a private package.** Do not:
|
|
504
|
+
- Share authentication credentials
|
|
505
|
+
- Publish package code publicly
|
|
506
|
+
- Include in public repositories
|
|
507
|
+
|
|
508
|
+
## 📝 License
|
|
509
|
+
|
|
510
|
+
UNLICENSED - Private and proprietary software for Turnix team use only.
|
|
357
511
|
|
|
358
|
-
##
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|------|------|---------|-------------|
|
|
364
|
-
| `title` | `string` | `'Untitled'` | Title displayed in the header |
|
|
365
|
-
| `autoSaveMessage` | `string` | `'Auto-saved just now'` | Auto-save status text |
|
|
366
|
-
| `onBack` | `() => void` | - | Callback when back button clicked |
|
|
367
|
-
| `onSettings` | `() => void` | - | Callback when settings button clicked |
|
|
368
|
-
| `onProfile` | `() => void` | - | Callback when profile button clicked |
|
|
369
|
-
| `onPublish` | `PublishButtonProps['onPublish']` | - | Publish function (shows publish button if provided) |
|
|
370
|
-
| `showBackButton` | `boolean` | `true` | Show/hide back button |
|
|
371
|
-
| `showSettingsButton` | `boolean` | `true` | Show/hide settings button |
|
|
372
|
-
| `showProfileButton` | `boolean` | `true` | Show/hide profile button |
|
|
373
|
-
| `showPublishButton` | `boolean` | `true` | Show/hide publish button |
|
|
374
|
-
| `leftContent` | `React.ReactNode` | - | Custom left side content |
|
|
375
|
-
| `rightContent` | `React.ReactNode` | - | Custom right side content |
|
|
376
|
-
| `className` | `string` | - | Additional CSS classes |
|
|
377
|
-
|
|
378
|
-
### BottomToolbar
|
|
379
|
-
|
|
380
|
-
| Prop | Type | Default | Description |
|
|
381
|
-
|------|------|---------|-------------|
|
|
382
|
-
| `colors` | `Array<{name: string, value: string}>` | Default colors | Custom color palette |
|
|
383
|
-
| `showSizeSlider` | `boolean` | `true` | Show/hide pen size slider |
|
|
384
|
-
| `showColorPicker` | `boolean` | `true` | Show/hide color picker |
|
|
385
|
-
| `showBackgroundPicker` | `boolean` | `true` | Show/hide background color picker |
|
|
386
|
-
| `className` | `string` | - | Additional CSS classes |
|
|
387
|
-
|
|
388
|
-
## Troubleshooting
|
|
512
|
+
## 🆘 Support
|
|
513
|
+
|
|
514
|
+
For issues or questions, contact the Turnix development team.
|
|
515
|
+
|
|
516
|
+
## 🐛 Troubleshooting
|
|
389
517
|
|
|
390
518
|
### Recorded Video Not Displaying Full Screen
|
|
391
519
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
520
|
+
If your recorded video doesn't display in full screen when played:
|
|
521
|
+
|
|
522
|
+
1. **Check Container Styling**: Ensure the Canvas is wrapped in a container that fills the viewport:
|
|
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
|
+
```
|
|
395
531
|
|
|
396
|
-
|
|
532
|
+
2. **Verify stageRef is Passed**: Make sure you're passing the `stageRef` to the Toolbar component:
|
|
533
|
+
```tsx
|
|
534
|
+
<Toolbar stageRef={stageRef} />
|
|
535
|
+
```
|
|
397
536
|
|
|
398
|
-
|
|
399
|
-
|
|
537
|
+
3. **Check onStageReady Callback**: Ensure the Canvas component's `onStageReady` callback is properly setting the stageRef:
|
|
538
|
+
```tsx
|
|
539
|
+
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
540
|
+
```
|
|
400
541
|
|
|
401
|
-
|
|
542
|
+
4. **No Fixed Dimensions**: Avoid setting fixed width/height on the Canvas container. Use `flex: 1` instead to allow it to fill available space.
|
|
402
543
|
|
|
403
|
-
|
|
404
|
-
1. Make sure your component has `'use client'` directive
|
|
405
|
-
2. Install Konva peer dependencies separately: `npm install konva react-konva react-konva-utils`
|
|
544
|
+
### Download and Re-record Buttons Not Visible
|
|
406
545
|
|
|
407
|
-
|
|
546
|
+
After recording stops, you should see a preview dialog with three buttons:
|
|
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
|
|
408
550
|
|
|
409
|
-
|
|
551
|
+
If you don't see these buttons, ensure you're using version `1.3.3` or later:
|
|
552
|
+
```bash
|
|
553
|
+
npm install @turnix-co/konva-editor@latest --registry=https://npm.pkg.github.com
|
|
554
|
+
```
|
|
410
555
|
|
|
411
|
-
|
|
556
|
+
---
|
|
412
557
|
|
|
413
|
-
|
|
414
|
-
https://github.com/turnix-co/schoopla-konva-editor/issues
|
|
558
|
+
**Built with ❤️ by the Turnix team**
|