@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 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**