@nice2dev/ui-printing 1.1.6 โ 1.1.9
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/dist/index.cjs +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +1894 -1728
- package/dist/ui-printing.css +1 -1
- package/package.json +3 -2
- package/src/index.ts +5 -24
- package/src/print-button/NicePrintButton.tsx +44 -18
- package/src/print-button/PrintButton.module.css +147 -43
- package/src/print-preview/NicePrintPreview.tsx +19 -17
- package/src/print-preview/PrintPreview.module.css +60 -18
- package/src/print-queue/NicePrintQueue.tsx +76 -42
- package/src/print-queue/PrintQueue.module.css +207 -57
- package/src/template-browser/NiceTemplateBrowser.tsx +44 -19
- package/src/template-browser/TemplateBrowser.module.css +70 -25
- package/src/template-editor/NiceTemplateEditor.tsx +307 -182
- package/src/template-editor/TemplateEditor.module.css +184 -128
- package/src/types/printingTypes.ts +1 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/print-button/NicePrintButton.d.ts.map +0 -1
- package/dist/print-button/index.d.ts.map +0 -1
- package/dist/print-preview/NicePrintPreview.d.ts.map +0 -1
- package/dist/print-preview/index.d.ts.map +0 -1
- package/dist/print-queue/NicePrintQueue.d.ts.map +0 -1
- package/dist/print-queue/index.d.ts.map +0 -1
- package/dist/style.css +0 -1
- package/dist/template-browser/NiceTemplateBrowser.d.ts.map +0 -1
- package/dist/template-browser/index.d.ts.map +0 -1
- package/dist/template-editor/NiceTemplateEditor.d.ts.map +0 -1
- package/dist/template-editor/index.d.ts.map +0 -1
- package/dist/types/printingTypes.d.ts.map +0 -1
- package/src/template-editor/NiceTemplateEditor.tsx.first +0 -1011
- package/src/template-editor/NiceTemplateEditor.tsx.tmp +0 -392
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable jsx-a11y/click-events-have-key-events -- modal overlay backdrops (Esc-on-parent provides keyboard close), selection rows in .map() accompanied by visible cursor:pointer styling and sibling buttons; full keyboard migration via useAccessibleClick + extracted subcomponents tracked in TODO_1.0.10 A2.3 */
|
|
2
|
-
import { NiceAppShell } from '@nice2dev/ui';
|
|
2
|
+
import { NiceAppShell, NiceIcon, useNiceTranslation } from '@nice2dev/ui';
|
|
3
|
+
import type { NiceTranslateFn } from '@nice2dev/ui';
|
|
3
4
|
import React, {
|
|
4
5
|
useState,
|
|
5
6
|
useCallback,
|
|
@@ -55,52 +56,55 @@ const FONT_FAMILIES = [
|
|
|
55
56
|
];
|
|
56
57
|
const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
|
|
57
58
|
|
|
59
|
+
/** NiceIcon name per field type. */
|
|
58
60
|
const FIELD_TYPE_ICONS: Record<string, string> = {
|
|
59
|
-
text: '
|
|
60
|
-
number: '
|
|
61
|
-
currency: '
|
|
62
|
-
date: '
|
|
63
|
-
datetime: '
|
|
64
|
-
boolean: '
|
|
65
|
-
image: '
|
|
66
|
-
barcode: '
|
|
67
|
-
qr: '
|
|
68
|
-
signature: '
|
|
69
|
-
static_text: '
|
|
70
|
-
static_image: '
|
|
71
|
-
line: '
|
|
72
|
-
rect: '
|
|
73
|
-
page_number: '
|
|
74
|
-
total_pages: '
|
|
75
|
-
print_date: '
|
|
61
|
+
text: 'type',
|
|
62
|
+
number: 'hash',
|
|
63
|
+
currency: 'dollar',
|
|
64
|
+
date: 'calendar',
|
|
65
|
+
datetime: 'clock',
|
|
66
|
+
boolean: 'check-square',
|
|
67
|
+
image: 'image',
|
|
68
|
+
barcode: 'barcode',
|
|
69
|
+
qr: 'qr-code',
|
|
70
|
+
signature: 'pen',
|
|
71
|
+
static_text: 'type',
|
|
72
|
+
static_image: 'image',
|
|
73
|
+
line: 'line',
|
|
74
|
+
rect: 'square',
|
|
75
|
+
page_number: 'hash',
|
|
76
|
+
total_pages: 'layers',
|
|
77
|
+
print_date: 'calendar',
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
const FIELD_TYPE_LABELS: Record<string, string> = {
|
|
79
|
-
text: '
|
|
80
|
-
number: '
|
|
81
|
-
currency: '
|
|
82
|
-
date: '
|
|
83
|
-
datetime: '
|
|
84
|
-
boolean: '
|
|
85
|
-
image: '
|
|
86
|
-
barcode: '
|
|
81
|
+
text: 'Text',
|
|
82
|
+
number: 'Number',
|
|
83
|
+
currency: 'Currency',
|
|
84
|
+
date: 'Date',
|
|
85
|
+
datetime: 'Date and time',
|
|
86
|
+
boolean: 'Yes/No',
|
|
87
|
+
image: 'Image',
|
|
88
|
+
barcode: 'Barcode',
|
|
87
89
|
qr: 'QR',
|
|
88
|
-
signature: '
|
|
89
|
-
static_text: '
|
|
90
|
-
static_image: '
|
|
91
|
-
line: '
|
|
92
|
-
rect: '
|
|
93
|
-
page_number: '
|
|
94
|
-
total_pages: '
|
|
95
|
-
print_date: '
|
|
90
|
+
signature: 'Signature',
|
|
91
|
+
static_text: 'Static text',
|
|
92
|
+
static_image: 'Static image',
|
|
93
|
+
line: 'Line',
|
|
94
|
+
rect: 'Rectangle',
|
|
95
|
+
page_number: 'Page number',
|
|
96
|
+
total_pages: 'Total pages',
|
|
97
|
+
print_date: 'Print date',
|
|
96
98
|
};
|
|
97
99
|
|
|
100
|
+
/** Field-type label in the active language; the table above holds the English default. */
|
|
101
|
+
const fieldTypeLabel = (tr: NiceTranslateFn, type: string): string =>
|
|
102
|
+
FIELD_TYPE_LABELS[type] ? tr(`printing.fieldType.${type}`, FIELD_TYPE_LABELS[type]) : type;
|
|
103
|
+
|
|
98
104
|
// โโโ Undo/Redo history โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
99
105
|
type HistoryState = { past: PrintTemplate[]; present: PrintTemplate; future: PrintTemplate[] };
|
|
100
106
|
type HistoryAction =
|
|
101
|
-
|
|
102
|
-
| { type: 'UNDO' }
|
|
103
|
-
| { type: 'REDO' };
|
|
107
|
+
{ type: 'UPDATE'; payload: PrintTemplate } | { type: 'UNDO' } | { type: 'REDO' };
|
|
104
108
|
|
|
105
109
|
const MAX_HISTORY = 50;
|
|
106
110
|
|
|
@@ -136,6 +140,17 @@ function historyReducer(state: HistoryState, action: HistoryAction): HistoryStat
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
143
|
+
/**
|
|
144
|
+
* Document ink, as a LITERAL rather than a token โ same reason as `344d1b2` in the vector
|
|
145
|
+
* editor. These values are template DATA: they are written into exported documents, where
|
|
146
|
+
* no --nice-* property is declared, and they are fed to `<input type="color">`, which only
|
|
147
|
+
* accepts a 7-character hex and silently shows black for anything else. A `var(โฆ)` string
|
|
148
|
+
* here never reached the paper as a colour and could not be edited in the picker.
|
|
149
|
+
*/
|
|
150
|
+
const INK = '#000000';
|
|
151
|
+
/** Same, for the faint grey a watermark is drawn in. */
|
|
152
|
+
const INK_FAINT = '#94a3b8';
|
|
153
|
+
|
|
139
154
|
function uid() {
|
|
140
155
|
return `f-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
|
141
156
|
}
|
|
@@ -147,7 +162,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
|
|
|
147
162
|
label: schemaField?.label ?? FIELD_TYPE_LABELS[type] ?? type,
|
|
148
163
|
type: type as PrintDataField['type'],
|
|
149
164
|
dataPath: schemaField?.path ?? '',
|
|
150
|
-
fallback: type === 'static_text' ? '
|
|
165
|
+
fallback: type === 'static_text' ? 'Static text' : undefined,
|
|
151
166
|
format: type === 'date' || type === 'datetime' ? 'DD.MM.YYYY' : undefined,
|
|
152
167
|
position: {
|
|
153
168
|
x: 15,
|
|
@@ -155,7 +170,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
|
|
|
155
170
|
width: type === 'line' ? 80 : 60,
|
|
156
171
|
height: type === 'line' ? 1 : type === 'image' ? 30 : 8,
|
|
157
172
|
},
|
|
158
|
-
style: { fontSize: 10, color:
|
|
173
|
+
style: { fontSize: 10, color: INK, fontFamily: 'sans-serif' },
|
|
159
174
|
};
|
|
160
175
|
if (type === 'rect') {
|
|
161
176
|
base.style.backgroundColor = 'var(--nice-bg-secondary, #f1f5f9)';
|
|
@@ -164,7 +179,7 @@ function makeField(type: string, schemaField?: DataSourceField): PrintDataField
|
|
|
164
179
|
base.style = {
|
|
165
180
|
...base.style,
|
|
166
181
|
borderWidth: 0.5,
|
|
167
|
-
borderColor:
|
|
182
|
+
borderColor: INK,
|
|
168
183
|
borderStyle: 'solid',
|
|
169
184
|
};
|
|
170
185
|
}
|
|
@@ -235,7 +250,7 @@ interface FieldElementProps {
|
|
|
235
250
|
|
|
236
251
|
function resolveFieldPreview(field: PrintDataField, data?: Record<string, unknown>): string {
|
|
237
252
|
if (field.type === 'static_text') {
|
|
238
|
-
return field.fallback ?? '
|
|
253
|
+
return field.fallback ?? 'Text';
|
|
239
254
|
}
|
|
240
255
|
if (field.type === 'page_number') {
|
|
241
256
|
return '1';
|
|
@@ -295,7 +310,7 @@ const FieldElement: React.FC<FieldElementProps> = ({
|
|
|
295
310
|
fontFamily: st.fontFamily ?? 'sans-serif',
|
|
296
311
|
fontWeight: st.fontWeight ?? 'normal',
|
|
297
312
|
fontStyle: st.fontStyle ?? 'normal',
|
|
298
|
-
color: st.color ??
|
|
313
|
+
color: st.color ?? INK,
|
|
299
314
|
textAlign: st.textAlign ?? 'left',
|
|
300
315
|
lineHeight: st.lineHeight ?? 1.4,
|
|
301
316
|
letterSpacing: st.letterSpacing ? `${st.letterSpacing}em` : undefined,
|
|
@@ -319,17 +334,22 @@ const FieldElement: React.FC<FieldElementProps> = ({
|
|
|
319
334
|
<div
|
|
320
335
|
style={{
|
|
321
336
|
width: '100%',
|
|
322
|
-
borderBottom: `${(st.borderWidth ?? 0.5) * zoom}px ${st.borderStyle ?? 'solid'} ${st.borderColor ??
|
|
337
|
+
borderBottom: `${(st.borderWidth ?? 0.5) * zoom}px ${st.borderStyle ?? 'solid'} ${st.borderColor ?? INK}`,
|
|
323
338
|
}}
|
|
324
339
|
/>
|
|
325
340
|
) : isRect ? null : isImage ? (
|
|
326
|
-
<div className={styles.fieldImagePlaceholder}
|
|
341
|
+
<div className={styles.fieldImagePlaceholder}>
|
|
342
|
+
<NiceIcon name="image" size={24} />
|
|
343
|
+
</div>
|
|
327
344
|
) : isBarcode ? (
|
|
328
345
|
<div className={styles.fieldBarcodePlaceholder}>
|
|
329
|
-
{field.type === 'qr' ? '
|
|
346
|
+
<NiceIcon name={field.type === 'qr' ? 'qr-code' : 'barcode'} size={14} />{' '}
|
|
347
|
+
{field.type === 'qr' ? 'QR' : 'Barcode'}
|
|
330
348
|
</div>
|
|
331
349
|
) : field.type === 'signature' ? (
|
|
332
|
-
<div className={styles.fieldSignaturePlaceholder}
|
|
350
|
+
<div className={styles.fieldSignaturePlaceholder}>
|
|
351
|
+
<NiceIcon name="pen" size={14} /> {field.label}
|
|
352
|
+
</div>
|
|
333
353
|
) : (
|
|
334
354
|
<span style={{ display: 'block', width: '100%' }}>
|
|
335
355
|
{resolveFieldPreview(field, sampleData)}
|
|
@@ -469,21 +489,21 @@ const ShortcutChip: React.FC<{ keys: string; label: string }> = ({ keys, label }
|
|
|
469
489
|
|
|
470
490
|
// โโโ Left panel tabs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
471
491
|
type LeftTab = 'fields' | 'layers' | 'watermarks' | 'signatures' | 'settings';
|
|
472
|
-
const LEFT_TABS: { id: LeftTab; icon: string; label: string }[] = [
|
|
473
|
-
{ id: 'fields', icon: '
|
|
474
|
-
{ id: 'layers', icon: '
|
|
475
|
-
{ id: 'watermarks', icon: '
|
|
476
|
-
{ id: 'signatures', icon: '
|
|
477
|
-
{ id: 'settings', icon: '
|
|
492
|
+
const LEFT_TABS: { id: LeftTab; icon: string; key: string; label: string }[] = [
|
|
493
|
+
{ id: 'fields', icon: 'list', key: 'printing.editor.tabFields', label: 'Fields' },
|
|
494
|
+
{ id: 'layers', icon: 'layers', key: 'printing.editor.tabLayers', label: 'Layers' },
|
|
495
|
+
{ id: 'watermarks', icon: 'droplet', key: 'printing.editor.tabWatermarks', label: 'Watermarks' },
|
|
496
|
+
{ id: 'signatures', icon: 'pen', key: 'printing.editor.tabSignatures', label: 'Signatures' },
|
|
497
|
+
{ id: 'settings', icon: 'settings', key: 'printing.editor.tabSettings', label: 'Settings' },
|
|
478
498
|
];
|
|
479
499
|
|
|
480
500
|
// โโโ Right panel tabs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
481
501
|
type RightTab = 'position' | 'style' | 'data' | 'conditional';
|
|
482
|
-
const RIGHT_TABS: { id: RightTab; label: string }[] = [
|
|
483
|
-
{ id: 'position', label: '
|
|
484
|
-
{ id: 'style', label: '
|
|
485
|
-
{ id: 'data', label: '
|
|
486
|
-
{ id: 'conditional', label: '
|
|
502
|
+
const RIGHT_TABS: { id: RightTab; key: string; label: string }[] = [
|
|
503
|
+
{ id: 'position', key: 'printing.editor.tabPosition', label: 'Position' },
|
|
504
|
+
{ id: 'style', key: 'printing.editor.tabStyle', label: 'Style' },
|
|
505
|
+
{ id: 'data', key: 'printing.editor.tabData', label: 'Data' },
|
|
506
|
+
{ id: 'conditional', key: 'printing.editor.tabConditional', label: 'Conditions' },
|
|
487
507
|
];
|
|
488
508
|
|
|
489
509
|
// โโโ Toolbar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -536,6 +556,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
536
556
|
onSendBackward,
|
|
537
557
|
onSave,
|
|
538
558
|
}) => {
|
|
559
|
+
const { t: tr } = useNiceTranslation();
|
|
539
560
|
const [insertOpen, setInsertOpen] = useState(false);
|
|
540
561
|
const ZOOM_PRESETS = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
541
562
|
|
|
@@ -559,14 +580,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
559
580
|
onClick={() => onPaperChange(tpl.paperSize, 'portrait')}
|
|
560
581
|
title="Pion"
|
|
561
582
|
>
|
|
562
|
-
|
|
583
|
+
<NiceIcon name="file" size={14} />
|
|
563
584
|
</button>
|
|
564
585
|
<button
|
|
565
586
|
className={`${styles.toolbarBtn} ${tpl.orientation === 'landscape' ? styles.toolbarBtnActive : ''}`}
|
|
566
587
|
onClick={() => onPaperChange(tpl.paperSize, 'landscape')}
|
|
567
588
|
title="Poziom"
|
|
568
589
|
>
|
|
569
|
-
|
|
590
|
+
<NiceIcon name="file" size={14} style={{ transform: 'rotate(90deg)' }} />
|
|
570
591
|
</button>
|
|
571
592
|
</div>
|
|
572
593
|
|
|
@@ -577,17 +598,17 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
577
598
|
className={styles.toolbarBtn}
|
|
578
599
|
disabled={!canUndo}
|
|
579
600
|
onClick={onUndo}
|
|
580
|
-
title=
|
|
601
|
+
title={`${tr('printing.editor.undo', 'Undo')} (Ctrl+Z)`}
|
|
581
602
|
>
|
|
582
|
-
|
|
603
|
+
<NiceIcon name="undo" size={14} />
|
|
583
604
|
</button>
|
|
584
605
|
<button
|
|
585
606
|
className={styles.toolbarBtn}
|
|
586
607
|
disabled={!canRedo}
|
|
587
608
|
onClick={onRedo}
|
|
588
|
-
title=
|
|
609
|
+
title={`${tr('printing.editor.redo', 'Redo')} (Ctrl+Y)`}
|
|
589
610
|
>
|
|
590
|
-
|
|
611
|
+
<NiceIcon name="redo" size={14} />
|
|
591
612
|
</button>
|
|
592
613
|
</div>
|
|
593
614
|
)}
|
|
@@ -599,11 +620,14 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
599
620
|
className={`${styles.toolbarBtn} ${styles.insertBtn}`}
|
|
600
621
|
onClick={() => setInsertOpen((o) => !o)}
|
|
601
622
|
>
|
|
602
|
-
|
|
623
|
+
<NiceIcon name="plus" size={14} /> {tr('printing.editor.insert', 'Insert')}{' '}
|
|
624
|
+
<NiceIcon name="chevron-down" size={12} />
|
|
603
625
|
</button>
|
|
604
626
|
{insertOpen && (
|
|
605
627
|
<div className={styles.insertDropdown} onMouseLeave={() => setInsertOpen(false)}>
|
|
606
|
-
<div className={styles.insertCategory}>
|
|
628
|
+
<div className={styles.insertCategory}>
|
|
629
|
+
{tr('printing.editor.dataFields', 'Data fields')}
|
|
630
|
+
</div>
|
|
607
631
|
{['text', 'number', 'currency', 'date', 'image', 'barcode', 'qr'].map((t) => (
|
|
608
632
|
<button
|
|
609
633
|
key={t}
|
|
@@ -614,7 +638,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
614
638
|
}}
|
|
615
639
|
>
|
|
616
640
|
<span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
|
|
617
|
-
{
|
|
641
|
+
{fieldTypeLabel(tr, t)}
|
|
618
642
|
</button>
|
|
619
643
|
))}
|
|
620
644
|
<div className={styles.insertCategory}>Elementy statyczne</div>
|
|
@@ -636,7 +660,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
636
660
|
}}
|
|
637
661
|
>
|
|
638
662
|
<span className={styles.insertIcon}>{FIELD_TYPE_ICONS[t]}</span>
|
|
639
|
-
{
|
|
663
|
+
{fieldTypeLabel(tr, t)}
|
|
640
664
|
</button>
|
|
641
665
|
))}
|
|
642
666
|
<div className={styles.insertCategory}>Specjalne</div>
|
|
@@ -647,7 +671,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
647
671
|
setInsertOpen(false);
|
|
648
672
|
}}
|
|
649
673
|
>
|
|
650
|
-
<span className={styles.insertIcon}
|
|
674
|
+
<span className={styles.insertIcon}>
|
|
675
|
+
<NiceIcon name="droplet" size={14} />
|
|
676
|
+
</span>
|
|
677
|
+
{tr('printing.editor.watermark', 'Watermark')}
|
|
651
678
|
</button>
|
|
652
679
|
<button
|
|
653
680
|
className={styles.insertItem}
|
|
@@ -656,7 +683,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
656
683
|
setInsertOpen(false);
|
|
657
684
|
}}
|
|
658
685
|
>
|
|
659
|
-
<span className={styles.insertIcon}
|
|
686
|
+
<span className={styles.insertIcon}>
|
|
687
|
+
<NiceIcon name="pen" size={14} />
|
|
688
|
+
</span>
|
|
689
|
+
{tr('printing.editor.signature', 'Signature')}
|
|
660
690
|
</button>
|
|
661
691
|
</div>
|
|
662
692
|
)}
|
|
@@ -666,53 +696,73 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
666
696
|
{/* Selection actions */}
|
|
667
697
|
{!readOnly && selectedCount > 0 && (
|
|
668
698
|
<div className={styles.toolbarGroup}>
|
|
669
|
-
<button
|
|
670
|
-
|
|
699
|
+
<button
|
|
700
|
+
className={styles.toolbarBtn}
|
|
701
|
+
onClick={onDuplicate}
|
|
702
|
+
title={`${tr('printing.editor.duplicate', 'Duplicate')} (Ctrl+D)`}
|
|
703
|
+
>
|
|
704
|
+
<NiceIcon name="copy" size={14} />
|
|
671
705
|
</button>
|
|
672
706
|
<button
|
|
673
707
|
className={`${styles.toolbarBtn} ${styles.toolbarBtnDanger}`}
|
|
674
708
|
onClick={onDelete}
|
|
675
|
-
title=
|
|
709
|
+
title={`${tr('printing.action.remove', 'Remove')} (Del)`}
|
|
676
710
|
>
|
|
677
|
-
|
|
711
|
+
<NiceIcon name="trash" size={14} />
|
|
678
712
|
</button>
|
|
679
713
|
<span className={styles.toolbarSep} />
|
|
680
714
|
<button
|
|
681
715
|
className={styles.toolbarBtn}
|
|
682
716
|
onClick={() => onAlignH('left')}
|
|
683
|
-
title=
|
|
717
|
+
title={tr('printing.editor.alignLeft', 'Align left')}
|
|
684
718
|
>
|
|
685
|
-
|
|
719
|
+
<NiceIcon name="align-left" size={14} />
|
|
686
720
|
</button>
|
|
687
721
|
<button
|
|
688
722
|
className={styles.toolbarBtn}
|
|
689
723
|
onClick={() => onAlignH('center')}
|
|
690
|
-
title=
|
|
724
|
+
title={tr('printing.editor.alignCenter', 'Align centre')}
|
|
691
725
|
>
|
|
692
|
-
|
|
726
|
+
<NiceIcon name="align-center" size={14} />
|
|
693
727
|
</button>
|
|
694
728
|
<button
|
|
695
729
|
className={styles.toolbarBtn}
|
|
696
730
|
onClick={() => onAlignH('right')}
|
|
697
|
-
title=
|
|
731
|
+
title={tr('printing.editor.alignRight', 'Align right')}
|
|
698
732
|
>
|
|
699
|
-
|
|
733
|
+
<NiceIcon name="align-right" size={14} />
|
|
700
734
|
</button>
|
|
701
|
-
<button
|
|
735
|
+
<button
|
|
736
|
+
className={styles.toolbarBtn}
|
|
737
|
+
onClick={() => onAlignV('top')}
|
|
738
|
+
title={tr('printing.editor.alignTop', 'Align top')}
|
|
739
|
+
>
|
|
702
740
|
โค
|
|
703
741
|
</button>
|
|
704
|
-
<button
|
|
742
|
+
<button
|
|
743
|
+
className={styles.toolbarBtn}
|
|
744
|
+
onClick={() => onAlignV('center')}
|
|
745
|
+
title={tr('printing.editor.alignMiddle', 'Align middle')}
|
|
746
|
+
>
|
|
705
747
|
โ
|
|
706
748
|
</button>
|
|
707
|
-
<button
|
|
749
|
+
<button
|
|
750
|
+
className={styles.toolbarBtn}
|
|
751
|
+
onClick={() => onAlignV('bottom')}
|
|
752
|
+
title={tr('printing.editor.alignBottom', 'Align bottom')}
|
|
753
|
+
>
|
|
708
754
|
โฅ
|
|
709
755
|
</button>
|
|
710
756
|
<span className={styles.toolbarSep} />
|
|
711
757
|
<button className={styles.toolbarBtn} onClick={onBringForward} title="Do przodu">
|
|
712
|
-
|
|
758
|
+
<NiceIcon name="arrow-up" size={14} />
|
|
713
759
|
</button>
|
|
714
|
-
<button
|
|
715
|
-
|
|
760
|
+
<button
|
|
761
|
+
className={styles.toolbarBtn}
|
|
762
|
+
onClick={onSendBackward}
|
|
763
|
+
title={tr('printing.editor.sendBackward', 'Send backward')}
|
|
764
|
+
>
|
|
765
|
+
<NiceIcon name="arrow-down" size={14} />
|
|
716
766
|
</button>
|
|
717
767
|
</div>
|
|
718
768
|
)}
|
|
@@ -722,16 +772,16 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
722
772
|
<button
|
|
723
773
|
className={`${styles.toolbarBtn} ${showGrid ? styles.toolbarBtnActive : ''}`}
|
|
724
774
|
onClick={onToggleGrid}
|
|
725
|
-
title=
|
|
775
|
+
title={tr('printing.editor.grid', 'Grid')}
|
|
726
776
|
>
|
|
727
777
|
โ
|
|
728
778
|
</button>
|
|
729
779
|
<button
|
|
730
780
|
className={`${styles.toolbarBtn} ${showRulers ? styles.toolbarBtnActive : ''}`}
|
|
731
781
|
onClick={onToggleRulers}
|
|
732
|
-
title=
|
|
782
|
+
title={tr('printing.editor.rulers', 'Rulers')}
|
|
733
783
|
>
|
|
734
|
-
|
|
784
|
+
<NiceIcon name="ruler" size={14} />
|
|
735
785
|
</button>
|
|
736
786
|
</div>
|
|
737
787
|
|
|
@@ -761,7 +811,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|
|
761
811
|
|
|
762
812
|
{!readOnly && onSave && (
|
|
763
813
|
<button className={styles.saveBtn} onClick={onSave}>
|
|
764
|
-
|
|
814
|
+
<NiceIcon name="save" size={14} /> {tr('printing.editor.save', 'Save')}
|
|
765
815
|
</button>
|
|
766
816
|
)}
|
|
767
817
|
</div>
|
|
@@ -794,6 +844,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
794
844
|
onUpdateTpl,
|
|
795
845
|
onReorderField,
|
|
796
846
|
}) => {
|
|
847
|
+
const { t: tr } = useNiceTranslation();
|
|
797
848
|
const [search, setSearch] = useState('');
|
|
798
849
|
const filteredFields = schema?.fields.filter(
|
|
799
850
|
(f) =>
|
|
@@ -809,10 +860,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
809
860
|
key={t.id}
|
|
810
861
|
className={`${styles.leftTab} ${activeTab === t.id ? styles.leftTabActive : ''}`}
|
|
811
862
|
onClick={() => onTabChange(t.id)}
|
|
812
|
-
title={t.label}
|
|
863
|
+
title={tr(t.key, t.label)}
|
|
813
864
|
>
|
|
814
|
-
<span className={styles.leftTabIcon}>
|
|
815
|
-
|
|
865
|
+
<span className={styles.leftTabIcon}>
|
|
866
|
+
<NiceIcon name={t.icon} size={16} />
|
|
867
|
+
</span>
|
|
868
|
+
<span className={styles.leftTabLabel}>{tr(t.key, t.label)}</span>
|
|
816
869
|
</button>
|
|
817
870
|
))}
|
|
818
871
|
</div>
|
|
@@ -821,11 +874,13 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
821
874
|
{/* โโ Fields tab โโ */}
|
|
822
875
|
{activeTab === 'fields' && (
|
|
823
876
|
<>
|
|
824
|
-
<div className={styles.panelHeader}>
|
|
877
|
+
<div className={styles.panelHeader}>
|
|
878
|
+
{tr('printing.editor.dataFields', 'Data fields')}
|
|
879
|
+
</div>
|
|
825
880
|
<div className={styles.searchBox}>
|
|
826
881
|
<input
|
|
827
882
|
className={styles.searchInput}
|
|
828
|
-
placeholder=
|
|
883
|
+
placeholder={tr('printing.editor.search', 'Searchโฆ')}
|
|
829
884
|
value={search}
|
|
830
885
|
onChange={(e) => setSearch(e.target.value)}
|
|
831
886
|
/>
|
|
@@ -844,7 +899,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
844
899
|
}}
|
|
845
900
|
onClick={() => onInsertSchemaField(f)}
|
|
846
901
|
>
|
|
847
|
-
<span className={styles.fieldItemIcon}>
|
|
902
|
+
<span className={styles.fieldItemIcon}>
|
|
903
|
+
<NiceIcon name={FIELD_TYPE_ICONS[f.type] ?? 'type'} size={14} />
|
|
904
|
+
</span>
|
|
848
905
|
<div className={styles.fieldItemInfo}>
|
|
849
906
|
<div className={styles.fieldItemLabel}>{f.label}</div>
|
|
850
907
|
<div className={styles.fieldItemPath}>{f.path}</div>
|
|
@@ -853,12 +910,17 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
853
910
|
</div>
|
|
854
911
|
))}
|
|
855
912
|
{filteredFields?.length === 0 && (
|
|
856
|
-
<div className={styles.emptyMsg}>
|
|
913
|
+
<div className={styles.emptyMsg}>
|
|
914
|
+
{tr('printing.editor.noMatches', 'No matches')}
|
|
915
|
+
</div>
|
|
857
916
|
)}
|
|
858
917
|
</div>
|
|
859
918
|
) : (
|
|
860
919
|
<div className={styles.emptyMsg}>
|
|
861
|
-
|
|
920
|
+
{tr(
|
|
921
|
+
'printing.editor.noSchema',
|
|
922
|
+
'No data schema. Assign an entity in the Settings tab.',
|
|
923
|
+
)}
|
|
862
924
|
</div>
|
|
863
925
|
)}
|
|
864
926
|
</>
|
|
@@ -867,9 +929,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
867
929
|
{/* โโ Layers tab โโ */}
|
|
868
930
|
{activeTab === 'layers' && (
|
|
869
931
|
<>
|
|
870
|
-
<div className={styles.panelHeader}>
|
|
932
|
+
<div className={styles.panelHeader}>
|
|
933
|
+
{tr('printing.editor.layers', 'Layers')} ({allFields.length})
|
|
934
|
+
</div>
|
|
871
935
|
<div className={styles.layerList}>
|
|
872
|
-
{allFields.length === 0 &&
|
|
936
|
+
{allFields.length === 0 && (
|
|
937
|
+
<div className={styles.emptyMsg}>
|
|
938
|
+
{tr('printing.editor.noElements', 'No elements')}
|
|
939
|
+
</div>
|
|
940
|
+
)}
|
|
873
941
|
{allFields
|
|
874
942
|
.slice()
|
|
875
943
|
.reverse()
|
|
@@ -879,7 +947,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
879
947
|
className={`${styles.layerItem} ${selectedIds.has(f.id) ? styles.layerItemSelected : ''}`}
|
|
880
948
|
onClick={() => onSelectField(f.id)}
|
|
881
949
|
>
|
|
882
|
-
<span className={styles.layerIcon}>
|
|
950
|
+
<span className={styles.layerIcon}>
|
|
951
|
+
<NiceIcon name={FIELD_TYPE_ICONS[f.type] ?? 'type'} size={14} />
|
|
952
|
+
</span>
|
|
883
953
|
<span className={styles.layerName}>{f.name || f.label}</span>
|
|
884
954
|
<div className={styles.layerActions}>
|
|
885
955
|
<button
|
|
@@ -888,9 +958,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
888
958
|
e.stopPropagation();
|
|
889
959
|
onReorderField(f.id, -1);
|
|
890
960
|
}}
|
|
891
|
-
title=
|
|
961
|
+
title={tr('printing.editor.bringForward', 'Bring forward')}
|
|
892
962
|
>
|
|
893
|
-
|
|
963
|
+
<NiceIcon name="arrow-up" size={12} />
|
|
894
964
|
</button>
|
|
895
965
|
<button
|
|
896
966
|
className={styles.layerBtn}
|
|
@@ -898,9 +968,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
898
968
|
e.stopPropagation();
|
|
899
969
|
onReorderField(f.id, 1);
|
|
900
970
|
}}
|
|
901
|
-
title=
|
|
971
|
+
title={tr('printing.editor.sendBackward', 'Send backward')}
|
|
902
972
|
>
|
|
903
|
-
|
|
973
|
+
<NiceIcon name="arrow-down" size={12} />
|
|
904
974
|
</button>
|
|
905
975
|
</div>
|
|
906
976
|
</div>
|
|
@@ -917,7 +987,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
917
987
|
{(tpl.watermarks ?? []).map((wm) => (
|
|
918
988
|
<div key={wm.id} className={styles.wmItem}>
|
|
919
989
|
<div className={styles.wmRow}>
|
|
920
|
-
<span className={styles.wmLabel}>
|
|
990
|
+
<span className={styles.wmLabel}>{tr('printing.editor.type', 'Type')}</span>
|
|
921
991
|
<select
|
|
922
992
|
className={styles.wmSelect}
|
|
923
993
|
value={wm.type}
|
|
@@ -929,13 +999,15 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
929
999
|
})
|
|
930
1000
|
}
|
|
931
1001
|
>
|
|
932
|
-
<option value="text">
|
|
933
|
-
<option value="image">
|
|
1002
|
+
<option value="text">{tr('printing.fieldType.text', 'Text')}</option>
|
|
1003
|
+
<option value="image">{tr('printing.fieldType.image', 'Image')}</option>
|
|
934
1004
|
</select>
|
|
935
1005
|
</div>
|
|
936
1006
|
{wm.type === 'text' && (
|
|
937
1007
|
<div className={styles.wmRow}>
|
|
938
|
-
<span className={styles.wmLabel}>
|
|
1008
|
+
<span className={styles.wmLabel}>
|
|
1009
|
+
{tr('printing.fieldType.text', 'Text')}
|
|
1010
|
+
</span>
|
|
939
1011
|
<input
|
|
940
1012
|
className={styles.wmInput}
|
|
941
1013
|
value={wm.text ?? ''}
|
|
@@ -969,7 +1041,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
969
1041
|
<span className={styles.wmValue}>{Math.round(wm.opacity * 100)}%</span>
|
|
970
1042
|
</div>
|
|
971
1043
|
<div className={styles.wmRow}>
|
|
972
|
-
<span className={styles.wmLabel}>
|
|
1044
|
+
<span className={styles.wmLabel}>{tr('printing.editor.angle', 'Angle')}</span>
|
|
973
1045
|
<input
|
|
974
1046
|
type="range"
|
|
975
1047
|
min={-90}
|
|
@@ -988,7 +1060,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
988
1060
|
<span className={styles.wmValue}>{wm.angle}ยฐ</span>
|
|
989
1061
|
</div>
|
|
990
1062
|
<div className={styles.wmRow}>
|
|
991
|
-
<span className={styles.wmLabel}>
|
|
1063
|
+
<span className={styles.wmLabel}>{tr('printing.editor.size', 'Size')}</span>
|
|
992
1064
|
<input
|
|
993
1065
|
type="number"
|
|
994
1066
|
min={8}
|
|
@@ -1006,11 +1078,11 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1006
1078
|
<span className={styles.wmUnit}>pt</span>
|
|
1007
1079
|
</div>
|
|
1008
1080
|
<div className={styles.wmRow}>
|
|
1009
|
-
<span className={styles.wmLabel}>
|
|
1081
|
+
<span className={styles.wmLabel}>{tr('printing.editor.colour', 'Colour')}</span>
|
|
1010
1082
|
<input
|
|
1011
1083
|
type="color"
|
|
1012
1084
|
className={styles.colorPicker}
|
|
1013
|
-
value={wm.color ??
|
|
1085
|
+
value={wm.color ?? INK_FAINT}
|
|
1014
1086
|
onChange={(e) =>
|
|
1015
1087
|
onUpdateTpl({
|
|
1016
1088
|
watermarks: tpl.watermarks?.map((w) =>
|
|
@@ -1026,7 +1098,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1026
1098
|
onUpdateTpl({ watermarks: tpl.watermarks?.filter((w) => w.id !== wm.id) })
|
|
1027
1099
|
}
|
|
1028
1100
|
>
|
|
1029
|
-
|
|
1101
|
+
{tr('printing.action.remove', 'Remove')}
|
|
1030
1102
|
</button>
|
|
1031
1103
|
</div>
|
|
1032
1104
|
))}
|
|
@@ -1039,17 +1111,18 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1039
1111
|
{
|
|
1040
1112
|
id: uid(),
|
|
1041
1113
|
type: 'text',
|
|
1042
|
-
text: '
|
|
1114
|
+
text: 'COPY',
|
|
1043
1115
|
opacity: 0.12,
|
|
1044
1116
|
angle: -45,
|
|
1045
1117
|
fontSize: 60,
|
|
1046
|
-
color:
|
|
1118
|
+
color: INK_FAINT,
|
|
1047
1119
|
},
|
|
1048
1120
|
],
|
|
1049
1121
|
})
|
|
1050
1122
|
}
|
|
1051
1123
|
>
|
|
1052
|
-
|
|
1124
|
+
<NiceIcon name="plus" size={12} />{' '}
|
|
1125
|
+
{tr('printing.editor.addWatermark', 'Add watermark')}
|
|
1053
1126
|
</button>
|
|
1054
1127
|
</div>
|
|
1055
1128
|
</>
|
|
@@ -1091,7 +1164,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1091
1164
|
/>
|
|
1092
1165
|
</div>
|
|
1093
1166
|
<div className={styles.wmRow}>
|
|
1094
|
-
<span className={styles.wmLabel}>
|
|
1167
|
+
<span className={styles.wmLabel}>{tr('printing.fieldType.line', 'Line')}</span>
|
|
1095
1168
|
<input
|
|
1096
1169
|
type="checkbox"
|
|
1097
1170
|
checked={sig.showLine ?? true}
|
|
@@ -1105,7 +1178,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1105
1178
|
/>
|
|
1106
1179
|
</div>
|
|
1107
1180
|
<div className={styles.wmRow}>
|
|
1108
|
-
<span className={styles.wmLabel}>
|
|
1181
|
+
<span className={styles.wmLabel}>
|
|
1182
|
+
{tr('printing.editor.showDate', 'Show date')}
|
|
1183
|
+
</span>
|
|
1109
1184
|
<input
|
|
1110
1185
|
type="checkbox"
|
|
1111
1186
|
checked={sig.showDate ?? false}
|
|
@@ -1124,7 +1199,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1124
1199
|
onUpdateTpl({ signatures: tpl.signatures?.filter((s) => s.id !== sig.id) })
|
|
1125
1200
|
}
|
|
1126
1201
|
>
|
|
1127
|
-
|
|
1202
|
+
{tr('printing.action.remove', 'Remove')}
|
|
1128
1203
|
</button>
|
|
1129
1204
|
</div>
|
|
1130
1205
|
))}
|
|
@@ -1136,7 +1211,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1136
1211
|
...(tpl.signatures ?? []),
|
|
1137
1212
|
{
|
|
1138
1213
|
id: uid(),
|
|
1139
|
-
label: '
|
|
1214
|
+
label: tr('printing.editor.signature', 'Signature'),
|
|
1140
1215
|
position: { x: 10, y: 5, width: 70, height: 22 },
|
|
1141
1216
|
showLine: true,
|
|
1142
1217
|
showDate: false,
|
|
@@ -1145,7 +1220,8 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1145
1220
|
})
|
|
1146
1221
|
}
|
|
1147
1222
|
>
|
|
1148
|
-
|
|
1223
|
+
<NiceIcon name="plus" size={12} />{' '}
|
|
1224
|
+
{tr('printing.editor.addSignature', 'Add signature')}
|
|
1149
1225
|
</button>
|
|
1150
1226
|
</div>
|
|
1151
1227
|
</>
|
|
@@ -1154,10 +1230,12 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1154
1230
|
{/* โโ Settings tab โโ */}
|
|
1155
1231
|
{activeTab === 'settings' && (
|
|
1156
1232
|
<>
|
|
1157
|
-
<div className={styles.panelHeader}>
|
|
1233
|
+
<div className={styles.panelHeader}>
|
|
1234
|
+
{tr('printing.editor.templateSettings', 'Template settings')}
|
|
1235
|
+
</div>
|
|
1158
1236
|
<div className={styles.settingsForm}>
|
|
1159
1237
|
<div className={styles.settingsRow}>
|
|
1160
|
-
<label className={styles.settingsLabel}>
|
|
1238
|
+
<label className={styles.settingsLabel}>{tr('printing.editor.name', 'Name')}</label>
|
|
1161
1239
|
<input
|
|
1162
1240
|
className={styles.settingsInput}
|
|
1163
1241
|
value={tpl.name}
|
|
@@ -1165,7 +1243,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1165
1243
|
/>
|
|
1166
1244
|
</div>
|
|
1167
1245
|
<div className={styles.settingsRow}>
|
|
1168
|
-
<label className={styles.settingsLabel}>
|
|
1246
|
+
<label className={styles.settingsLabel}>
|
|
1247
|
+
{tr('printing.editor.description', 'Description')}
|
|
1248
|
+
</label>
|
|
1169
1249
|
<textarea
|
|
1170
1250
|
className={styles.settingsTextarea}
|
|
1171
1251
|
value={tpl.description ?? ''}
|
|
@@ -1181,17 +1261,19 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
|
|
|
1181
1261
|
onChange={(e) => onUpdateTpl({ category: e.target.value })}
|
|
1182
1262
|
/>
|
|
1183
1263
|
</div>
|
|
1184
|
-
<div className={styles.settingsDivider}>
|
|
1264
|
+
<div className={styles.settingsDivider}>
|
|
1265
|
+
{tr('printing.editor.margins', 'Margins (mm)')}
|
|
1266
|
+
</div>
|
|
1185
1267
|
{(['top', 'right', 'bottom', 'left'] as const).map((side) => (
|
|
1186
1268
|
<div key={side} className={styles.settingsRow}>
|
|
1187
1269
|
<label className={styles.settingsLabel}>
|
|
1188
1270
|
{side === 'top'
|
|
1189
|
-
? '
|
|
1271
|
+
? tr('printing.editor.marginTop', 'Top')
|
|
1190
1272
|
: side === 'right'
|
|
1191
|
-
? '
|
|
1273
|
+
? tr('printing.editor.marginRight', 'Right')
|
|
1192
1274
|
: side === 'bottom'
|
|
1193
|
-
? '
|
|
1194
|
-
: '
|
|
1275
|
+
? tr('printing.editor.marginBottom', 'Bottom')
|
|
1276
|
+
: tr('printing.editor.marginLeft', 'Left')}
|
|
1195
1277
|
</label>
|
|
1196
1278
|
<input
|
|
1197
1279
|
type="number"
|
|
@@ -1233,6 +1315,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1233
1315
|
paperWidthMm,
|
|
1234
1316
|
paperHeightMm: _paperHeightMm,
|
|
1235
1317
|
}) => {
|
|
1318
|
+
const { t: tr } = useNiceTranslation();
|
|
1236
1319
|
const p = useCallback(
|
|
1237
1320
|
(patch: Partial<FieldPosition>) =>
|
|
1238
1321
|
field && onChange({ position: { ...field.position, ...patch } }),
|
|
@@ -1247,17 +1330,21 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1247
1330
|
if (!field) {
|
|
1248
1331
|
return (
|
|
1249
1332
|
<div className={styles.rightPanel}>
|
|
1250
|
-
<div className={styles.panelHeader}>
|
|
1333
|
+
<div className={styles.panelHeader}>{tr('printing.editor.properties', 'Properties')}</div>
|
|
1251
1334
|
<div className={styles.propsEmpty}>
|
|
1252
|
-
<div className={styles.propsEmptyIcon}
|
|
1253
|
-
|
|
1335
|
+
<div className={styles.propsEmptyIcon}>
|
|
1336
|
+
<NiceIcon name="cursor" size={28} />
|
|
1337
|
+
</div>
|
|
1338
|
+
<div className={styles.propsEmptyText}>
|
|
1339
|
+
{tr('printing.editor.selectElement', 'Select an element on the template')}
|
|
1340
|
+
</div>
|
|
1254
1341
|
<div className={styles.shortcutGrid}>
|
|
1255
|
-
<ShortcutChip keys="Del" label=
|
|
1256
|
-
<ShortcutChip keys="Ctrl+D" label=
|
|
1257
|
-
<ShortcutChip keys="โโโโ" label=
|
|
1258
|
-
<ShortcutChip keys="Ctrl+Z" label=
|
|
1259
|
-
<ShortcutChip keys="Ctrl+A" label=
|
|
1260
|
-
<ShortcutChip keys="Shift+โ" label=
|
|
1342
|
+
<ShortcutChip keys="Del" label={tr('printing.action.remove', 'Remove')} />
|
|
1343
|
+
<ShortcutChip keys="Ctrl+D" label={tr('printing.editor.duplicate', 'Duplicate')} />
|
|
1344
|
+
<ShortcutChip keys="โโโโ" label={tr('printing.editor.move', 'Move')} />
|
|
1345
|
+
<ShortcutChip keys="Ctrl+Z" label={tr('printing.editor.undo', 'Undo')} />
|
|
1346
|
+
<ShortcutChip keys="Ctrl+A" label={tr('printing.editor.selectAll', 'Select all')} />
|
|
1347
|
+
<ShortcutChip keys="Shift+โ" label={tr('printing.editor.move5mm', 'Move 5 mm')} />
|
|
1261
1348
|
</div>
|
|
1262
1349
|
</div>
|
|
1263
1350
|
</div>
|
|
@@ -1295,7 +1382,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1295
1382
|
className={`${styles.rightTab} ${activeTab === t.id ? styles.rightTabActive : ''}`}
|
|
1296
1383
|
onClick={() => onTabChange(t.id)}
|
|
1297
1384
|
>
|
|
1298
|
-
{t.label}
|
|
1385
|
+
{tr(t.key, t.label)}
|
|
1299
1386
|
</button>
|
|
1300
1387
|
);
|
|
1301
1388
|
})}
|
|
@@ -1325,7 +1412,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1325
1412
|
</div>
|
|
1326
1413
|
))}
|
|
1327
1414
|
<div className={styles.posCell}>
|
|
1328
|
-
<label className={styles.posLabel}>
|
|
1415
|
+
<label className={styles.posLabel}>
|
|
1416
|
+
{tr('printing.editor.rotation', 'Rotation')}
|
|
1417
|
+
</label>
|
|
1329
1418
|
<input
|
|
1330
1419
|
type="number"
|
|
1331
1420
|
step={5}
|
|
@@ -1349,24 +1438,30 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1349
1438
|
</div>
|
|
1350
1439
|
|
|
1351
1440
|
<div className={styles.snapSection}>
|
|
1352
|
-
<div className={styles.snapTitle}>
|
|
1441
|
+
<div className={styles.snapTitle}>
|
|
1442
|
+
{tr('printing.editor.snapToPage', 'Snap to page')}
|
|
1443
|
+
</div>
|
|
1353
1444
|
<div className={styles.snapRow}>
|
|
1354
|
-
<button
|
|
1355
|
-
|
|
1445
|
+
<button
|
|
1446
|
+
className={styles.snapBtn}
|
|
1447
|
+
onClick={() => p({ x: 0 })}
|
|
1448
|
+
title={tr('printing.editor.leftEdge', 'Left edge')}
|
|
1449
|
+
>
|
|
1450
|
+
<NiceIcon name="align-left" size={14} />
|
|
1356
1451
|
</button>
|
|
1357
1452
|
<button
|
|
1358
1453
|
className={styles.snapBtn}
|
|
1359
1454
|
onClick={() => p({ x: (paperWidthMm - field.position.width) / 2 })}
|
|
1360
|
-
title=
|
|
1455
|
+
title={tr('printing.editor.horizontalCentre', 'Horizontal centre')}
|
|
1361
1456
|
>
|
|
1362
1457
|
โ
|
|
1363
1458
|
</button>
|
|
1364
1459
|
<button
|
|
1365
1460
|
className={styles.snapBtn}
|
|
1366
1461
|
onClick={() => p({ x: paperWidthMm - field.position.width })}
|
|
1367
|
-
title=
|
|
1462
|
+
title={tr('printing.editor.rightEdge', 'Right edge')}
|
|
1368
1463
|
>
|
|
1369
|
-
|
|
1464
|
+
<NiceIcon name="align-right" size={14} />
|
|
1370
1465
|
</button>
|
|
1371
1466
|
</div>
|
|
1372
1467
|
</div>
|
|
@@ -1394,7 +1489,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1394
1489
|
</select>
|
|
1395
1490
|
</div>
|
|
1396
1491
|
<div className={styles.styleRow}>
|
|
1397
|
-
<label className={styles.styleLabel}>
|
|
1492
|
+
<label className={styles.styleLabel}>{tr('printing.editor.size', 'Size')}</label>
|
|
1398
1493
|
<select
|
|
1399
1494
|
className={styles.styleSelectSm}
|
|
1400
1495
|
value={
|
|
@@ -1422,22 +1517,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1422
1517
|
<span className={styles.styleUnit}>pt</span>
|
|
1423
1518
|
</div>
|
|
1424
1519
|
<div className={styles.styleRow}>
|
|
1425
|
-
<label className={styles.styleLabel}>
|
|
1520
|
+
<label className={styles.styleLabel}>
|
|
1521
|
+
{tr('printing.editor.colour', 'Colour')}
|
|
1522
|
+
</label>
|
|
1426
1523
|
<input
|
|
1427
1524
|
type="color"
|
|
1428
1525
|
className={styles.colorPicker}
|
|
1429
|
-
value={field.style.color ??
|
|
1526
|
+
value={field.style.color ?? INK}
|
|
1430
1527
|
onChange={(e) => s({ color: e.target.value })}
|
|
1431
1528
|
/>
|
|
1432
1529
|
<input
|
|
1433
1530
|
className={styles.colorHex}
|
|
1434
|
-
value={field.style.color ??
|
|
1531
|
+
value={field.style.color ?? INK}
|
|
1435
1532
|
onChange={(e) => s({ color: e.target.value })}
|
|
1436
1533
|
maxLength={7}
|
|
1437
1534
|
/>
|
|
1438
1535
|
</div>
|
|
1439
1536
|
<div className={styles.styleRow}>
|
|
1440
|
-
<label className={styles.styleLabel}>
|
|
1537
|
+
<label className={styles.styleLabel}>
|
|
1538
|
+
{tr('printing.editor.style', 'Style')}
|
|
1539
|
+
</label>
|
|
1441
1540
|
<div className={styles.toggleGroup}>
|
|
1442
1541
|
<button
|
|
1443
1542
|
className={`${styles.toggleBtn} ${field.style.fontWeight === 'bold' ? styles.toggleBtnActive : ''}`}
|
|
@@ -1458,7 +1557,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1458
1557
|
</div>
|
|
1459
1558
|
</div>
|
|
1460
1559
|
<div className={styles.styleRow}>
|
|
1461
|
-
<label className={styles.styleLabel}>
|
|
1560
|
+
<label className={styles.styleLabel}>
|
|
1561
|
+
{tr('printing.editor.align', 'Align')}
|
|
1562
|
+
</label>
|
|
1462
1563
|
<div className={styles.toggleGroup}>
|
|
1463
1564
|
{(['left', 'center', 'right', 'justify'] as const).map((a) => (
|
|
1464
1565
|
<button
|
|
@@ -1485,7 +1586,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1485
1586
|
/>
|
|
1486
1587
|
</div>
|
|
1487
1588
|
<div className={styles.styleRow}>
|
|
1488
|
-
<label className={styles.styleLabel}>
|
|
1589
|
+
<label className={styles.styleLabel}>
|
|
1590
|
+
{tr('printing.editor.spacing', 'Spacing')}
|
|
1591
|
+
</label>
|
|
1489
1592
|
<input
|
|
1490
1593
|
type="number"
|
|
1491
1594
|
step={0.01}
|
|
@@ -1501,9 +1604,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1501
1604
|
)}
|
|
1502
1605
|
|
|
1503
1606
|
<div className={styles.styleGroup}>
|
|
1504
|
-
<div className={styles.styleGroupTitle}>
|
|
1607
|
+
<div className={styles.styleGroupTitle}>
|
|
1608
|
+
{tr('printing.editor.background', 'Background and border')}
|
|
1609
|
+
</div>
|
|
1505
1610
|
<div className={styles.styleRow}>
|
|
1506
|
-
<label className={styles.styleLabel}>
|
|
1611
|
+
<label className={styles.styleLabel}>{tr('printing.editor.fill', 'Fill')}</label>
|
|
1507
1612
|
<input
|
|
1508
1613
|
type="color"
|
|
1509
1614
|
className={styles.colorPicker}
|
|
@@ -1513,9 +1618,9 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1513
1618
|
<button
|
|
1514
1619
|
className={styles.clearBtn}
|
|
1515
1620
|
onClick={() => s({ backgroundColor: undefined })}
|
|
1516
|
-
title=
|
|
1621
|
+
title={tr('printing.editor.clearFill', 'Clear fill')}
|
|
1517
1622
|
>
|
|
1518
|
-
|
|
1623
|
+
<NiceIcon name="close" size={12} />
|
|
1519
1624
|
</button>
|
|
1520
1625
|
</div>
|
|
1521
1626
|
<div className={styles.styleRow}>
|
|
@@ -1532,7 +1637,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1532
1637
|
<input
|
|
1533
1638
|
type="color"
|
|
1534
1639
|
className={styles.colorPicker}
|
|
1535
|
-
value={field.style.borderColor ??
|
|
1640
|
+
value={field.style.borderColor ?? INK}
|
|
1536
1641
|
onChange={(e) => s({ borderColor: e.target.value })}
|
|
1537
1642
|
/>
|
|
1538
1643
|
<select
|
|
@@ -1593,9 +1698,11 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1593
1698
|
{/* โโ Data โโ */}
|
|
1594
1699
|
{activeTab === 'data' && hasData && (
|
|
1595
1700
|
<div className={styles.styleGroup}>
|
|
1596
|
-
<div className={styles.styleGroupTitle}
|
|
1701
|
+
<div className={styles.styleGroupTitle}>
|
|
1702
|
+
{tr('printing.editor.dataSource', 'Data source')}
|
|
1703
|
+
</div>
|
|
1597
1704
|
<div className={styles.styleRow}>
|
|
1598
|
-
<label className={styles.styleLabel}
|
|
1705
|
+
<label className={styles.styleLabel}>{tr('printing.editor.path', 'Path')}</label>
|
|
1599
1706
|
<input
|
|
1600
1707
|
className={styles.styleInputFull}
|
|
1601
1708
|
value={field.dataPath}
|
|
@@ -1604,7 +1711,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1604
1711
|
/>
|
|
1605
1712
|
</div>
|
|
1606
1713
|
<div className={styles.styleRow}>
|
|
1607
|
-
<label className={styles.styleLabel}>Format</label>
|
|
1714
|
+
<label className={styles.styleLabel}>{tr('printing.editor.format', 'Format')}</label>
|
|
1608
1715
|
<input
|
|
1609
1716
|
className={styles.styleInputFull}
|
|
1610
1717
|
value={field.format ?? ''}
|
|
@@ -1613,31 +1720,35 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1613
1720
|
/>
|
|
1614
1721
|
</div>
|
|
1615
1722
|
<div className={styles.styleRow}>
|
|
1616
|
-
<label className={styles.styleLabel}>
|
|
1723
|
+
<label className={styles.styleLabel}>
|
|
1724
|
+
{tr('printing.editor.default', 'Default')}
|
|
1725
|
+
</label>
|
|
1617
1726
|
<input
|
|
1618
1727
|
className={styles.styleInputFull}
|
|
1619
1728
|
value={field.fallback ?? ''}
|
|
1620
1729
|
onChange={(e) => onChange({ fallback: e.target.value })}
|
|
1621
|
-
placeholder=
|
|
1730
|
+
placeholder={tr('printing.editor.whenEmpty', 'When there is no value')}
|
|
1622
1731
|
/>
|
|
1623
1732
|
</div>
|
|
1624
1733
|
<div className={styles.styleRow}>
|
|
1625
|
-
<label className={styles.styleLabel}>
|
|
1734
|
+
<label className={styles.styleLabel}>{tr('printing.editor.type', 'Type')}</label>
|
|
1626
1735
|
<select
|
|
1627
1736
|
className={styles.styleSelectFull}
|
|
1628
1737
|
value={field.type}
|
|
1629
1738
|
onChange={(e) => onChange({ type: e.target.value as PrintDataField['type'] })}
|
|
1630
1739
|
>
|
|
1631
|
-
{Object.
|
|
1740
|
+
{Object.keys(FIELD_TYPE_LABELS).map((k) => (
|
|
1632
1741
|
<option key={k} value={k}>
|
|
1633
|
-
{
|
|
1742
|
+
{fieldTypeLabel(tr, k)}
|
|
1634
1743
|
</option>
|
|
1635
1744
|
))}
|
|
1636
1745
|
</select>
|
|
1637
1746
|
</div>
|
|
1638
1747
|
{(field.type === 'barcode' || field.type === 'qr') && (
|
|
1639
1748
|
<div className={styles.styleRow}>
|
|
1640
|
-
<label className={styles.styleLabel}>
|
|
1749
|
+
<label className={styles.styleLabel}>
|
|
1750
|
+
{tr('printing.editor.codeFormat', 'Code format')}
|
|
1751
|
+
</label>
|
|
1641
1752
|
<select
|
|
1642
1753
|
className={styles.styleSelectFull}
|
|
1643
1754
|
value={field.barcodeType ?? 'code128'}
|
|
@@ -1657,12 +1768,19 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1657
1768
|
{/* โโ Conditional โโ */}
|
|
1658
1769
|
{activeTab === 'conditional' && (
|
|
1659
1770
|
<div className={styles.styleGroup}>
|
|
1660
|
-
<div className={styles.styleGroupTitle}>
|
|
1771
|
+
<div className={styles.styleGroupTitle}>
|
|
1772
|
+
{tr('printing.editor.conditionalVisibility', 'Conditional visibility')}
|
|
1773
|
+
</div>
|
|
1661
1774
|
<div className={styles.conditionalHint}>
|
|
1662
|
-
|
|
1775
|
+
{tr(
|
|
1776
|
+
'printing.editor.conditionalHint',
|
|
1777
|
+
'The element is hidden when the condition is not met.',
|
|
1778
|
+
)}
|
|
1663
1779
|
</div>
|
|
1664
1780
|
<div className={styles.styleRow}>
|
|
1665
|
-
<label className={styles.styleLabel}>
|
|
1781
|
+
<label className={styles.styleLabel}>
|
|
1782
|
+
{tr('printing.editor.conditionalField', 'Field')}
|
|
1783
|
+
</label>
|
|
1666
1784
|
<input
|
|
1667
1785
|
className={styles.styleInputFull}
|
|
1668
1786
|
value={field.conditional?.field ?? ''}
|
|
@@ -1695,15 +1813,15 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1695
1813
|
})
|
|
1696
1814
|
}
|
|
1697
1815
|
>
|
|
1698
|
-
<option value="eq">=
|
|
1699
|
-
<option value="neq">โ
|
|
1816
|
+
<option value="eq">= {tr('printing.editor.opEqual', 'equal')}</option>
|
|
1817
|
+
<option value="neq">โ {tr('printing.editor.opNotEqual', 'not equal')}</option>
|
|
1700
1818
|
<option value="gt">> wieksze</option>
|
|
1701
1819
|
<option value="lt">< mniejsze</option>
|
|
1702
1820
|
<option value="contains">zawiera</option>
|
|
1703
1821
|
</select>
|
|
1704
1822
|
</div>
|
|
1705
1823
|
<div className={styles.styleRow}>
|
|
1706
|
-
<label className={styles.styleLabel}>
|
|
1824
|
+
<label className={styles.styleLabel}>{tr('printing.editor.value', 'Value')}</label>
|
|
1707
1825
|
<input
|
|
1708
1826
|
className={styles.styleInputFull}
|
|
1709
1827
|
value={String(field.conditional?.value ?? '')}
|
|
@@ -1724,7 +1842,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1724
1842
|
className={styles.clearConditionalBtn}
|
|
1725
1843
|
onClick={() => onChange({ conditional: undefined })}
|
|
1726
1844
|
>
|
|
1727
|
-
|
|
1845
|
+
{tr('printing.editor.clearCondition', 'Clear condition')}
|
|
1728
1846
|
</button>
|
|
1729
1847
|
)}
|
|
1730
1848
|
</div>
|
|
@@ -1738,7 +1856,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|
|
1738
1856
|
function defaultTemplate(): PrintTemplate {
|
|
1739
1857
|
return {
|
|
1740
1858
|
id: `tpl-${Date.now()}`,
|
|
1741
|
-
name: '
|
|
1859
|
+
name: 'New template',
|
|
1742
1860
|
description: '',
|
|
1743
1861
|
paperSize: 'A4',
|
|
1744
1862
|
orientation: 'portrait',
|
|
@@ -1770,6 +1888,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
1770
1888
|
present: initialTemplate ?? defaultTemplate(),
|
|
1771
1889
|
future: [],
|
|
1772
1890
|
});
|
|
1891
|
+
const { t: tr } = useNiceTranslation();
|
|
1773
1892
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
1774
1893
|
const [activeSection, _setActiveSection] = useState('sec-body');
|
|
1775
1894
|
const [zoom, setZoom] = useState(1);
|
|
@@ -1842,11 +1961,11 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
1842
1961
|
{
|
|
1843
1962
|
id: uid(),
|
|
1844
1963
|
type: 'text',
|
|
1845
|
-
text: '
|
|
1964
|
+
text: 'COPY',
|
|
1846
1965
|
opacity: 0.12,
|
|
1847
1966
|
angle: -45,
|
|
1848
1967
|
fontSize: 60,
|
|
1849
|
-
color:
|
|
1968
|
+
color: INK_FAINT,
|
|
1850
1969
|
},
|
|
1851
1970
|
],
|
|
1852
1971
|
});
|
|
@@ -1859,7 +1978,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
1859
1978
|
...(tpl.signatures ?? []),
|
|
1860
1979
|
{
|
|
1861
1980
|
id: uid(),
|
|
1862
|
-
label: '
|
|
1981
|
+
label: tr('printing.editor.signature', 'Signature'),
|
|
1863
1982
|
position: { x: 10, y: 5, width: 70, height: 22 },
|
|
1864
1983
|
showLine: true,
|
|
1865
1984
|
showDate: false,
|
|
@@ -2315,7 +2434,9 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
2315
2434
|
{/* Test data bar */}
|
|
2316
2435
|
{Array.isArray(sampleData) && sampleData.length > 0 && (
|
|
2317
2436
|
<div className={styles.testDataBar}>
|
|
2318
|
-
<span className={styles.testDataLabel}>
|
|
2437
|
+
<span className={styles.testDataLabel}>
|
|
2438
|
+
{tr('printing.editor.testData', 'Sample data')}:
|
|
2439
|
+
</span>
|
|
2319
2440
|
<select
|
|
2320
2441
|
className={styles.testDataSelect}
|
|
2321
2442
|
value={testDataIdx}
|
|
@@ -2323,7 +2444,7 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
2323
2444
|
>
|
|
2324
2445
|
{sampleData.map((_, i) => (
|
|
2325
2446
|
<option key={i} value={i}>
|
|
2326
|
-
|
|
2447
|
+
{tr('printing.editor.record', 'Record')} {i + 1}
|
|
2327
2448
|
</option>
|
|
2328
2449
|
))}
|
|
2329
2450
|
</select>
|
|
@@ -2360,12 +2481,16 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
|
|
|
2360
2481
|
className={styles.watermark}
|
|
2361
2482
|
style={{
|
|
2362
2483
|
fontSize: (wm.fontSize ?? 60) * zoom,
|
|
2363
|
-
color: wm.color ??
|
|
2484
|
+
color: wm.color ?? INK_FAINT,
|
|
2364
2485
|
opacity: wm.opacity,
|
|
2365
2486
|
transform: `translate(-50%, -50%) rotate(${wm.angle}deg)`,
|
|
2366
2487
|
}}
|
|
2367
2488
|
>
|
|
2368
|
-
{wm.type === 'text' ?
|
|
2489
|
+
{wm.type === 'text' ? (
|
|
2490
|
+
wm.text
|
|
2491
|
+
) : (
|
|
2492
|
+
<NiceIcon name="image" size={(wm.fontSize ?? 60) * zoom} />
|
|
2493
|
+
)}
|
|
2369
2494
|
</div>
|
|
2370
2495
|
))}
|
|
2371
2496
|
|