labellife-design-tool 2.1.0 → 2.1.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 +338 -176
- package/dist/cjs/canvas/workspace.js +323 -4
- package/dist/cjs/canvas/workspace.js.map +1 -1
- package/dist/cjs/config.js +31 -4
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/index.js +838 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/model/store.js +37 -1
- package/dist/cjs/model/store.js.map +1 -1
- package/dist/cjs/side-panel/index.js +483 -2
- package/dist/cjs/side-panel/index.js.map +1 -1
- package/dist/cjs/side-panel/side-panel.js +17 -2
- package/dist/cjs/side-panel/side-panel.js.map +1 -1
- package/dist/cjs/toolbar/toolbar.js +17 -2
- package/dist/cjs/toolbar/toolbar.js.map +1 -1
- package/dist/esm/canvas/workspace.js +324 -5
- package/dist/esm/canvas/workspace.js.map +1 -1
- package/dist/esm/config.js +30 -5
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/index.js +836 -9
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model/store.js +37 -1
- package/dist/esm/model/store.js.map +1 -1
- package/dist/esm/side-panel/index.js +484 -4
- package/dist/esm/side-panel/index.js.map +1 -1
- package/dist/esm/side-panel/side-panel.js +17 -2
- package/dist/esm/side-panel/side-panel.js.map +1 -1
- package/dist/esm/toolbar/toolbar.js +17 -2
- package/dist/esm/toolbar/toolbar.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# labellife-design-tool
|
|
2
2
|
|
|
3
|
-
A fully featured, modular React design editor library for building label, print, and graphic design applications. Built on top of Konva.js for high-performance canvas rendering and MobX-State-Tree for reactive state management.
|
|
3
|
+
A fully featured, modular React design editor library for building label, print, and graphic design applications. Built on top of **Konva.js** for high-performance canvas rendering and **MobX-State-Tree** for reactive state management.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/labellife-design-tool)
|
|
6
6
|
[](LICENSE)
|
|
@@ -9,33 +9,39 @@ A fully featured, modular React design editor library for building label, print,
|
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
1. [Features](#features)
|
|
13
|
+
2. [Installation](#installation)
|
|
14
|
+
3. [Quick Start](#quick-start)
|
|
15
|
+
4. [Architecture Overview](#architecture-overview)
|
|
16
|
+
5. [Store API](#store-api)
|
|
17
|
+
6. [Element Types](#element-types)
|
|
18
|
+
7. [UI Components](#ui-components)
|
|
19
|
+
8. [Side Panels](#side-panels)
|
|
20
|
+
9. [Toolbar](#toolbar)
|
|
21
|
+
10. [Input Fields](#input-fields)
|
|
22
|
+
11. [Image Effects & Filters](#image-effects--filters)
|
|
23
|
+
12. [Image Masking](#image-masking)
|
|
24
|
+
13. [Export & Download](#export--download)
|
|
25
|
+
14. [Configuration](#configuration)
|
|
26
|
+
15. [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
27
|
+
16. [JSON Schema](#json-schema)
|
|
28
|
+
17. [Dependencies](#dependencies)
|
|
29
|
+
18. [Roadmap](#roadmap)
|
|
30
|
+
19. [License](#license)
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
36
|
+
### Core Editor
|
|
36
37
|
- **Canvas Editor** — Zoom, pan, drag-and-drop, multi-select, and keyboard shortcuts
|
|
37
38
|
- **Rich Element Types** — Images, text, shapes (60+ presets), lines, SVGs, and groups
|
|
38
39
|
- **Context-Sensitive Toolbar** — Adapts controls to the selected element type
|
|
40
|
+
- **Multi-Page Support** — Add, remove, reorder, and navigate between pages
|
|
41
|
+
- **Undo/Redo** — Full history stack with snapshot-based state management
|
|
42
|
+
- **Responsive Layout** — Flexible container system that adapts to any viewport
|
|
43
|
+
|
|
44
|
+
### Design Capabilities
|
|
39
45
|
- **Image Editing** — Crop, flip, border, corner radius, masks (circle, triangle, star, diamond, hexagon, pentagon, heart, cloud, cross), and visual effects (blur, brightness, grayscale, sepia, warm, cold)
|
|
40
46
|
- **Text Editing** — Inline editing, 200+ Google Fonts with auto-loading, font weight/style, text decoration, alignment, letter spacing, line height, stroke, background highlight, and curved text
|
|
41
47
|
- **Shapes Library** — 60+ vector shapes including basic geometry, arrows, decorative shapes, organic blobs, and custom SVG paths
|
|
@@ -43,13 +49,21 @@ A fully featured, modular React design editor library for building label, print,
|
|
|
43
49
|
- **Shadow System** — Configurable blur, offset, color, and opacity on any element
|
|
44
50
|
- **Layer Management** — Reorder, lock/unlock, show/hide, delete per element
|
|
45
51
|
- **Grouping** — Group and ungroup elements with preserved relative positioning
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
|
|
53
|
+
### Template & Input System
|
|
54
|
+
- **Input Fields** — Define placeholder elements (Text, Date, Integer, Image) that prompt end-users for values when a template is loaded
|
|
55
|
+
- **Input Field Popup** — Built-in sequential dialog that collects user input for each field, with full style customization
|
|
56
|
+
- **Required Fields** — Mark input fields as required to prevent skipping
|
|
57
|
+
- **Custom Dialog Support** — Replace the default popup with your own component
|
|
58
|
+
|
|
59
|
+
### Import / Export
|
|
48
60
|
- **Export** — PNG, JPEG, PDF (multi-page), and JSON serialization
|
|
49
61
|
- **Import** — Load designs from JSON, import images via drag-and-drop or file picker
|
|
50
|
-
|
|
62
|
+
|
|
63
|
+
### Extensibility
|
|
51
64
|
- **Pluggable Panels** — Extend the side panel with custom sections
|
|
52
|
-
- **
|
|
65
|
+
- **Internationalization** — Deep-merge translation system with dot-notation key resolution
|
|
66
|
+
- **Theming** — Light/dark theme support
|
|
53
67
|
- **Unit Conversion** — px, mm, cm, in, pt, pc with configurable DPI
|
|
54
68
|
|
|
55
69
|
---
|
|
@@ -73,6 +87,7 @@ npm install react react-dom @mui/material @mui/icons-material @emotion/react @em
|
|
|
73
87
|
## Quick Start
|
|
74
88
|
|
|
75
89
|
```jsx
|
|
90
|
+
import React, { useMemo } from 'react';
|
|
76
91
|
import { createStore } from 'labellife-design-tool/model/store';
|
|
77
92
|
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';
|
|
78
93
|
import { SidePanel } from 'labellife-design-tool/side-panel';
|
|
@@ -80,20 +95,24 @@ import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';
|
|
|
80
95
|
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';
|
|
81
96
|
import { Workspace } from 'labellife-design-tool/canvas/workspace';
|
|
82
97
|
|
|
83
|
-
const store = createStore();
|
|
84
|
-
store.addPage();
|
|
85
|
-
|
|
86
98
|
function App() {
|
|
99
|
+
const store = useMemo(() => {
|
|
100
|
+
const s = createStore();
|
|
101
|
+
s.setSize(800, 600);
|
|
102
|
+
s.addPage();
|
|
103
|
+
return s;
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
87
106
|
return (
|
|
88
107
|
<DesignContainer style={{ width: '100vw', height: '100vh' }}>
|
|
89
|
-
<Toolbar store={store} />
|
|
90
108
|
<SidePanelWrap>
|
|
91
109
|
<SidePanel store={store} />
|
|
92
|
-
<WorkspaceWrap>
|
|
93
|
-
<Workspace store={store} />
|
|
94
|
-
<ZoomButtons store={store} />
|
|
95
|
-
</WorkspaceWrap>
|
|
96
110
|
</SidePanelWrap>
|
|
111
|
+
<WorkspaceWrap>
|
|
112
|
+
<Toolbar store={store} />
|
|
113
|
+
<Workspace store={store} />
|
|
114
|
+
<ZoomButtons store={store} />
|
|
115
|
+
</WorkspaceWrap>
|
|
97
116
|
</DesignContainer>
|
|
98
117
|
);
|
|
99
118
|
}
|
|
@@ -104,22 +123,23 @@ function App() {
|
|
|
104
123
|
## Architecture Overview
|
|
105
124
|
|
|
106
125
|
```
|
|
107
|
-
|
|
108
|
-
│ DesignContainer
|
|
109
|
-
│
|
|
110
|
-
│ │
|
|
111
|
-
│
|
|
112
|
-
│ │
|
|
113
|
-
│ │
|
|
114
|
-
│ │
|
|
115
|
-
│ │
|
|
116
|
-
│ │
|
|
117
|
-
│ │
|
|
118
|
-
│ │
|
|
119
|
-
│ │
|
|
120
|
-
│ │
|
|
121
|
-
│
|
|
122
|
-
|
|
126
|
+
┌──────────────────────────────────────────────────────┐
|
|
127
|
+
│ DesignContainer │
|
|
128
|
+
│ ┌────────────┬─────────────────────────────────────┐│
|
|
129
|
+
│ │ SidePanel │ WorkspaceWrap ││
|
|
130
|
+
│ │ │ ┌────────────────────────────────┐ ││
|
|
131
|
+
│ │ • Text │ │ Toolbar (context-sensitive) │ ││
|
|
132
|
+
│ │ • Shapes │ ├────────────────────────────────┤ ││
|
|
133
|
+
│ │ • Upload │ │ Workspace (Konva Stage) │ ││
|
|
134
|
+
│ │ • Input │ │ │ ││
|
|
135
|
+
│ │ • Bg │ │ Canvas: zoom, pan, select │ ││
|
|
136
|
+
│ │ • Layers │ │ Elements rendered here │ ││
|
|
137
|
+
│ │ │ │ Input Fields Popup (optional) │ ││
|
|
138
|
+
│ │ │ │ │ ││
|
|
139
|
+
│ │ │ ├────────────────────────────────┤ ││
|
|
140
|
+
│ │ │ │ ZoomButtons │ ││
|
|
141
|
+
│ └────────────┴──┴────────────────────────────────┘ ││
|
|
142
|
+
└──────────────────────────────────────────────────────┘
|
|
123
143
|
```
|
|
124
144
|
|
|
125
145
|
**State management** uses MobX-State-Tree. The `store` is the single source of truth — all UI components observe it reactively.
|
|
@@ -139,7 +159,7 @@ const store = createStore();
|
|
|
139
159
|
### Canvas Size
|
|
140
160
|
|
|
141
161
|
```js
|
|
142
|
-
store.setSize(800, 600);
|
|
162
|
+
store.setSize(800, 600); // Set canvas dimensions in pixels
|
|
143
163
|
store.setUnit({ unit: 'mm', dpi: 300 }); // Set working unit and DPI
|
|
144
164
|
```
|
|
145
165
|
|
|
@@ -188,7 +208,7 @@ store.ungroupElements([groupId]); // Ungroup a group
|
|
|
188
208
|
|
|
189
209
|
// Find
|
|
190
210
|
store.getElementById(id); // Find element by ID across all pages
|
|
191
|
-
store.find(callback);
|
|
211
|
+
store.find(callback); // Find first element matching callback
|
|
192
212
|
```
|
|
193
213
|
|
|
194
214
|
### Element Actions
|
|
@@ -228,6 +248,8 @@ store.clear(); // Reset store to empty state
|
|
|
228
248
|
store.validate(json); // Returns array of validation errors
|
|
229
249
|
```
|
|
230
250
|
|
|
251
|
+
> **Note:** `loadJSON` automatically loads referenced Google Fonts and triggers the [Input Fields popup](#input-fields) if the template contains input field elements.
|
|
252
|
+
|
|
231
253
|
### Events
|
|
232
254
|
|
|
233
255
|
```js
|
|
@@ -245,8 +267,6 @@ store.addFont({ fontFamily: 'Custom', url: '...' });
|
|
|
245
267
|
store.removeFont('Custom');
|
|
246
268
|
```
|
|
247
269
|
|
|
248
|
-
> When loading a design via `loadJSON`, all fonts referenced in text elements are **automatically loaded** from Google Fonts.
|
|
249
|
-
|
|
250
270
|
---
|
|
251
271
|
|
|
252
272
|
## Element Types
|
|
@@ -330,7 +350,7 @@ store.removeFont('Custom');
|
|
|
330
350
|
| `selectable` | boolean | `true` | Can be selected |
|
|
331
351
|
| `removable` | boolean | `true` | Can be deleted |
|
|
332
352
|
| `name` | string | `''` | Display name (shown in layers) |
|
|
333
|
-
| `custom` | object | `{}` | Arbitrary custom data |
|
|
353
|
+
| `custom` | object | `{}` | Arbitrary custom data (preserved through serialization) |
|
|
334
354
|
|
|
335
355
|
### Shadow (All Elements)
|
|
336
356
|
|
|
@@ -356,8 +376,8 @@ import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-
|
|
|
356
376
|
| Component | Description |
|
|
357
377
|
|---|---|
|
|
358
378
|
| `DesignContainer` | Root layout container. Accepts `style` prop for dimensions. |
|
|
359
|
-
| `SidePanelWrap` |
|
|
360
|
-
| `WorkspaceWrap` | Flex container for
|
|
379
|
+
| `SidePanelWrap` | Wrapper for the side panel area. |
|
|
380
|
+
| `WorkspaceWrap` | Flex container for toolbar, canvas, and zoom controls. |
|
|
361
381
|
|
|
362
382
|
### Workspace
|
|
363
383
|
|
|
@@ -367,12 +387,19 @@ import { Workspace } from 'labellife-design-tool/canvas/workspace';
|
|
|
367
387
|
<Workspace store={store} />
|
|
368
388
|
```
|
|
369
389
|
|
|
390
|
+
| Prop | Type | Default | Description |
|
|
391
|
+
|---|---|---|---|
|
|
392
|
+
| `store` | Store | *required* | The MobX-State-Tree store instance |
|
|
393
|
+
| `showInputFieldsPopup` | boolean | `true` | Show the input fields popup when a template with input fields is loaded. Set to `false` to disable. |
|
|
394
|
+
| `components` | object | `{}` | Override slots (e.g. `{ PageControls: MyComponent }`) |
|
|
395
|
+
|
|
370
396
|
The workspace provides:
|
|
371
397
|
- **Infinite canvas** with zoom and pan (mouse wheel + drag)
|
|
372
398
|
- **Click selection** and **multi-select** (Shift+click)
|
|
373
399
|
- **Transform handles** for resize and rotation
|
|
374
400
|
- **Auto-fit** to container on mount
|
|
375
401
|
- **Background rendering** per page
|
|
402
|
+
- **Input fields popup** (automatically shown when a loaded template contains input fields)
|
|
376
403
|
|
|
377
404
|
### Zoom Buttons
|
|
378
405
|
|
|
@@ -406,13 +433,21 @@ import {
|
|
|
406
433
|
TextSection,
|
|
407
434
|
ElementsSection,
|
|
408
435
|
UploadSection,
|
|
436
|
+
InputFieldsSection,
|
|
409
437
|
BackgroundSection,
|
|
410
438
|
LayersSection,
|
|
411
439
|
} from 'labellife-design-tool/side-panel';
|
|
412
440
|
|
|
413
441
|
<SidePanel
|
|
414
442
|
store={store}
|
|
415
|
-
sections={[
|
|
443
|
+
sections={[
|
|
444
|
+
TextSection,
|
|
445
|
+
ElementsSection,
|
|
446
|
+
UploadSection,
|
|
447
|
+
InputFieldsSection,
|
|
448
|
+
BackgroundSection,
|
|
449
|
+
LayersSection,
|
|
450
|
+
]}
|
|
416
451
|
defaultSection="text"
|
|
417
452
|
/>
|
|
418
453
|
```
|
|
@@ -424,6 +459,7 @@ import {
|
|
|
424
459
|
| `TextSection` | Text | Add heading, subheading, and body text presets |
|
|
425
460
|
| `ElementsSection` | Elements | 60+ shapes and 6 line styles |
|
|
426
461
|
| `UploadSection` | Upload | Drag-and-drop or file picker for images |
|
|
462
|
+
| `InputFieldsSection` | Input Fields | Add placeholder input elements (Text, Date, Integer, Image) for template-based workflows |
|
|
427
463
|
| `BackgroundSection` | Background | Color picker with 22 preset colors |
|
|
428
464
|
| `LayersSection` | Layers | Layer list with visibility, lock, reorder, and delete |
|
|
429
465
|
|
|
@@ -447,7 +483,6 @@ const MyCustomSection = {
|
|
|
447
483
|
),
|
|
448
484
|
};
|
|
449
485
|
|
|
450
|
-
// Use it
|
|
451
486
|
<SidePanel store={store} sections={[MyCustomSection, TextSection, LayersSection]} />
|
|
452
487
|
```
|
|
453
488
|
|
|
@@ -480,23 +515,165 @@ The toolbar is **context-sensitive** — it automatically shows relevant control
|
|
|
480
515
|
| **Line** | Line color |
|
|
481
516
|
| **All** | Opacity, Move up/down, Lock/Unlock, Duplicate, Delete |
|
|
482
517
|
|
|
483
|
-
|
|
518
|
+
The toolbar includes built-in **Import** and **Download** buttons by default.
|
|
519
|
+
|
|
520
|
+
### Customizing Toolbar Buttons
|
|
484
521
|
|
|
485
522
|
```jsx
|
|
486
523
|
<Toolbar
|
|
487
524
|
store={store}
|
|
488
525
|
components={{
|
|
526
|
+
// Add custom action buttons
|
|
489
527
|
ActionControls: ({ store }) => (
|
|
490
|
-
|
|
491
|
-
{/* Custom Download button */}
|
|
492
|
-
<button onClick={() => store.saveAsImage()}>Export</button>
|
|
493
|
-
</>
|
|
528
|
+
<button onClick={() => store.saveAsImage()}>Export</button>
|
|
494
529
|
),
|
|
530
|
+
// Replace or hide the Import button
|
|
531
|
+
ImportButton: null, // Set to null to hide
|
|
532
|
+
// Replace or hide the Download button
|
|
533
|
+
DownloadButton: MyDownload, // Provide a custom component
|
|
495
534
|
}}
|
|
496
535
|
/>
|
|
497
536
|
```
|
|
498
537
|
|
|
499
|
-
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## Input Fields
|
|
541
|
+
|
|
542
|
+
Input fields allow template designers (admins) to define placeholder elements that prompt end-users for values when a template is loaded. This is useful for creating reusable templates where certain fields — such as a name, date, or quantity — need to be filled in by the user.
|
|
543
|
+
|
|
544
|
+
### Overview
|
|
545
|
+
|
|
546
|
+
The input fields system consists of three parts:
|
|
547
|
+
|
|
548
|
+
1. **Admin Panel** (`InputFieldsSection`) — A side panel where the admin adds input field elements to the template and configures their prompt text and whether they are required.
|
|
549
|
+
2. **Metadata** — Each input field element stores its configuration in the `custom` property, which is preserved through JSON serialization.
|
|
550
|
+
3. **User Popup** — When a template containing input fields is loaded via `store.loadJSON()`, a sequential dialog automatically prompts the user to fill in each field.
|
|
551
|
+
|
|
552
|
+
### Supported Input Types
|
|
553
|
+
|
|
554
|
+
| Type | Description | Input Control |
|
|
555
|
+
|---|---|---|
|
|
556
|
+
| `text` | Free-form text entry | Text input |
|
|
557
|
+
| `date` | Date value with configurable format | Date picker |
|
|
558
|
+
| `integer` | Whole number value | Number input (integers only) |
|
|
559
|
+
| `image` | Image upload placeholder | **Not yet implemented** — see [Roadmap](#roadmap) |
|
|
560
|
+
|
|
561
|
+
### Adding Input Fields (Admin Side)
|
|
562
|
+
|
|
563
|
+
Include `InputFieldsSection` in your side panel sections:
|
|
564
|
+
|
|
565
|
+
```jsx
|
|
566
|
+
import { InputFieldsSection } from 'labellife-design-tool/side-panel';
|
|
567
|
+
|
|
568
|
+
<SidePanel
|
|
569
|
+
store={store}
|
|
570
|
+
sections={[TextSection, ElementsSection, InputFieldsSection, LayersSection]}
|
|
571
|
+
/>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
For each input type, the admin can configure:
|
|
575
|
+
|
|
576
|
+
- **Prompt Text** — The message displayed to the end-user (e.g. "Enter your name", "Select the event date").
|
|
577
|
+
- **Required** — Whether the field must be filled in. If required, the Skip button is hidden and the Confirm button is disabled until a value is entered.
|
|
578
|
+
- **Date Format** *(date type only)* — Choose from `MM-DD-YYYY`, `Month Name-DD-YYYY`, `Month Name-DD`, `YYYY`, or `MM-DD`.
|
|
579
|
+
|
|
580
|
+
### Input Field Metadata
|
|
581
|
+
|
|
582
|
+
Each input field element stores the following in its `custom` property:
|
|
583
|
+
|
|
584
|
+
```json
|
|
585
|
+
{
|
|
586
|
+
"inputField": true,
|
|
587
|
+
"inputType": "text",
|
|
588
|
+
"placeholder": "Enter text",
|
|
589
|
+
"promptText": "Please enter your name",
|
|
590
|
+
"required": false
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
For date fields, an additional `dateFormat` property is included:
|
|
595
|
+
|
|
596
|
+
```json
|
|
597
|
+
{
|
|
598
|
+
"inputField": true,
|
|
599
|
+
"inputType": "date",
|
|
600
|
+
"placeholder": "Select date",
|
|
601
|
+
"promptText": "When is the event?",
|
|
602
|
+
"required": true,
|
|
603
|
+
"dateFormat": "MM-DD-YYYY"
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Controlling the Popup
|
|
608
|
+
|
|
609
|
+
The input fields popup is rendered inside the `<Workspace>` component and is **enabled by default**. When `store.loadJSON()` is called with a template that contains input field elements, the popup automatically appears.
|
|
610
|
+
|
|
611
|
+
```jsx
|
|
612
|
+
// Popup enabled (default) — dialog appears after loadJSON if input fields exist
|
|
613
|
+
<Workspace store={store} />
|
|
614
|
+
|
|
615
|
+
// Popup disabled — input fields retain their template placeholder values
|
|
616
|
+
<Workspace store={store} showInputFieldsPopup={false} />
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
When `showInputFieldsPopup` is set to `false`, all input fields are treated as if they were skipped — their original template values are preserved regardless of whether they are marked as required.
|
|
620
|
+
|
|
621
|
+
### Customizing the Popup Appearance
|
|
622
|
+
|
|
623
|
+
Use `setInputFieldsConfig` to override the default dialog styles:
|
|
624
|
+
|
|
625
|
+
```js
|
|
626
|
+
import { setInputFieldsConfig } from 'labellife-design-tool';
|
|
627
|
+
|
|
628
|
+
setInputFieldsConfig({
|
|
629
|
+
// Style overrides (applied via MUI sx prop)
|
|
630
|
+
dialogStyle: { borderRadius: 16 },
|
|
631
|
+
titleStyle: { color: '#1a1a1a', fontFamily: 'Georgia' },
|
|
632
|
+
promptStyle: { fontSize: 15 },
|
|
633
|
+
inputStyle: { borderRadius: 8 },
|
|
634
|
+
submitButtonStyle: { backgroundColor: '#4caf50', '&:hover': { backgroundColor: '#388e3c' } },
|
|
635
|
+
skipButtonStyle: { color: '#757575' },
|
|
636
|
+
|
|
637
|
+
// Button text overrides
|
|
638
|
+
submitButtonText: 'Apply',
|
|
639
|
+
skipButtonText: 'Leave as is',
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Providing a Custom Dialog Component
|
|
644
|
+
|
|
645
|
+
For complete control over the popup UI, provide a `CustomDialog` component:
|
|
646
|
+
|
|
647
|
+
```js
|
|
648
|
+
import { setInputFieldsConfig } from 'labellife-design-tool';
|
|
649
|
+
|
|
650
|
+
setInputFieldsConfig({
|
|
651
|
+
CustomDialog: ({ fields, currentIndex, onSubmit, onSkip, onComplete }) => {
|
|
652
|
+
// fields — array of input field elements
|
|
653
|
+
// currentIndex — index of the current field being displayed
|
|
654
|
+
// onSubmit(elementId, value) — call to set the value and advance
|
|
655
|
+
// onSkip(elementId) — call to skip a field and advance
|
|
656
|
+
// onComplete() — call when all fields are processed
|
|
657
|
+
return <MyCustomDialogUI />;
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Accessing Input Fields Programmatically
|
|
663
|
+
|
|
664
|
+
```js
|
|
665
|
+
// Get all input field elements across all pages
|
|
666
|
+
const fields = store.inputFields;
|
|
667
|
+
|
|
668
|
+
// Get pending fields awaiting user input
|
|
669
|
+
const pending = store._pendingInputFields;
|
|
670
|
+
|
|
671
|
+
// Manually trigger the input fields flow (called automatically by loadJSON)
|
|
672
|
+
store.requestInputFields();
|
|
673
|
+
|
|
674
|
+
// Clear all pending fields
|
|
675
|
+
store.clearPendingInputFields();
|
|
676
|
+
```
|
|
500
677
|
|
|
501
678
|
---
|
|
502
679
|
|
|
@@ -546,11 +723,8 @@ To remove a mask, click **Remove Mask** in the mask panel.
|
|
|
546
723
|
Programmatically:
|
|
547
724
|
|
|
548
725
|
```js
|
|
549
|
-
// Apply mask
|
|
550
|
-
element.set({ clipSrc: '
|
|
551
|
-
|
|
552
|
-
// Remove mask
|
|
553
|
-
element.set({ clipSrc: '' });
|
|
726
|
+
element.set({ clipSrc: 'circle' }); // Apply mask
|
|
727
|
+
element.set({ clipSrc: '' }); // Remove mask
|
|
554
728
|
```
|
|
555
729
|
|
|
556
730
|
---
|
|
@@ -560,172 +734,138 @@ element.set({ clipSrc: '' });
|
|
|
560
734
|
### Image Export
|
|
561
735
|
|
|
562
736
|
```js
|
|
563
|
-
// Export as data URL
|
|
564
737
|
const dataURL = await store.toDataURL({
|
|
565
738
|
mimeType: 'image/png', // 'image/png' or 'image/jpeg'
|
|
566
739
|
quality: 1, // 0–1 (JPEG quality)
|
|
567
740
|
pixelRatio: 2, // Resolution multiplier
|
|
568
741
|
});
|
|
569
742
|
|
|
570
|
-
// Export as Blob
|
|
571
743
|
const blob = await store.toBlob({ mimeType: 'image/png' });
|
|
572
744
|
|
|
573
|
-
// Direct download
|
|
574
745
|
await store.saveAsImage({ fileName: 'my-design.png' });
|
|
575
746
|
```
|
|
576
747
|
|
|
577
748
|
### PDF Export
|
|
578
749
|
|
|
579
750
|
```js
|
|
580
|
-
|
|
581
|
-
const pdfDataURL = await store.toPDFDataURL({
|
|
582
|
-
pixelRatio: 2,
|
|
583
|
-
dpi: 300,
|
|
584
|
-
});
|
|
751
|
+
const pdfDataURL = await store.toPDFDataURL({ pixelRatio: 2, dpi: 300 });
|
|
585
752
|
|
|
586
|
-
// Direct download
|
|
587
753
|
await store.saveAsPDF({ fileName: 'my-design.pdf' });
|
|
588
754
|
```
|
|
589
755
|
|
|
590
756
|
### JSON Export
|
|
591
757
|
|
|
592
758
|
```js
|
|
593
|
-
// Serialize
|
|
594
759
|
const json = store.toJSON();
|
|
595
|
-
|
|
596
|
-
// Download as file
|
|
597
760
|
store.saveAsJSON({ fileName: 'my-design.json' });
|
|
598
|
-
|
|
599
|
-
// Load back
|
|
600
761
|
store.loadJSON(json);
|
|
601
762
|
```
|
|
602
763
|
|
|
603
764
|
---
|
|
604
765
|
|
|
605
|
-
##
|
|
766
|
+
## Configuration
|
|
606
767
|
|
|
607
|
-
|
|
768
|
+
### Internationalization (i18n)
|
|
608
769
|
|
|
609
|
-
|
|
770
|
+
The library supports full internationalization via a nested translation system with dot-notation key resolution.
|
|
610
771
|
|
|
611
772
|
```js
|
|
612
|
-
import { setTranslations } from 'labellife-design-tool/config';
|
|
773
|
+
import { setTranslations, t } from 'labellife-design-tool/config';
|
|
613
774
|
|
|
614
775
|
setTranslations({
|
|
615
776
|
toolbar: {
|
|
616
777
|
flip: 'Spiegelen',
|
|
617
|
-
flipHorizontally: 'Horizontaal spiegelen',
|
|
618
|
-
flipVertically: 'Verticaal spiegelen',
|
|
619
778
|
effects: 'Effecten',
|
|
620
|
-
fitToBackground: 'Passend maken',
|
|
621
|
-
clip: 'Masker toepassen',
|
|
622
|
-
removeMask: 'Masker verwijderen',
|
|
623
|
-
crop: 'Bijsnijden',
|
|
624
|
-
fill: 'Vulkleur',
|
|
625
|
-
textStroke: 'Lijnkleur',
|
|
626
|
-
border: 'Rand',
|
|
627
|
-
cornerRadius: 'Hoekstraal',
|
|
628
|
-
color: 'Kleur',
|
|
629
|
-
opacity: 'Dekking',
|
|
630
|
-
up: 'Omhoog',
|
|
631
|
-
down: 'Omlaag',
|
|
632
|
-
lockedDescription: 'Vergrendelen',
|
|
633
|
-
unlockedDescription: 'Ontgrendelen',
|
|
634
|
-
duplicateElements: 'Dupliceren',
|
|
635
|
-
removeElements: 'Verwijderen',
|
|
636
779
|
download: 'Downloaden',
|
|
637
780
|
import: 'Importeren',
|
|
638
|
-
fileType: 'Bestandstype',
|
|
639
|
-
quality: 'Kwaliteit',
|
|
640
781
|
undo: 'Ongedaan maken',
|
|
641
782
|
redo: 'Opnieuw',
|
|
642
|
-
|
|
643
|
-
brightness: 'Helderheid',
|
|
644
|
-
shadow: 'Schaduw',
|
|
645
|
-
offsetX: 'Verschuiving X',
|
|
646
|
-
offsetY: 'Verschuiving Y',
|
|
783
|
+
// ... see full key reference below
|
|
647
784
|
},
|
|
648
785
|
sidePanel: {
|
|
649
786
|
text: 'Tekst',
|
|
650
787
|
elements: 'Elementen',
|
|
651
|
-
shapes: 'Vormen',
|
|
652
|
-
lines: 'Lijnen',
|
|
653
788
|
upload: 'Uploaden',
|
|
654
|
-
uploadTip: 'Sleep afbeeldingen hierheen of klik om te uploaden',
|
|
655
789
|
background: 'Achtergrond',
|
|
656
790
|
layers: 'Lagen',
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
791
|
+
inputFields: {
|
|
792
|
+
tab: 'Invoervelden',
|
|
793
|
+
title: 'Invoervelden',
|
|
794
|
+
required: 'Verplicht',
|
|
795
|
+
// ...
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
inputFieldsDialog: {
|
|
799
|
+
title: 'Invoer vereist',
|
|
800
|
+
submit: 'Bevestigen',
|
|
801
|
+
skip: 'Overslaan',
|
|
802
|
+
stepIndicator: 'Veld {current} van {total}',
|
|
661
803
|
},
|
|
662
804
|
});
|
|
663
|
-
```
|
|
664
805
|
|
|
665
|
-
Translations are
|
|
666
|
-
|
|
667
|
-
```js
|
|
668
|
-
// First call
|
|
806
|
+
// Translations are deep-merged — safe to call multiple times
|
|
669
807
|
setTranslations({ toolbar: { flip: 'Flip' } });
|
|
808
|
+
setTranslations({ toolbar: { effects: 'Effects' } }); // Does not overwrite toolbar.flip
|
|
670
809
|
|
|
671
|
-
//
|
|
672
|
-
|
|
810
|
+
// Use the translation function anywhere
|
|
811
|
+
t('toolbar.flip'); // Returns translated value
|
|
812
|
+
t('toolbar.flip', 'Flip'); // Returns default if key not found
|
|
673
813
|
```
|
|
674
814
|
|
|
675
|
-
|
|
815
|
+
<details>
|
|
816
|
+
<summary><strong>Full translation key reference</strong></summary>
|
|
676
817
|
|
|
677
|
-
```js
|
|
678
|
-
import { t } from 'labellife-design-tool/config';
|
|
679
|
-
|
|
680
|
-
t('toolbar.flip'); // Returns translated value
|
|
681
|
-
t('toolbar.flip', 'Flip'); // Returns default if key not found
|
|
682
818
|
```
|
|
819
|
+
toolbar.flip, toolbar.flipHorizontally, toolbar.flipVertically, toolbar.effects,
|
|
820
|
+
toolbar.fitToBackground, toolbar.clip, toolbar.removeMask, toolbar.crop, toolbar.fill,
|
|
821
|
+
toolbar.textStroke, toolbar.border, toolbar.cornerRadius, toolbar.color, toolbar.opacity,
|
|
822
|
+
toolbar.up, toolbar.down, toolbar.lockedDescription, toolbar.unlockedDescription,
|
|
823
|
+
toolbar.duplicateElements, toolbar.removeElements, toolbar.download, toolbar.import,
|
|
824
|
+
toolbar.fileType, toolbar.quality, toolbar.undo, toolbar.redo, toolbar.blur,
|
|
825
|
+
toolbar.brightness, toolbar.shadow, toolbar.offsetX, toolbar.offsetY,
|
|
826
|
+
toolbar.group, toolbar.ungroup
|
|
683
827
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
828
|
+
sidePanel.text, sidePanel.elements, sidePanel.shapes, sidePanel.lines,
|
|
829
|
+
sidePanel.upload, sidePanel.uploadTip, sidePanel.background, sidePanel.layers,
|
|
830
|
+
sidePanel.noLayers, sidePanel.headerText, sidePanel.subHeaderText, sidePanel.bodyText,
|
|
831
|
+
sidePanel.inputFields.tab, sidePanel.inputFields.title, sidePanel.inputFields.description,
|
|
832
|
+
sidePanel.inputFields.textInput, sidePanel.inputFields.dateInput,
|
|
833
|
+
sidePanel.inputFields.integerInput, sidePanel.inputFields.imageInput,
|
|
834
|
+
sidePanel.inputFields.required, sidePanel.inputFields.promptTextHint,
|
|
835
|
+
sidePanel.inputFields.dateFormat
|
|
690
836
|
|
|
691
|
-
|
|
837
|
+
inputFieldsDialog.title, inputFieldsDialog.submit, inputFieldsDialog.skip,
|
|
838
|
+
inputFieldsDialog.stepIndicator
|
|
692
839
|
```
|
|
693
840
|
|
|
694
|
-
|
|
841
|
+
</details>
|
|
695
842
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
### Unit Conversion
|
|
843
|
+
### Theming
|
|
699
844
|
|
|
700
845
|
```js
|
|
701
|
-
import {
|
|
846
|
+
import { setTheme, getTheme } from 'labellife-design-tool/config';
|
|
702
847
|
|
|
703
|
-
|
|
704
|
-
pxToUnit({ unit: 'mm', dpi: 300, pxVal: 590.55 }); // → millimeters
|
|
848
|
+
setTheme('light'); // or 'dark'
|
|
705
849
|
```
|
|
706
850
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
### Image Utilities
|
|
710
|
-
|
|
711
|
-
```js
|
|
712
|
-
import { getImageSize } from 'labellife-design-tool/utils/image';
|
|
713
|
-
|
|
714
|
-
const { width, height } = await getImageSize('https://example.com/photo.jpg');
|
|
715
|
-
```
|
|
851
|
+
### Input Fields Dialog Configuration
|
|
716
852
|
|
|
717
|
-
|
|
853
|
+
See [Input Fields — Customizing the Popup Appearance](#customizing-the-popup-appearance) for full details.
|
|
718
854
|
|
|
719
855
|
```js
|
|
720
|
-
import {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
//
|
|
856
|
+
import { setInputFieldsConfig, getInputFieldsConfig } from 'labellife-design-tool';
|
|
857
|
+
|
|
858
|
+
setInputFieldsConfig({
|
|
859
|
+
dialogStyle: {}, // MUI sx overrides for the Dialog paper
|
|
860
|
+
titleStyle: {}, // MUI sx overrides for the Dialog title
|
|
861
|
+
promptStyle: {}, // MUI sx overrides for the prompt text
|
|
862
|
+
inputStyle: {}, // MUI sx overrides for the input field
|
|
863
|
+
submitButtonStyle: {}, // MUI sx overrides for the Confirm button
|
|
864
|
+
skipButtonStyle: {}, // MUI sx overrides for the Skip button
|
|
865
|
+
submitButtonText: '', // Override Confirm button label
|
|
866
|
+
skipButtonText: '', // Override Skip button label
|
|
867
|
+
CustomDialog: null, // Provide a fully custom dialog component
|
|
868
|
+
});
|
|
729
869
|
```
|
|
730
870
|
|
|
731
871
|
---
|
|
@@ -762,7 +902,6 @@ The design JSON follows this structure:
|
|
|
762
902
|
{
|
|
763
903
|
"id": "page-1",
|
|
764
904
|
"background": "#ffffff",
|
|
765
|
-
"duration": 5000,
|
|
766
905
|
"children": [
|
|
767
906
|
{
|
|
768
907
|
"id": "el-1",
|
|
@@ -774,16 +913,25 @@ The design JSON follows this structure:
|
|
|
774
913
|
"text": "Hello World",
|
|
775
914
|
"fontSize": 32,
|
|
776
915
|
"fontFamily": "Roboto",
|
|
777
|
-
"fill": "#000000"
|
|
916
|
+
"fill": "#000000",
|
|
917
|
+
"custom": {}
|
|
778
918
|
},
|
|
779
919
|
{
|
|
780
920
|
"id": "el-2",
|
|
781
|
-
"type": "
|
|
782
|
-
"x":
|
|
921
|
+
"type": "text",
|
|
922
|
+
"x": 200,
|
|
783
923
|
"y": 200,
|
|
784
|
-
"width":
|
|
785
|
-
"height":
|
|
786
|
-
"
|
|
924
|
+
"width": 250,
|
|
925
|
+
"height": 40,
|
|
926
|
+
"text": "Sample Text",
|
|
927
|
+
"fontSize": 20,
|
|
928
|
+
"custom": {
|
|
929
|
+
"inputField": true,
|
|
930
|
+
"inputType": "text",
|
|
931
|
+
"placeholder": "Enter text",
|
|
932
|
+
"promptText": "What is your name?",
|
|
933
|
+
"required": true
|
|
934
|
+
}
|
|
787
935
|
}
|
|
788
936
|
]
|
|
789
937
|
}
|
|
@@ -793,7 +941,11 @@ The design JSON follows this structure:
|
|
|
793
941
|
|
|
794
942
|
---
|
|
795
943
|
|
|
796
|
-
##
|
|
944
|
+
## Dependencies
|
|
945
|
+
|
|
946
|
+
### Peer Dependencies
|
|
947
|
+
|
|
948
|
+
These must be installed in your host application:
|
|
797
949
|
|
|
798
950
|
| Package | Version |
|
|
799
951
|
|---|---|
|
|
@@ -808,10 +960,20 @@ The design JSON follows this structure:
|
|
|
808
960
|
|
|
809
961
|
These are included in the package — no need to install separately:
|
|
810
962
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
- `
|
|
963
|
+
| Package | Purpose |
|
|
964
|
+
|---|---|
|
|
965
|
+
| `konva` + `react-konva` | Canvas rendering |
|
|
966
|
+
| `mobx` + `mobx-state-tree` + `mobx-react-lite` | State management |
|
|
967
|
+
| `jspdf` | PDF generation |
|
|
968
|
+
| `uuid` | Unique ID generation |
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Roadmap
|
|
973
|
+
|
|
974
|
+
The following features are planned but not yet implemented:
|
|
975
|
+
|
|
976
|
+
- **Image Input Field Popup** — The `image` input type can be added to templates and its metadata is preserved in JSON, but the popup dialog for uploading images at load time is not yet available. Image fields are skipped during the input collection flow.
|
|
815
977
|
|
|
816
978
|
---
|
|
817
979
|
|