@turnix-co/konva-editor 2.0.0
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 +558 -0
- package/dist/index.d.ts +635 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# @turnix-co/konva-editor
|
|
2
|
+
|
|
3
|
+
A powerful, private multi-slide canvas editor built with React, Konva, and Redux. Features drawing tools, media support, recording capabilities, and more.
|
|
4
|
+
|
|
5
|
+
## 🔒 Private Package
|
|
6
|
+
|
|
7
|
+
This package is **private** and only available to authorized Turnix team members via GitHub Packages.
|
|
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
|
+
```bash
|
|
20
|
+
npm install konva react-konva react-konva-utils
|
|
21
|
+
```
|
|
22
|
+
|
|
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)
|
|
55
|
+
|
|
56
|
+
## 🚀 Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Import the CSS styles
|
|
59
|
+
|
|
60
|
+
**IMPORTANT:** You must import the package's CSS file for components to display properly.
|
|
61
|
+
|
|
62
|
+
In your root layout or main CSS file (e.g., `app/layout.tsx` or `app/globals.css`):
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// In your layout.tsx or _app.tsx
|
|
66
|
+
import '@turnix-co/konva-editor/styles.css';
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or in your CSS file:
|
|
70
|
+
```css
|
|
71
|
+
@import '@turnix-co/konva-editor/styles.css';
|
|
72
|
+
```
|
|
73
|
+
|
|
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:
|
|
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
|
+
};
|
|
212
|
+
|
|
213
|
+
return (
|
|
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} />
|
|
225
|
+
|
|
226
|
+
<div style={{ flex: 1, position: 'relative' }}>
|
|
227
|
+
<Canvas onStageReady={(ref) => { stageRef.current = ref.current; }} />
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<SlideNavigation />
|
|
231
|
+
|
|
232
|
+
{/* Add PublishButton with your custom publish function */}
|
|
233
|
+
<PublishButton
|
|
234
|
+
onPublish={handlePublish}
|
|
235
|
+
label="Save to Cloud"
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
</PersistenceLoader>
|
|
239
|
+
</ReduxProvider>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 📚 Components
|
|
245
|
+
|
|
246
|
+
### Core Components
|
|
247
|
+
|
|
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
|
|
253
|
+
|
|
254
|
+
### Redux Setup
|
|
255
|
+
|
|
256
|
+
- **`ReduxProvider`** - Redux provider (from react-redux)
|
|
257
|
+
- **`store`** - Pre-configured Redux store
|
|
258
|
+
|
|
259
|
+
## 🎥 Using the Recording Feature
|
|
260
|
+
|
|
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:
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
'use client';
|
|
265
|
+
|
|
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';
|
|
277
|
+
|
|
278
|
+
export default function EditorPage() {
|
|
279
|
+
const stageRef = useRef<Konva.Stage | null>(null);
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<ReduxProvider store={store}>
|
|
283
|
+
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
284
|
+
{/* Pass stageRef to Toolbar to enable recording */}
|
|
285
|
+
<Toolbar stageRef={stageRef} />
|
|
286
|
+
|
|
287
|
+
<div style={{ flex: 1, position: 'relative' }}>
|
|
288
|
+
{/* Canvas provides the stageRef via onStageReady callback */}
|
|
289
|
+
<Canvas
|
|
290
|
+
onStageReady={(ref) => {
|
|
291
|
+
stageRef.current = ref.current;
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<SlideNavigation />
|
|
297
|
+
</div>
|
|
298
|
+
</ReduxProvider>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
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
|
|
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
|
|
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
|
+
}));
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return <button onClick={handleAddImage}>Add Image</button>;
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### TypeScript Support
|
|
403
|
+
|
|
404
|
+
Full TypeScript support with exported types:
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import type { RootState, ImageElement, CanvasState } from '@turnix/konva-editor';
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## 📖 API Reference
|
|
411
|
+
|
|
412
|
+
### PublishButton Props
|
|
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
|
+
}
|
|
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`):
|
|
444
|
+
```tsx
|
|
445
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
446
|
+
|
|
447
|
+
export async function POST(request: NextRequest) {
|
|
448
|
+
const { slides } = await request.json();
|
|
449
|
+
|
|
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
|
+
});
|
|
457
|
+
|
|
458
|
+
return NextResponse.json({
|
|
459
|
+
id: project.id,
|
|
460
|
+
url: `/projects/${project.id}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
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
|
|
481
|
+
|
|
482
|
+
**Toolbar Actions:**
|
|
483
|
+
- `setTool(tool)` - Set active tool ('pen' | 'eraser' | 'text')
|
|
484
|
+
- `setPenColor(color)` - Set pen color
|
|
485
|
+
- `setStrokeWidth(width)` - Set stroke width
|
|
486
|
+
|
|
487
|
+
### Utilities
|
|
488
|
+
|
|
489
|
+
- `exportSlideAsImage(slideId)` - Export slide as PNG
|
|
490
|
+
- `exportSlideAsPDF(slideId)` - Export slide as PDF
|
|
491
|
+
|
|
492
|
+
## 🏗️ Architecture
|
|
493
|
+
|
|
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
|
|
500
|
+
|
|
501
|
+
## 🔐 Security
|
|
502
|
+
|
|
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.
|
|
511
|
+
|
|
512
|
+
## 🆘 Support
|
|
513
|
+
|
|
514
|
+
For issues or questions, contact the Turnix development team.
|
|
515
|
+
|
|
516
|
+
## 🐛 Troubleshooting
|
|
517
|
+
|
|
518
|
+
### Recorded Video Not Displaying Full Screen
|
|
519
|
+
|
|
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
|
+
```
|
|
531
|
+
|
|
532
|
+
2. **Verify stageRef is Passed**: Make sure you're passing the `stageRef` to the Toolbar component:
|
|
533
|
+
```tsx
|
|
534
|
+
<Toolbar stageRef={stageRef} />
|
|
535
|
+
```
|
|
536
|
+
|
|
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
|
+
```
|
|
541
|
+
|
|
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.
|
|
543
|
+
|
|
544
|
+
### Download and Re-record Buttons Not Visible
|
|
545
|
+
|
|
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
|
|
550
|
+
|
|
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
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
**Built with ❤️ by the Turnix team**
|