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