@zentto/report-designer 0.5.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
// @zentto/report-designer — WYSIWYG report designer web component
|
|
8
8
|
import { LitElement, html, css, nothing } from 'lit';
|
|
9
9
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
|
-
import { createBlankLayout, toPx } from '@zentto/report-core';
|
|
10
|
+
import { createBlankLayout, toPx, PAGE_SIZES } from '@zentto/report-core';
|
|
11
11
|
const BAND_LABELS = {
|
|
12
12
|
reportHeader: 'Report Header',
|
|
13
13
|
pageHeader: 'Page Header',
|
|
@@ -103,6 +103,13 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
103
103
|
this._editingElementId = null;
|
|
104
104
|
this._contextMenu = null;
|
|
105
105
|
this._zoom = 1.5; // Default 150% for comfortable editing
|
|
106
|
+
this._showPageSetup = false;
|
|
107
|
+
this._showBandMenu = false;
|
|
108
|
+
this._mobileLeftOpen = false;
|
|
109
|
+
this._mobileRightOpen = false;
|
|
110
|
+
this._guides = { x: [], y: [] };
|
|
111
|
+
this._collapsedSections = new Set();
|
|
112
|
+
this._pageSetupDraft = null;
|
|
106
113
|
this._clipboard = [];
|
|
107
114
|
this._saveTimer = null;
|
|
108
115
|
this._idCounter = 0;
|
|
@@ -127,6 +134,14 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
127
134
|
get _unit() {
|
|
128
135
|
return this._layout.pageSize.unit;
|
|
129
136
|
}
|
|
137
|
+
_toggleSection(section) {
|
|
138
|
+
const next = new Set(this._collapsedSections);
|
|
139
|
+
if (next.has(section))
|
|
140
|
+
next.delete(section);
|
|
141
|
+
else
|
|
142
|
+
next.add(section);
|
|
143
|
+
this._collapsedSections = next;
|
|
144
|
+
}
|
|
130
145
|
// Helper: single selected element id (for property panel, backward compat)
|
|
131
146
|
get _selectedElementId() {
|
|
132
147
|
if (this._selectedElementIds.size === 1) {
|
|
@@ -158,7 +173,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
158
173
|
.designer {
|
|
159
174
|
display: grid;
|
|
160
175
|
grid-template-columns: 220px 1fr 260px;
|
|
161
|
-
grid-template-rows: auto 1fr;
|
|
176
|
+
grid-template-rows: auto auto 1fr;
|
|
162
177
|
height: 100%;
|
|
163
178
|
background: var(--zrd-bg);
|
|
164
179
|
color: var(--zrd-text);
|
|
@@ -173,6 +188,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
173
188
|
padding: 6px 12px;
|
|
174
189
|
background: var(--zrd-panel-bg);
|
|
175
190
|
border-bottom: 1px solid var(--zrd-border);
|
|
191
|
+
flex-wrap: wrap;
|
|
192
|
+
min-height: 36px;
|
|
176
193
|
}
|
|
177
194
|
.top-toolbar button {
|
|
178
195
|
background: none;
|
|
@@ -284,10 +301,12 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
284
301
|
background: #d0d0d0;
|
|
285
302
|
display: flex;
|
|
286
303
|
justify-content: center;
|
|
304
|
+
-webkit-overflow-scrolling: touch;
|
|
287
305
|
}
|
|
288
306
|
.canvas-scroll-container {
|
|
289
307
|
transform-origin: top center;
|
|
290
308
|
transition: transform 0.1s ease;
|
|
309
|
+
min-width: min-content;
|
|
291
310
|
}
|
|
292
311
|
.canvas-wrapper {
|
|
293
312
|
position: relative;
|
|
@@ -380,6 +399,7 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
380
399
|
cursor: pointer;
|
|
381
400
|
user-select: none;
|
|
382
401
|
gap: 6px;
|
|
402
|
+
overflow: hidden;
|
|
383
403
|
}
|
|
384
404
|
.band-header:hover { filter: brightness(0.95); }
|
|
385
405
|
.band-header.selected { box-shadow: inset 0 0 0 2px var(--zrd-accent); color: var(--zrd-accent); }
|
|
@@ -387,6 +407,10 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
387
407
|
display: flex;
|
|
388
408
|
align-items: center;
|
|
389
409
|
gap: 6px;
|
|
410
|
+
overflow: hidden;
|
|
411
|
+
white-space: nowrap;
|
|
412
|
+
text-overflow: ellipsis;
|
|
413
|
+
min-width: 0;
|
|
390
414
|
}
|
|
391
415
|
.band-type-icon {
|
|
392
416
|
font-size: 13px;
|
|
@@ -501,79 +525,306 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
501
525
|
box-sizing: border-box;
|
|
502
526
|
}
|
|
503
527
|
|
|
504
|
-
/* ─── Right Panel (Properties) ─── */
|
|
528
|
+
/* ─── Right Panel (Figma-style Properties) ─── */
|
|
505
529
|
.right-panel {
|
|
506
530
|
background: var(--zrd-panel-bg);
|
|
507
531
|
border-left: 1px solid var(--zrd-border);
|
|
508
532
|
overflow-y: auto;
|
|
509
|
-
|
|
533
|
+
overflow-x: hidden;
|
|
534
|
+
scrollbar-width: thin;
|
|
535
|
+
scrollbar-color: #ccc transparent;
|
|
510
536
|
}
|
|
537
|
+
.right-panel::-webkit-scrollbar { width: 6px; }
|
|
538
|
+
.right-panel::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
539
|
+
|
|
511
540
|
.prop-section {
|
|
512
|
-
|
|
541
|
+
border-bottom: 1px solid var(--zrd-border);
|
|
542
|
+
padding: 10px 12px;
|
|
513
543
|
}
|
|
514
|
-
.prop-section
|
|
544
|
+
.prop-section:last-child { border-bottom: none; }
|
|
545
|
+
|
|
546
|
+
.prop-section-header {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
justify-content: space-between;
|
|
550
|
+
cursor: pointer;
|
|
551
|
+
user-select: none;
|
|
552
|
+
margin-bottom: 8px;
|
|
553
|
+
}
|
|
554
|
+
.prop-section-header h4 {
|
|
515
555
|
font-size: 11px;
|
|
556
|
+
font-weight: 600;
|
|
516
557
|
text-transform: uppercase;
|
|
558
|
+
letter-spacing: 0.5px;
|
|
559
|
+
color: var(--zrd-text);
|
|
560
|
+
margin: 0;
|
|
561
|
+
}
|
|
562
|
+
.prop-section-header .collapse-icon {
|
|
563
|
+
font-size: 10px;
|
|
517
564
|
color: var(--zrd-text-muted);
|
|
518
|
-
|
|
565
|
+
transition: transform 0.15s;
|
|
566
|
+
}
|
|
567
|
+
.prop-section-header .collapse-icon.collapsed {
|
|
568
|
+
transform: rotate(-90deg);
|
|
569
|
+
}
|
|
570
|
+
.prop-section-body {
|
|
571
|
+
display: grid;
|
|
572
|
+
gap: 4px;
|
|
573
|
+
}
|
|
574
|
+
.prop-section-body.collapsed {
|
|
575
|
+
display: none;
|
|
519
576
|
}
|
|
577
|
+
|
|
578
|
+
/* ─── Property Rows ─── */
|
|
520
579
|
.prop-row {
|
|
521
|
-
display:
|
|
580
|
+
display: grid;
|
|
581
|
+
grid-template-columns: 68px 1fr;
|
|
522
582
|
align-items: center;
|
|
523
|
-
gap:
|
|
524
|
-
|
|
583
|
+
gap: 6px;
|
|
584
|
+
min-height: 28px;
|
|
585
|
+
}
|
|
586
|
+
.prop-row-full {
|
|
587
|
+
grid-template-columns: 1fr;
|
|
525
588
|
}
|
|
526
589
|
.prop-label {
|
|
527
|
-
width: 70px;
|
|
528
590
|
font-size: 11px;
|
|
529
591
|
color: var(--zrd-text-muted);
|
|
530
|
-
|
|
592
|
+
white-space: nowrap;
|
|
593
|
+
overflow: hidden;
|
|
594
|
+
text-overflow: ellipsis;
|
|
595
|
+
padding-left: 2px;
|
|
531
596
|
}
|
|
597
|
+
|
|
598
|
+
/* ─── Inputs (Figma-style) ─── */
|
|
532
599
|
.prop-input {
|
|
533
|
-
|
|
534
|
-
border: 1px solid
|
|
535
|
-
border-radius:
|
|
536
|
-
padding:
|
|
600
|
+
width: 100%;
|
|
601
|
+
border: 1px solid transparent;
|
|
602
|
+
border-radius: 4px;
|
|
603
|
+
padding: 4px 8px;
|
|
537
604
|
font-size: 12px;
|
|
538
|
-
background:
|
|
605
|
+
background: var(--zrd-bg);
|
|
539
606
|
color: var(--zrd-text);
|
|
607
|
+
transition: border-color 0.15s, background 0.15s;
|
|
608
|
+
outline: none;
|
|
609
|
+
min-width: 0;
|
|
540
610
|
}
|
|
541
|
-
.prop-input:
|
|
542
|
-
|
|
543
|
-
|
|
611
|
+
.prop-input:hover {
|
|
612
|
+
border-color: var(--zrd-border);
|
|
613
|
+
}
|
|
614
|
+
.prop-input:focus {
|
|
615
|
+
border-color: var(--zrd-accent);
|
|
616
|
+
background: white;
|
|
617
|
+
}
|
|
618
|
+
select.prop-input {
|
|
619
|
+
padding: 3px 6px;
|
|
620
|
+
cursor: pointer;
|
|
621
|
+
appearance: none;
|
|
622
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
|
|
623
|
+
background-repeat: no-repeat;
|
|
624
|
+
background-position: right 6px center;
|
|
625
|
+
padding-right: 20px;
|
|
626
|
+
}
|
|
627
|
+
textarea.prop-input {
|
|
628
|
+
min-height: 52px;
|
|
629
|
+
resize: vertical;
|
|
630
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
631
|
+
font-size: 11px;
|
|
632
|
+
line-height: 1.5;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* ─── Compact number inputs (X, Y, W, H grid) ─── */
|
|
636
|
+
.prop-grid-2x2 {
|
|
637
|
+
display: grid;
|
|
638
|
+
grid-template-columns: 1fr 1fr;
|
|
639
|
+
gap: 4px;
|
|
640
|
+
}
|
|
641
|
+
.prop-grid-item {
|
|
642
|
+
display: flex;
|
|
643
|
+
align-items: center;
|
|
644
|
+
background: var(--zrd-bg);
|
|
645
|
+
border: 1px solid transparent;
|
|
646
|
+
border-radius: 4px;
|
|
647
|
+
padding: 2px 6px;
|
|
648
|
+
gap: 4px;
|
|
649
|
+
transition: border-color 0.15s;
|
|
650
|
+
}
|
|
651
|
+
.prop-grid-item:hover { border-color: var(--zrd-border); }
|
|
652
|
+
.prop-grid-item:focus-within { border-color: var(--zrd-accent); }
|
|
653
|
+
.prop-grid-item .prop-grid-label {
|
|
544
654
|
font-size: 10px;
|
|
545
|
-
color: var(--zrd-
|
|
546
|
-
|
|
655
|
+
color: var(--zrd-accent);
|
|
656
|
+
font-weight: 600;
|
|
657
|
+
width: 12px;
|
|
658
|
+
text-align: center;
|
|
547
659
|
flex-shrink: 0;
|
|
548
660
|
}
|
|
661
|
+
.prop-grid-item input {
|
|
662
|
+
border: none;
|
|
663
|
+
background: transparent;
|
|
664
|
+
width: 100%;
|
|
665
|
+
font-size: 12px;
|
|
666
|
+
color: var(--zrd-text);
|
|
667
|
+
outline: none;
|
|
668
|
+
padding: 2px 0;
|
|
669
|
+
min-width: 0;
|
|
670
|
+
}
|
|
549
671
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
672
|
+
/* ─── Color picker (Figma-style swatch + input) ─── */
|
|
673
|
+
.prop-color-row {
|
|
674
|
+
display: flex;
|
|
675
|
+
align-items: center;
|
|
676
|
+
gap: 6px;
|
|
677
|
+
}
|
|
678
|
+
.prop-color-swatch {
|
|
679
|
+
width: 24px;
|
|
680
|
+
height: 24px;
|
|
681
|
+
border-radius: 4px;
|
|
682
|
+
border: 1px solid var(--zrd-border);
|
|
683
|
+
cursor: pointer;
|
|
684
|
+
flex-shrink: 0;
|
|
685
|
+
position: relative;
|
|
686
|
+
overflow: hidden;
|
|
687
|
+
}
|
|
688
|
+
.prop-color-swatch input[type="color"] {
|
|
689
|
+
position: absolute;
|
|
690
|
+
inset: -4px;
|
|
691
|
+
width: calc(100% + 8px);
|
|
692
|
+
height: calc(100% + 8px);
|
|
693
|
+
cursor: pointer;
|
|
553
694
|
border: none;
|
|
695
|
+
padding: 0;
|
|
696
|
+
}
|
|
697
|
+
.prop-color-hex {
|
|
698
|
+
border: 1px solid transparent;
|
|
554
699
|
border-radius: 4px;
|
|
555
|
-
padding: 4px
|
|
700
|
+
padding: 4px 8px;
|
|
701
|
+
font-size: 11px;
|
|
702
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
703
|
+
background: var(--zrd-bg);
|
|
704
|
+
color: var(--zrd-text);
|
|
705
|
+
width: 80px;
|
|
706
|
+
outline: none;
|
|
707
|
+
}
|
|
708
|
+
.prop-color-hex:hover { border-color: var(--zrd-border); }
|
|
709
|
+
.prop-color-hex:focus { border-color: var(--zrd-accent); }
|
|
710
|
+
|
|
711
|
+
/* ─── Toggle switches ─── */
|
|
712
|
+
.prop-toggle {
|
|
713
|
+
display: flex;
|
|
714
|
+
align-items: center;
|
|
715
|
+
gap: 8px;
|
|
716
|
+
}
|
|
717
|
+
.prop-switch {
|
|
718
|
+
position: relative;
|
|
719
|
+
width: 32px;
|
|
720
|
+
height: 18px;
|
|
721
|
+
border-radius: 9px;
|
|
722
|
+
background: #ccc;
|
|
723
|
+
cursor: pointer;
|
|
724
|
+
transition: background 0.2s;
|
|
725
|
+
flex-shrink: 0;
|
|
726
|
+
}
|
|
727
|
+
.prop-switch.active { background: var(--zrd-accent); }
|
|
728
|
+
.prop-switch::after {
|
|
729
|
+
content: '';
|
|
730
|
+
position: absolute;
|
|
731
|
+
top: 2px;
|
|
732
|
+
left: 2px;
|
|
733
|
+
width: 14px;
|
|
734
|
+
height: 14px;
|
|
735
|
+
border-radius: 50%;
|
|
736
|
+
background: white;
|
|
737
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
738
|
+
transition: transform 0.2s;
|
|
739
|
+
}
|
|
740
|
+
.prop-switch.active::after { transform: translateX(14px); }
|
|
741
|
+
|
|
742
|
+
/* ─── Segmented buttons (alignment, etc.) ─── */
|
|
743
|
+
.prop-segmented {
|
|
744
|
+
display: flex;
|
|
745
|
+
border: 1px solid var(--zrd-border);
|
|
746
|
+
border-radius: 4px;
|
|
747
|
+
overflow: hidden;
|
|
748
|
+
}
|
|
749
|
+
.prop-seg-btn {
|
|
750
|
+
flex: 1;
|
|
751
|
+
padding: 4px 0;
|
|
752
|
+
background: var(--zrd-bg);
|
|
753
|
+
border: none;
|
|
754
|
+
border-right: 1px solid var(--zrd-border);
|
|
755
|
+
cursor: pointer;
|
|
756
|
+
font-size: 12px;
|
|
757
|
+
color: var(--zrd-text-muted);
|
|
758
|
+
transition: background 0.15s;
|
|
759
|
+
text-align: center;
|
|
760
|
+
}
|
|
761
|
+
.prop-seg-btn:last-child { border-right: none; }
|
|
762
|
+
.prop-seg-btn:hover { background: var(--zrd-accent-light); }
|
|
763
|
+
.prop-seg-btn.active { background: var(--zrd-accent); color: white; }
|
|
764
|
+
|
|
765
|
+
/* ─── Font size + family row ─── */
|
|
766
|
+
.prop-font-row {
|
|
767
|
+
display: grid;
|
|
768
|
+
grid-template-columns: 1fr 60px;
|
|
769
|
+
gap: 4px;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/* ─── Delete button ─── */
|
|
773
|
+
.btn-danger {
|
|
774
|
+
background: none;
|
|
775
|
+
color: var(--zrd-danger);
|
|
776
|
+
border: 1px solid var(--zrd-danger);
|
|
777
|
+
border-radius: 4px;
|
|
778
|
+
padding: 6px 10px;
|
|
556
779
|
cursor: pointer;
|
|
557
780
|
font-size: 12px;
|
|
558
781
|
width: 100%;
|
|
782
|
+
transition: background 0.15s;
|
|
559
783
|
}
|
|
560
|
-
.btn-danger:hover {
|
|
784
|
+
.btn-danger:hover { background: var(--zrd-danger); color: white; }
|
|
561
785
|
|
|
786
|
+
/* ─── Z-order buttons ─── */
|
|
787
|
+
.z-order-buttons {
|
|
788
|
+
display: flex;
|
|
789
|
+
gap: 2px;
|
|
790
|
+
}
|
|
562
791
|
.btn-small {
|
|
563
|
-
background:
|
|
564
|
-
border: 1px solid
|
|
565
|
-
border-radius:
|
|
566
|
-
padding:
|
|
792
|
+
background: var(--zrd-bg);
|
|
793
|
+
border: 1px solid transparent;
|
|
794
|
+
border-radius: 4px;
|
|
795
|
+
padding: 4px 8px;
|
|
567
796
|
cursor: pointer;
|
|
568
797
|
font-size: 11px;
|
|
569
798
|
color: var(--zrd-text);
|
|
799
|
+
transition: all 0.15s;
|
|
570
800
|
}
|
|
571
|
-
.btn-small:hover { background: var(--zrd-accent-light); }
|
|
801
|
+
.btn-small:hover { background: var(--zrd-accent-light); border-color: var(--zrd-border); }
|
|
572
802
|
|
|
573
|
-
.
|
|
574
|
-
|
|
803
|
+
.prop-unit {
|
|
804
|
+
font-size: 10px;
|
|
805
|
+
color: var(--zrd-text-muted);
|
|
806
|
+
width: 20px;
|
|
807
|
+
flex-shrink: 0;
|
|
808
|
+
text-align: center;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/* ─── Section-specific colors ─── */
|
|
812
|
+
.prop-section[data-section="position"] .prop-section-header h4 { color: var(--zrd-accent); }
|
|
813
|
+
.prop-section[data-section="style"] .prop-section-header h4 { color: #7c3aed; }
|
|
814
|
+
.prop-section[data-section="visibility"] .prop-section-header h4 { color: #0d9488; }
|
|
815
|
+
.prop-section[data-section="data"] .prop-section-header h4 { color: #ea580c; }
|
|
816
|
+
|
|
817
|
+
/* ─── Element type badge ─── */
|
|
818
|
+
.element-type-badge {
|
|
819
|
+
display: inline-flex;
|
|
820
|
+
align-items: center;
|
|
575
821
|
gap: 4px;
|
|
576
|
-
|
|
822
|
+
padding: 3px 10px;
|
|
823
|
+
border-radius: 10px;
|
|
824
|
+
font-size: 11px;
|
|
825
|
+
font-weight: 600;
|
|
826
|
+
text-transform: uppercase;
|
|
827
|
+
letter-spacing: 0.3px;
|
|
577
828
|
}
|
|
578
829
|
|
|
579
830
|
/* ─── Data Source Panel ─── */
|
|
@@ -603,6 +854,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
603
854
|
z-index: 1000;
|
|
604
855
|
min-width: 160px;
|
|
605
856
|
padding: 4px 0;
|
|
857
|
+
max-height: calc(100vh - 40px);
|
|
858
|
+
overflow-y: auto;
|
|
606
859
|
}
|
|
607
860
|
.context-menu-item {
|
|
608
861
|
padding: 6px 12px;
|
|
@@ -629,67 +882,367 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
629
882
|
inset: 0;
|
|
630
883
|
z-index: 999;
|
|
631
884
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
885
|
+
|
|
886
|
+
/* ─── Dialog ─── */
|
|
887
|
+
.dialog-overlay { position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:center;justify-content:center; }
|
|
888
|
+
.dialog { background:white;border-radius:8px;box-shadow:0 20px 60px rgba(0,0,0,0.3);min-width:480px;max-width:600px; }
|
|
889
|
+
.dialog-header { padding:16px 20px;border-bottom:1px solid var(--zrd-border);font-weight:600;font-size:15px;display:flex;justify-content:space-between;align-items:center; }
|
|
890
|
+
.dialog-body { padding:20px; }
|
|
891
|
+
.dialog-footer { padding:12px 20px;border-top:1px solid var(--zrd-border);display:flex;justify-content:flex-end;gap:8px; }
|
|
892
|
+
.dialog-btn { padding:8px 16px;border-radius:4px;border:1px solid var(--zrd-border);cursor:pointer;font-size:13px;background:white; }
|
|
893
|
+
.dialog-btn-primary { background:var(--zrd-accent);color:white;border-color:var(--zrd-accent); }
|
|
894
|
+
.dialog-btn-primary:hover { opacity:0.9; }
|
|
895
|
+
.dialog-row { display:flex;gap:12px;margin-bottom:12px;align-items:center; }
|
|
896
|
+
.dialog-label { font-size:12px;font-weight:600;color:var(--zrd-text-muted);min-width:70px; }
|
|
897
|
+
.dialog-select, .dialog-input {
|
|
898
|
+
flex:1;padding:6px 8px;border:1px solid var(--zrd-border);border-radius:4px;font-size:13px;
|
|
899
|
+
font-family:inherit;color:var(--zrd-text);
|
|
900
|
+
}
|
|
901
|
+
.dialog-input { width:60px;flex:none; }
|
|
902
|
+
.dialog-group { display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px; }
|
|
903
|
+
.dialog-group label { font-size:11px;color:var(--zrd-text-muted);display:flex;flex-direction:column;gap:2px; }
|
|
904
|
+
.dialog-group input { padding:5px 6px;border:1px solid var(--zrd-border);border-radius:3px;font-size:12px; }
|
|
905
|
+
.orientation-toggle { display:flex;gap:0; }
|
|
906
|
+
.orientation-toggle button {
|
|
907
|
+
padding:6px 14px;border:1px solid var(--zrd-border);cursor:pointer;font-size:12px;background:white;color:var(--zrd-text);
|
|
908
|
+
}
|
|
909
|
+
.orientation-toggle button:first-child { border-radius:4px 0 0 4px; }
|
|
910
|
+
.orientation-toggle button:last-child { border-radius:0 4px 4px 0;border-left:none; }
|
|
911
|
+
.orientation-toggle button.active { background:var(--zrd-accent);color:white;border-color:var(--zrd-accent); }
|
|
912
|
+
.page-preview-box {
|
|
913
|
+
width:100%;height:140px;background:#f9f9f9;border:1px solid var(--zrd-border);border-radius:4px;
|
|
914
|
+
display:flex;align-items:center;justify-content:center;position:relative;
|
|
915
|
+
}
|
|
916
|
+
.page-preview-page {
|
|
917
|
+
background:white;border:1px solid #999;position:relative;box-shadow:0 1px 4px rgba(0,0,0,0.15);
|
|
918
|
+
}
|
|
919
|
+
.page-preview-margin {
|
|
920
|
+
position:absolute;border:1px dashed rgba(25,118,210,0.4);
|
|
644
921
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
922
|
+
|
|
923
|
+
/* ─── Band Menu Dropdown ─── */
|
|
924
|
+
.band-menu-wrapper { position:relative;display:inline-block; }
|
|
925
|
+
.band-menu {
|
|
926
|
+
position:absolute;top:100%;left:0;background:white;border:1px solid var(--zrd-border);border-radius:4px;
|
|
927
|
+
box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:100;min-width:200px;padding:4px 0;
|
|
928
|
+
}
|
|
929
|
+
.band-menu-item {
|
|
930
|
+
padding:6px 12px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;
|
|
931
|
+
}
|
|
932
|
+
.band-menu-item:hover { background:var(--zrd-accent-light); }
|
|
933
|
+
.band-menu-item.disabled { opacity:0.4;cursor:default; }
|
|
934
|
+
.band-menu-cat { padding:4px 12px;font-size:10px;font-weight:600;color:var(--zrd-text-muted);text-transform:uppercase; }
|
|
935
|
+
|
|
936
|
+
/* ─── Page Setup Bar ─── */
|
|
937
|
+
.page-setup-bar {
|
|
938
|
+
grid-column: 1 / -1;
|
|
939
|
+
display: flex;
|
|
940
|
+
align-items: center;
|
|
941
|
+
gap: 12px;
|
|
942
|
+
padding: 6px 16px;
|
|
943
|
+
background: #f8f9fa;
|
|
944
|
+
border-bottom: 1px solid var(--zrd-border);
|
|
945
|
+
font-size: 12px;
|
|
946
|
+
flex-wrap: wrap;
|
|
947
|
+
min-height: 32px;
|
|
649
948
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (changed.has('dataSources') && this.dataSources.length > 0) {
|
|
655
|
-
// Sync dataSources into layout
|
|
656
|
-
this._layout = { ...this._layout, dataSources: this.dataSources };
|
|
657
|
-
}
|
|
949
|
+
.page-setup-group {
|
|
950
|
+
display: flex;
|
|
951
|
+
align-items: center;
|
|
952
|
+
gap: 4px;
|
|
658
953
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
this.requestUpdate();
|
|
664
|
-
}
|
|
954
|
+
.page-setup-label {
|
|
955
|
+
font-size: 11px;
|
|
956
|
+
color: var(--zrd-text-muted);
|
|
957
|
+
white-space: nowrap;
|
|
665
958
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
959
|
+
.page-setup-select {
|
|
960
|
+
border: 1px solid var(--zrd-border);
|
|
961
|
+
border-radius: 3px;
|
|
962
|
+
padding: 3px 6px;
|
|
963
|
+
font-size: 12px;
|
|
964
|
+
background: white;
|
|
965
|
+
color: var(--zrd-text);
|
|
966
|
+
max-width: 180px;
|
|
669
967
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
968
|
+
.page-setup-input {
|
|
969
|
+
border: 1px solid var(--zrd-border);
|
|
970
|
+
border-radius: 3px;
|
|
971
|
+
padding: 3px 4px;
|
|
972
|
+
font-size: 11px;
|
|
973
|
+
width: 42px;
|
|
974
|
+
text-align: center;
|
|
975
|
+
background: white;
|
|
676
976
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
977
|
+
.orientation-btn {
|
|
978
|
+
padding: 4px 8px;
|
|
979
|
+
border: 1px solid var(--zrd-border);
|
|
980
|
+
border-radius: 3px;
|
|
981
|
+
background: white;
|
|
982
|
+
cursor: pointer;
|
|
983
|
+
font-size: 14px;
|
|
984
|
+
line-height: 1;
|
|
685
985
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
986
|
+
.orientation-btn.active {
|
|
987
|
+
background: var(--zrd-accent);
|
|
988
|
+
color: white;
|
|
989
|
+
border-color: var(--zrd-accent);
|
|
990
|
+
}
|
|
991
|
+
.page-setup-separator {
|
|
992
|
+
width: 1px;
|
|
993
|
+
height: 20px;
|
|
994
|
+
background: var(--zrd-border);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/* ─── Smart Alignment Guides ─── */
|
|
998
|
+
.guide-line-v { position:absolute;top:0;bottom:0;width:1px;background:#ff4444;z-index:100;pointer-events:none; }
|
|
999
|
+
.guide-line-h { position:absolute;left:0;right:0;height:1px;background:#ff4444;z-index:100;pointer-events:none; }
|
|
1000
|
+
|
|
1001
|
+
/* ─── Empty State / Onboarding ─── */
|
|
1002
|
+
.band-empty-hint {
|
|
1003
|
+
position: absolute;
|
|
1004
|
+
inset: 0;
|
|
1005
|
+
display: flex;
|
|
1006
|
+
align-items: center;
|
|
1007
|
+
justify-content: center;
|
|
1008
|
+
color: #bbb;
|
|
1009
|
+
font-size: 11px;
|
|
1010
|
+
pointer-events: none;
|
|
1011
|
+
font-style: italic;
|
|
1012
|
+
}
|
|
1013
|
+
.help-card {
|
|
1014
|
+
background: #f0f7ff;
|
|
1015
|
+
border: 1px solid #c2deff;
|
|
1016
|
+
border-radius: 6px;
|
|
1017
|
+
padding: 12px;
|
|
1018
|
+
font-size: 12px;
|
|
1019
|
+
line-height: 1.6;
|
|
1020
|
+
}
|
|
1021
|
+
.help-card h4 { margin: 0 0 8px; font-size: 13px; color: var(--zrd-accent); }
|
|
1022
|
+
.help-card-section { margin-top: 10px; }
|
|
1023
|
+
.help-card kbd {
|
|
1024
|
+
background: #e8e8e8;
|
|
1025
|
+
border: 1px solid #ccc;
|
|
1026
|
+
border-radius: 3px;
|
|
1027
|
+
padding: 1px 5px;
|
|
1028
|
+
font-size: 10px;
|
|
1029
|
+
font-family: monospace;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/* ─── Mobile Panel Toggle Buttons ─── */
|
|
1033
|
+
.mobile-panel-toggles {
|
|
1034
|
+
display: none;
|
|
1035
|
+
gap: 8px;
|
|
1036
|
+
padding: 8px;
|
|
1037
|
+
}
|
|
1038
|
+
@media (max-width: 768px) {
|
|
1039
|
+
.mobile-panel-toggles {
|
|
1040
|
+
display: flex;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
.mobile-toggle-btn {
|
|
1044
|
+
padding: 6px 12px;
|
|
1045
|
+
border: 1px solid var(--zrd-border);
|
|
1046
|
+
border-radius: 4px;
|
|
1047
|
+
background: var(--zrd-panel-bg);
|
|
1048
|
+
cursor: pointer;
|
|
1049
|
+
font-size: 12px;
|
|
1050
|
+
color: var(--zrd-text);
|
|
1051
|
+
}
|
|
1052
|
+
.mobile-toggle-btn.active {
|
|
1053
|
+
background: var(--zrd-accent);
|
|
1054
|
+
color: white;
|
|
1055
|
+
border-color: var(--zrd-accent);
|
|
1056
|
+
}
|
|
1057
|
+
.mobile-overlay {
|
|
1058
|
+
position: fixed;
|
|
1059
|
+
inset: 0;
|
|
1060
|
+
background: rgba(0,0,0,0.3);
|
|
1061
|
+
z-index: 499;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/* ─── Responsive: Grid Layout ─── */
|
|
1065
|
+
@media (max-width: 1024px) {
|
|
1066
|
+
.designer {
|
|
1067
|
+
grid-template-columns: 180px 1fr 220px;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
@media (max-width: 768px) {
|
|
1071
|
+
.designer {
|
|
1072
|
+
grid-template-columns: 1fr;
|
|
1073
|
+
grid-template-rows: auto auto 1fr auto;
|
|
1074
|
+
}
|
|
1075
|
+
.left-panel {
|
|
1076
|
+
display: none;
|
|
1077
|
+
position: fixed;
|
|
1078
|
+
left: 0;
|
|
1079
|
+
top: 0;
|
|
1080
|
+
bottom: 0;
|
|
1081
|
+
width: 260px;
|
|
1082
|
+
z-index: 500;
|
|
1083
|
+
box-shadow: 4px 0 20px rgba(0,0,0,0.2);
|
|
1084
|
+
}
|
|
1085
|
+
.left-panel.mobile-open {
|
|
1086
|
+
display: block;
|
|
1087
|
+
}
|
|
1088
|
+
.right-panel {
|
|
1089
|
+
display: none;
|
|
1090
|
+
position: fixed;
|
|
1091
|
+
right: 0;
|
|
1092
|
+
top: 0;
|
|
1093
|
+
bottom: 0;
|
|
1094
|
+
width: 280px;
|
|
1095
|
+
z-index: 500;
|
|
1096
|
+
box-shadow: -4px 0 20px rgba(0,0,0,0.2);
|
|
1097
|
+
}
|
|
1098
|
+
.right-panel.mobile-open {
|
|
1099
|
+
display: block;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/* ─── Responsive: Top Toolbar ─── */
|
|
1104
|
+
@media (max-width: 768px) {
|
|
1105
|
+
.top-toolbar {
|
|
1106
|
+
padding: 4px 8px;
|
|
1107
|
+
gap: 4px;
|
|
1108
|
+
}
|
|
1109
|
+
.top-toolbar button {
|
|
1110
|
+
padding: 3px 6px;
|
|
1111
|
+
font-size: 11px;
|
|
1112
|
+
}
|
|
1113
|
+
.top-toolbar .report-name {
|
|
1114
|
+
font-size: 12px;
|
|
1115
|
+
max-width: 120px;
|
|
1116
|
+
overflow: hidden;
|
|
1117
|
+
text-overflow: ellipsis;
|
|
1118
|
+
white-space: nowrap;
|
|
1119
|
+
}
|
|
1120
|
+
.zoom-controls {
|
|
1121
|
+
border-left: none;
|
|
1122
|
+
padding-left: 0;
|
|
1123
|
+
margin-left: 0;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/* ─── Responsive: Page Setup Bar ─── */
|
|
1128
|
+
@media (max-width: 900px) {
|
|
1129
|
+
.page-setup-bar {
|
|
1130
|
+
gap: 6px;
|
|
1131
|
+
padding: 4px 8px;
|
|
1132
|
+
}
|
|
1133
|
+
.page-setup-input {
|
|
1134
|
+
width: 36px;
|
|
1135
|
+
}
|
|
1136
|
+
.page-setup-select {
|
|
1137
|
+
max-width: 140px;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
@media (max-width: 600px) {
|
|
1141
|
+
.page-setup-bar {
|
|
1142
|
+
font-size: 11px;
|
|
1143
|
+
}
|
|
1144
|
+
.page-setup-label {
|
|
1145
|
+
display: none;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/* ─── Responsive: Toolbox Touch Targets ─── */
|
|
1150
|
+
@media (max-width: 768px) {
|
|
1151
|
+
.toolbox-item {
|
|
1152
|
+
padding: 12px 10px;
|
|
1153
|
+
min-height: 44px;
|
|
1154
|
+
}
|
|
1155
|
+
.toolbox-icon {
|
|
1156
|
+
width: 32px;
|
|
1157
|
+
height: 32px;
|
|
1158
|
+
font-size: 13px;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/* ─── Responsive: Properties Panel ─── */
|
|
1163
|
+
@media (max-width: 768px) {
|
|
1164
|
+
.prop-label {
|
|
1165
|
+
width: 60px;
|
|
1166
|
+
font-size: 10px;
|
|
1167
|
+
}
|
|
1168
|
+
.prop-input {
|
|
1169
|
+
font-size: 11px;
|
|
1170
|
+
padding: 4px 4px;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/* ─── Responsive: Dialog ─── */
|
|
1175
|
+
@media (max-width: 600px) {
|
|
1176
|
+
.dialog {
|
|
1177
|
+
min-width: auto;
|
|
1178
|
+
max-width: calc(100vw - 32px);
|
|
1179
|
+
margin: 16px;
|
|
1180
|
+
}
|
|
1181
|
+
.dialog-body {
|
|
1182
|
+
padding: 12px;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
`; }
|
|
1186
|
+
// ─── Lifecycle ──────────────────────────────────────────────────
|
|
1187
|
+
connectedCallback() {
|
|
1188
|
+
super.connectedCallback();
|
|
1189
|
+
if (this.layout) {
|
|
1190
|
+
this._layout = structuredClone(this.layout);
|
|
1191
|
+
}
|
|
1192
|
+
this._pushUndo();
|
|
1193
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
1194
|
+
this._onGlobalClick = this._onGlobalClick.bind(this);
|
|
1195
|
+
document.addEventListener('keydown', this._onKeyDown);
|
|
1196
|
+
document.addEventListener('click', this._onGlobalClick);
|
|
1197
|
+
}
|
|
1198
|
+
disconnectedCallback() {
|
|
1199
|
+
super.disconnectedCallback();
|
|
1200
|
+
document.removeEventListener('keydown', this._onKeyDown);
|
|
1201
|
+
document.removeEventListener('click', this._onGlobalClick);
|
|
1202
|
+
}
|
|
1203
|
+
updated(changed) {
|
|
1204
|
+
if (changed.has('layout') && this.layout) {
|
|
1205
|
+
this._layout = structuredClone(this.layout);
|
|
1206
|
+
}
|
|
1207
|
+
if (changed.has('dataSources') && this.dataSources.length > 0) {
|
|
1208
|
+
// Sync dataSources into layout
|
|
1209
|
+
this._layout = { ...this._layout, dataSources: this.dataSources };
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
// ─── Global click to dismiss context menu ──────────────────────
|
|
1213
|
+
_onGlobalClick() {
|
|
1214
|
+
if (this._contextMenu) {
|
|
1215
|
+
this._contextMenu = null;
|
|
1216
|
+
this.requestUpdate();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
// ─── ID Generation ──────────────────────────────────────────────
|
|
1220
|
+
_newId(prefix = 'el') {
|
|
1221
|
+
return `${prefix}_${Date.now()}_${this._idCounter++}`;
|
|
1222
|
+
}
|
|
1223
|
+
// ─── Undo / Redo ────────────────────────────────────────────────
|
|
1224
|
+
_pushUndo() {
|
|
1225
|
+
this._undoStack = [...this._undoStack, JSON.stringify(this._layout)];
|
|
1226
|
+
this._redoStack = [];
|
|
1227
|
+
if (this._undoStack.length > 50)
|
|
1228
|
+
this._undoStack = this._undoStack.slice(-50);
|
|
1229
|
+
}
|
|
1230
|
+
_undo() {
|
|
1231
|
+
if (this._undoStack.length <= 1)
|
|
1232
|
+
return;
|
|
1233
|
+
const current = this._undoStack.pop();
|
|
1234
|
+
this._redoStack = [...this._redoStack, current];
|
|
1235
|
+
const prev = this._undoStack[this._undoStack.length - 1];
|
|
1236
|
+
this._layout = JSON.parse(prev);
|
|
1237
|
+
this._emitChange();
|
|
1238
|
+
}
|
|
1239
|
+
_redo() {
|
|
1240
|
+
if (this._redoStack.length === 0)
|
|
1241
|
+
return;
|
|
1242
|
+
const next = this._redoStack.pop();
|
|
1243
|
+
this._undoStack = [...this._undoStack, next];
|
|
1244
|
+
this._layout = JSON.parse(next);
|
|
1245
|
+
this._emitChange();
|
|
693
1246
|
}
|
|
694
1247
|
// ─── Change Emission ────────────────────────────────────────────
|
|
695
1248
|
_emitChange() {
|
|
@@ -939,10 +1492,17 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
939
1492
|
el.x = Math.max(0, Math.round((orig.x + dxUnit) / snap) * snap);
|
|
940
1493
|
el.y = Math.max(0, Math.round((orig.y + dyUnit) / snap) * snap);
|
|
941
1494
|
}
|
|
1495
|
+
// Smart alignment guides
|
|
1496
|
+
const primaryEl = elements.find(e => e.id === elementId);
|
|
1497
|
+
const currentBand = this._layout.bands.find(b => b.id === bandId);
|
|
1498
|
+
if (primaryEl && currentBand) {
|
|
1499
|
+
this._calculateGuides(primaryEl, currentBand);
|
|
1500
|
+
}
|
|
942
1501
|
this.requestUpdate();
|
|
943
1502
|
};
|
|
944
1503
|
const onUp = () => {
|
|
945
1504
|
this._drag = null;
|
|
1505
|
+
this._guides = { x: [], y: [] };
|
|
946
1506
|
// Update originals for final positions
|
|
947
1507
|
this._commitChange();
|
|
948
1508
|
document.removeEventListener('mousemove', onMove);
|
|
@@ -1217,8 +1777,13 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1217
1777
|
return html `
|
|
1218
1778
|
<div class="designer">
|
|
1219
1779
|
${this._renderTopToolbar()}
|
|
1780
|
+
${this._renderPageSetupBar()}
|
|
1781
|
+
|
|
1782
|
+
${this._mobileLeftOpen || this._mobileRightOpen ? html `
|
|
1783
|
+
<div class="mobile-overlay" @click=${() => { this._mobileLeftOpen = false; this._mobileRightOpen = false; }}></div>
|
|
1784
|
+
` : nothing}
|
|
1220
1785
|
|
|
1221
|
-
<div class="left-panel">
|
|
1786
|
+
<div class="left-panel ${this._mobileLeftOpen ? 'mobile-open' : ''}">
|
|
1222
1787
|
<div class="panel-tabs">
|
|
1223
1788
|
<div class="panel-tab ${this._activePanel === 'toolbox' ? 'active' : ''}"
|
|
1224
1789
|
@click=${() => this._activePanel = 'toolbox'}>Toolbox</div>
|
|
@@ -1241,6 +1806,16 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1241
1806
|
this.requestUpdate();
|
|
1242
1807
|
}
|
|
1243
1808
|
}}>
|
|
1809
|
+
<div class="mobile-panel-toggles">
|
|
1810
|
+
<button class="mobile-toggle-btn ${this._mobileLeftOpen ? 'active' : ''}"
|
|
1811
|
+
@click=${(e) => { e.stopPropagation(); this._mobileLeftOpen = !this._mobileLeftOpen; this._mobileRightOpen = false; }}>
|
|
1812
|
+
Toolbox
|
|
1813
|
+
</button>
|
|
1814
|
+
<button class="mobile-toggle-btn ${this._mobileRightOpen ? 'active' : ''}"
|
|
1815
|
+
@click=${(e) => { e.stopPropagation(); this._mobileRightOpen = !this._mobileRightOpen; this._mobileLeftOpen = false; }}>
|
|
1816
|
+
Properties
|
|
1817
|
+
</button>
|
|
1818
|
+
</div>
|
|
1244
1819
|
<div class="canvas-scroll-container" style="transform:scale(${this._zoom});padding:20px;">
|
|
1245
1820
|
<div class="canvas-wrapper" style="width:${pageWidthPx}px;">
|
|
1246
1821
|
${this._renderRuler(pageWidthPx)}
|
|
@@ -1258,11 +1833,12 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1258
1833
|
</div>
|
|
1259
1834
|
</div>
|
|
1260
1835
|
|
|
1261
|
-
<div class="right-panel">
|
|
1836
|
+
<div class="right-panel ${this._mobileRightOpen ? 'mobile-open' : ''}">
|
|
1262
1837
|
${this._renderPropertiesPanel()}
|
|
1263
1838
|
</div>
|
|
1264
1839
|
</div>
|
|
1265
1840
|
${this._renderContextMenu()}
|
|
1841
|
+
${this._renderPageSetupDialog()}
|
|
1266
1842
|
`;
|
|
1267
1843
|
}
|
|
1268
1844
|
// ─── Ruler ────────────────────────────────────────────────────
|
|
@@ -1308,6 +1884,476 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1308
1884
|
</div>
|
|
1309
1885
|
`;
|
|
1310
1886
|
}
|
|
1887
|
+
// ─── Page Setup Dialog ──────────────────────────────────────
|
|
1888
|
+
_openPageSetup() {
|
|
1889
|
+
// Find matching preset
|
|
1890
|
+
const layout = this._layout;
|
|
1891
|
+
const match = PAGE_SIZES.find(p => Math.abs(p.pageSize.width - layout.pageSize.width) < 0.5 &&
|
|
1892
|
+
Math.abs(p.pageSize.height - layout.pageSize.height) < 0.5 &&
|
|
1893
|
+
p.pageSize.unit === layout.pageSize.unit);
|
|
1894
|
+
this._pageSetupDraft = {
|
|
1895
|
+
presetId: match?.id || 'custom',
|
|
1896
|
+
width: layout.pageSize.width,
|
|
1897
|
+
height: layout.pageSize.height,
|
|
1898
|
+
unit: layout.pageSize.unit,
|
|
1899
|
+
marginTop: layout.margins.top,
|
|
1900
|
+
marginRight: layout.margins.right,
|
|
1901
|
+
marginBottom: layout.margins.bottom,
|
|
1902
|
+
marginLeft: layout.margins.left,
|
|
1903
|
+
};
|
|
1904
|
+
this._showPageSetup = true;
|
|
1905
|
+
}
|
|
1906
|
+
_applyPageSetup() {
|
|
1907
|
+
if (!this._pageSetupDraft)
|
|
1908
|
+
return;
|
|
1909
|
+
const d = this._pageSetupDraft;
|
|
1910
|
+
this._layout = {
|
|
1911
|
+
...this._layout,
|
|
1912
|
+
pageSize: { width: d.width, height: d.height, unit: d.unit },
|
|
1913
|
+
margins: { top: d.marginTop, right: d.marginRight, bottom: d.marginBottom, left: d.marginLeft },
|
|
1914
|
+
};
|
|
1915
|
+
this._showPageSetup = false;
|
|
1916
|
+
this._pageSetupDraft = null;
|
|
1917
|
+
this._commitChange();
|
|
1918
|
+
}
|
|
1919
|
+
_onPresetChange(presetId) {
|
|
1920
|
+
if (!this._pageSetupDraft)
|
|
1921
|
+
return;
|
|
1922
|
+
if (presetId === 'custom') {
|
|
1923
|
+
this._pageSetupDraft = { ...this._pageSetupDraft, presetId: 'custom' };
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
const preset = PAGE_SIZES.find(p => p.id === presetId);
|
|
1927
|
+
if (!preset)
|
|
1928
|
+
return;
|
|
1929
|
+
this._pageSetupDraft = {
|
|
1930
|
+
presetId,
|
|
1931
|
+
width: preset.pageSize.width,
|
|
1932
|
+
height: preset.pageSize.height,
|
|
1933
|
+
unit: preset.pageSize.unit,
|
|
1934
|
+
marginTop: preset.margins.top,
|
|
1935
|
+
marginRight: preset.margins.right,
|
|
1936
|
+
marginBottom: preset.margins.bottom,
|
|
1937
|
+
marginLeft: preset.margins.left,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
_toggleOrientation() {
|
|
1941
|
+
if (!this._pageSetupDraft)
|
|
1942
|
+
return;
|
|
1943
|
+
const d = this._pageSetupDraft;
|
|
1944
|
+
this._pageSetupDraft = {
|
|
1945
|
+
...d,
|
|
1946
|
+
width: d.height,
|
|
1947
|
+
height: d.width,
|
|
1948
|
+
marginTop: d.marginLeft,
|
|
1949
|
+
marginRight: d.marginTop,
|
|
1950
|
+
marginBottom: d.marginRight,
|
|
1951
|
+
marginLeft: d.marginBottom,
|
|
1952
|
+
presetId: 'custom', // swapping breaks preset match
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
_renderPageSetupDialog() {
|
|
1956
|
+
if (!this._showPageSetup || !this._pageSetupDraft)
|
|
1957
|
+
return nothing;
|
|
1958
|
+
const d = this._pageSetupDraft;
|
|
1959
|
+
const isPortrait = d.height >= d.width;
|
|
1960
|
+
// Mini preview scaling — fit into 120x120 box
|
|
1961
|
+
const scale = Math.min(120 / d.width, 120 / d.height);
|
|
1962
|
+
const pw = d.width * scale;
|
|
1963
|
+
const ph = d.height * scale;
|
|
1964
|
+
const mt = d.marginTop * scale;
|
|
1965
|
+
const mr = d.marginRight * scale;
|
|
1966
|
+
const mb = d.marginBottom * scale;
|
|
1967
|
+
const ml = d.marginLeft * scale;
|
|
1968
|
+
// Group presets by category
|
|
1969
|
+
const categories = [
|
|
1970
|
+
{ label: 'Paper', cat: 'paper' },
|
|
1971
|
+
{ label: 'Receipt / POS', cat: 'receipt' },
|
|
1972
|
+
{ label: 'Labels', cat: 'label' },
|
|
1973
|
+
];
|
|
1974
|
+
return html `
|
|
1975
|
+
<div class="dialog-overlay" @click=${(e) => {
|
|
1976
|
+
if (e.target.classList.contains('dialog-overlay')) {
|
|
1977
|
+
this._showPageSetup = false;
|
|
1978
|
+
}
|
|
1979
|
+
}}>
|
|
1980
|
+
<div class="dialog" @click=${(e) => e.stopPropagation()}>
|
|
1981
|
+
<div class="dialog-header">
|
|
1982
|
+
<span>Page Setup</span>
|
|
1983
|
+
<button class="dialog-btn" @click=${() => { this._showPageSetup = false; }}>\u2715</button>
|
|
1984
|
+
</div>
|
|
1985
|
+
<div class="dialog-body">
|
|
1986
|
+
<!-- Page size preset -->
|
|
1987
|
+
<div class="dialog-row">
|
|
1988
|
+
<span class="dialog-label">Page Size</span>
|
|
1989
|
+
<select class="dialog-select" .value=${d.presetId}
|
|
1990
|
+
@change=${(e) => this._onPresetChange(e.target.value)}>
|
|
1991
|
+
<option value="custom">Custom</option>
|
|
1992
|
+
${categories.map(c => html `
|
|
1993
|
+
<optgroup label=${c.label}>
|
|
1994
|
+
${PAGE_SIZES.filter(p => p.category === c.cat).map(p => html `
|
|
1995
|
+
<option value=${p.id} ?selected=${d.presetId === p.id}>${p.name} (${p.pageSize.width}\u00D7${p.pageSize.height} ${p.pageSize.unit})</option>
|
|
1996
|
+
`)}
|
|
1997
|
+
</optgroup>
|
|
1998
|
+
`)}
|
|
1999
|
+
</select>
|
|
2000
|
+
</div>
|
|
2001
|
+
|
|
2002
|
+
<!-- Orientation -->
|
|
2003
|
+
<div class="dialog-row">
|
|
2004
|
+
<span class="dialog-label">Orientation</span>
|
|
2005
|
+
<div class="orientation-toggle">
|
|
2006
|
+
<button class=${isPortrait ? 'active' : ''}
|
|
2007
|
+
@click=${() => { if (!isPortrait)
|
|
2008
|
+
this._toggleOrientation(); }}>Portrait</button>
|
|
2009
|
+
<button class=${!isPortrait ? 'active' : ''}
|
|
2010
|
+
@click=${() => { if (isPortrait)
|
|
2011
|
+
this._toggleOrientation(); }}>Landscape</button>
|
|
2012
|
+
</div>
|
|
2013
|
+
</div>
|
|
2014
|
+
|
|
2015
|
+
<!-- Unit -->
|
|
2016
|
+
<div class="dialog-row">
|
|
2017
|
+
<span class="dialog-label">Unit</span>
|
|
2018
|
+
<select class="dialog-select" style="max-width:100px;"
|
|
2019
|
+
.value=${d.unit}
|
|
2020
|
+
@change=${(e) => {
|
|
2021
|
+
this._pageSetupDraft = { ...d, unit: e.target.value, presetId: 'custom' };
|
|
2022
|
+
}}>
|
|
2023
|
+
<option value="mm">mm</option>
|
|
2024
|
+
<option value="in">in</option>
|
|
2025
|
+
<option value="pt">pt</option>
|
|
2026
|
+
<option value="px">px</option>
|
|
2027
|
+
</select>
|
|
2028
|
+
</div>
|
|
2029
|
+
|
|
2030
|
+
<!-- Width / Height -->
|
|
2031
|
+
<div class="dialog-row">
|
|
2032
|
+
<span class="dialog-label">Dimensions</span>
|
|
2033
|
+
<label style="font-size:11px;color:var(--zrd-text-muted)">W
|
|
2034
|
+
<input class="dialog-input" type="number" step="0.1" .value=${String(d.width)}
|
|
2035
|
+
@change=${(e) => {
|
|
2036
|
+
this._pageSetupDraft = { ...d, width: parseFloat(e.target.value) || d.width, presetId: 'custom' };
|
|
2037
|
+
}} />
|
|
2038
|
+
</label>
|
|
2039
|
+
<label style="font-size:11px;color:var(--zrd-text-muted)">H
|
|
2040
|
+
<input class="dialog-input" type="number" step="0.1" .value=${String(d.height)}
|
|
2041
|
+
@change=${(e) => {
|
|
2042
|
+
this._pageSetupDraft = { ...d, height: parseFloat(e.target.value) || d.height, presetId: 'custom' };
|
|
2043
|
+
}} />
|
|
2044
|
+
</label>
|
|
2045
|
+
<span style="font-size:11px;color:var(--zrd-text-muted)">${d.unit}</span>
|
|
2046
|
+
</div>
|
|
2047
|
+
|
|
2048
|
+
<!-- Margins -->
|
|
2049
|
+
<div class="dialog-row" style="align-items:flex-start;">
|
|
2050
|
+
<span class="dialog-label" style="margin-top:4px;">Margins</span>
|
|
2051
|
+
<div class="dialog-group" style="flex:1;">
|
|
2052
|
+
<label>Top
|
|
2053
|
+
<input type="number" step="0.1" .value=${String(d.marginTop)}
|
|
2054
|
+
@change=${(e) => {
|
|
2055
|
+
this._pageSetupDraft = { ...d, marginTop: parseFloat(e.target.value) || 0 };
|
|
2056
|
+
}} />
|
|
2057
|
+
</label>
|
|
2058
|
+
<label>Right
|
|
2059
|
+
<input type="number" step="0.1" .value=${String(d.marginRight)}
|
|
2060
|
+
@change=${(e) => {
|
|
2061
|
+
this._pageSetupDraft = { ...d, marginRight: parseFloat(e.target.value) || 0 };
|
|
2062
|
+
}} />
|
|
2063
|
+
</label>
|
|
2064
|
+
<label>Bottom
|
|
2065
|
+
<input type="number" step="0.1" .value=${String(d.marginBottom)}
|
|
2066
|
+
@change=${(e) => {
|
|
2067
|
+
this._pageSetupDraft = { ...d, marginBottom: parseFloat(e.target.value) || 0 };
|
|
2068
|
+
}} />
|
|
2069
|
+
</label>
|
|
2070
|
+
<label>Left
|
|
2071
|
+
<input type="number" step="0.1" .value=${String(d.marginLeft)}
|
|
2072
|
+
@change=${(e) => {
|
|
2073
|
+
this._pageSetupDraft = { ...d, marginLeft: parseFloat(e.target.value) || 0 };
|
|
2074
|
+
}} />
|
|
2075
|
+
</label>
|
|
2076
|
+
</div>
|
|
2077
|
+
</div>
|
|
2078
|
+
|
|
2079
|
+
<!-- Preview -->
|
|
2080
|
+
<div class="page-preview-box">
|
|
2081
|
+
<div class="page-preview-page" style="width:${pw}px;height:${ph}px;">
|
|
2082
|
+
<div class="page-preview-margin" style="top:${mt}px;right:${mr}px;bottom:${mb}px;left:${ml}px;"></div>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
</div>
|
|
2086
|
+
<div class="dialog-footer">
|
|
2087
|
+
<button class="dialog-btn" @click=${() => { this._showPageSetup = false; }}>Cancel</button>
|
|
2088
|
+
<button class="dialog-btn dialog-btn-primary" @click=${() => this._applyPageSetup()}>Apply</button>
|
|
2089
|
+
</div>
|
|
2090
|
+
</div>
|
|
2091
|
+
</div>
|
|
2092
|
+
`;
|
|
2093
|
+
}
|
|
2094
|
+
// ─── Band Menu Dropdown ──────────────────────────────────────
|
|
2095
|
+
_renderBandMenu() {
|
|
2096
|
+
const existingTypes = new Set(this._layout.bands.map(b => b.type));
|
|
2097
|
+
// Band types that can only appear once
|
|
2098
|
+
const singleBands = ['reportHeader', 'pageHeader', 'columnHeader', 'detail', 'columnFooter', 'pageFooter', 'reportFooter'];
|
|
2099
|
+
// Group header/footer can have multiples
|
|
2100
|
+
const allBands = ['reportHeader', 'pageHeader', 'groupHeader', 'columnHeader', 'detail', 'columnFooter', 'groupFooter', 'pageFooter', 'reportFooter'];
|
|
2101
|
+
return html `
|
|
2102
|
+
<div class="overlay-dismiss" @click=${() => { this._showBandMenu = false; }}></div>
|
|
2103
|
+
<div class="band-menu">
|
|
2104
|
+
${allBands.map(type => {
|
|
2105
|
+
const isSingle = singleBands.includes(type);
|
|
2106
|
+
const alreadyExists = existingTypes.has(type);
|
|
2107
|
+
const disabled = isSingle && alreadyExists;
|
|
2108
|
+
return html `
|
|
2109
|
+
<div class="band-menu-item ${disabled ? 'disabled' : ''}"
|
|
2110
|
+
@click=${() => {
|
|
2111
|
+
if (!disabled) {
|
|
2112
|
+
this._addBand(type);
|
|
2113
|
+
this._showBandMenu = false;
|
|
2114
|
+
}
|
|
2115
|
+
}}>
|
|
2116
|
+
<span>${BAND_ICONS[type]}</span>
|
|
2117
|
+
<span>${BAND_LABELS[type]}</span>
|
|
2118
|
+
${disabled ? html `<span style="font-size:10px;color:var(--zrd-text-muted);margin-left:auto;">(exists)</span>` : nothing}
|
|
2119
|
+
</div>
|
|
2120
|
+
`;
|
|
2121
|
+
})}
|
|
2122
|
+
</div>
|
|
2123
|
+
`;
|
|
2124
|
+
}
|
|
2125
|
+
// ─── Page Setup Bar (always visible) ──────────────────────────
|
|
2126
|
+
_renderPageSetupBar() {
|
|
2127
|
+
const ps = this._layout.pageSize;
|
|
2128
|
+
const m = this._layout.margins;
|
|
2129
|
+
const isPortrait = ps.height > ps.width;
|
|
2130
|
+
const currentPreset = PAGE_SIZES.find(p => Math.abs(p.pageSize.width - ps.width) < 0.5 && Math.abs(p.pageSize.height - ps.height) < 0.5);
|
|
2131
|
+
const categories = [
|
|
2132
|
+
{ key: 'paper', label: 'Paper' },
|
|
2133
|
+
{ key: 'receipt', label: 'Receipt / POS' },
|
|
2134
|
+
{ key: 'label', label: 'Labels' },
|
|
2135
|
+
];
|
|
2136
|
+
return html `
|
|
2137
|
+
<div class="page-setup-bar">
|
|
2138
|
+
<!-- Page Size Dropdown -->
|
|
2139
|
+
<div class="page-setup-group">
|
|
2140
|
+
<span class="page-setup-label">Page:</span>
|
|
2141
|
+
<select class="page-setup-select" @change=${(e) => this._onPageSizePresetChange(e.target.value)}>
|
|
2142
|
+
${categories.map(cat => html `
|
|
2143
|
+
<optgroup label="${cat.label}">
|
|
2144
|
+
${PAGE_SIZES.filter(p => p.category === cat.key).map(p => html `
|
|
2145
|
+
<option value="${p.id}" ?selected=${currentPreset?.id === p.id}>${p.name} (${p.pageSize.width}\u00D7${p.pageSize.height})</option>
|
|
2146
|
+
`)}
|
|
2147
|
+
</optgroup>
|
|
2148
|
+
`)}
|
|
2149
|
+
<optgroup label="Custom">
|
|
2150
|
+
<option value="custom" ?selected=${!currentPreset}>Custom (${ps.width}\u00D7${ps.height} ${ps.unit})</option>
|
|
2151
|
+
</optgroup>
|
|
2152
|
+
</select>
|
|
2153
|
+
</div>
|
|
2154
|
+
|
|
2155
|
+
<div class="page-setup-separator"></div>
|
|
2156
|
+
|
|
2157
|
+
<!-- Orientation -->
|
|
2158
|
+
<div class="page-setup-group">
|
|
2159
|
+
<span class="page-setup-label">Orientation:</span>
|
|
2160
|
+
<button class="orientation-btn ${isPortrait ? 'active' : ''}"
|
|
2161
|
+
title="Portrait"
|
|
2162
|
+
@click=${() => this._setOrientation('portrait')}>
|
|
2163
|
+
<span style="display:inline-block;width:10px;height:14px;border:1.5px solid currentColor;border-radius:1px;"></span>
|
|
2164
|
+
</button>
|
|
2165
|
+
<button class="orientation-btn ${!isPortrait ? 'active' : ''}"
|
|
2166
|
+
title="Landscape"
|
|
2167
|
+
@click=${() => this._setOrientation('landscape')}>
|
|
2168
|
+
<span style="display:inline-block;width:14px;height:10px;border:1.5px solid currentColor;border-radius:1px;"></span>
|
|
2169
|
+
</button>
|
|
2170
|
+
</div>
|
|
2171
|
+
|
|
2172
|
+
<div class="page-setup-separator"></div>
|
|
2173
|
+
|
|
2174
|
+
<!-- Margins -->
|
|
2175
|
+
<div class="page-setup-group">
|
|
2176
|
+
<span class="page-setup-label">Margins:</span>
|
|
2177
|
+
<span class="page-setup-label">T</span>
|
|
2178
|
+
<input class="page-setup-input" type="number" step="0.5" min="0"
|
|
2179
|
+
.value=${String(m.top)}
|
|
2180
|
+
@change=${(e) => this._updateMargin('top', Number(e.target.value))} />
|
|
2181
|
+
<span class="page-setup-label">R</span>
|
|
2182
|
+
<input class="page-setup-input" type="number" step="0.5" min="0"
|
|
2183
|
+
.value=${String(m.right)}
|
|
2184
|
+
@change=${(e) => this._updateMargin('right', Number(e.target.value))} />
|
|
2185
|
+
<span class="page-setup-label">B</span>
|
|
2186
|
+
<input class="page-setup-input" type="number" step="0.5" min="0"
|
|
2187
|
+
.value=${String(m.bottom)}
|
|
2188
|
+
@change=${(e) => this._updateMargin('bottom', Number(e.target.value))} />
|
|
2189
|
+
<span class="page-setup-label">L</span>
|
|
2190
|
+
<input class="page-setup-input" type="number" step="0.5" min="0"
|
|
2191
|
+
.value=${String(m.left)}
|
|
2192
|
+
@change=${(e) => this._updateMargin('left', Number(e.target.value))} />
|
|
2193
|
+
<span class="page-setup-label">${ps.unit}</span>
|
|
2194
|
+
</div>
|
|
2195
|
+
|
|
2196
|
+
<div class="page-setup-separator"></div>
|
|
2197
|
+
|
|
2198
|
+
<!-- Unit -->
|
|
2199
|
+
<div class="page-setup-group">
|
|
2200
|
+
<span class="page-setup-label">Unit:</span>
|
|
2201
|
+
<select class="page-setup-select" style="width:60px;"
|
|
2202
|
+
@change=${(e) => this._changeUnit(e.target.value)}>
|
|
2203
|
+
<option value="mm" ?selected=${ps.unit === 'mm'}>mm</option>
|
|
2204
|
+
<option value="in" ?selected=${ps.unit === 'in'}>in</option>
|
|
2205
|
+
<option value="pt" ?selected=${ps.unit === 'pt'}>pt</option>
|
|
2206
|
+
<option value="px" ?selected=${ps.unit === 'px'}>px</option>
|
|
2207
|
+
</select>
|
|
2208
|
+
</div>
|
|
2209
|
+
</div>
|
|
2210
|
+
`;
|
|
2211
|
+
}
|
|
2212
|
+
_onPageSizePresetChange(presetId) {
|
|
2213
|
+
if (presetId === 'custom')
|
|
2214
|
+
return;
|
|
2215
|
+
const preset = PAGE_SIZES.find(p => p.id === presetId);
|
|
2216
|
+
if (!preset)
|
|
2217
|
+
return;
|
|
2218
|
+
this._layout.pageSize = { ...preset.pageSize };
|
|
2219
|
+
this._layout.margins = { ...preset.margins };
|
|
2220
|
+
this._commitChange();
|
|
2221
|
+
}
|
|
2222
|
+
_setOrientation(orientation) {
|
|
2223
|
+
const ps = this._layout.pageSize;
|
|
2224
|
+
const isPortrait = ps.height > ps.width;
|
|
2225
|
+
if ((orientation === 'portrait' && !isPortrait) || (orientation === 'landscape' && isPortrait)) {
|
|
2226
|
+
const tmp = ps.width;
|
|
2227
|
+
ps.width = ps.height;
|
|
2228
|
+
ps.height = tmp;
|
|
2229
|
+
// Also swap horizontal/vertical margins
|
|
2230
|
+
const m = this._layout.margins;
|
|
2231
|
+
const tmpT = m.top;
|
|
2232
|
+
const tmpB = m.bottom;
|
|
2233
|
+
m.top = m.left;
|
|
2234
|
+
m.bottom = m.right;
|
|
2235
|
+
m.left = tmpT;
|
|
2236
|
+
m.right = tmpB;
|
|
2237
|
+
this._commitChange();
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
_updateMargin(side, value) {
|
|
2241
|
+
this._layout.margins[side] = Math.max(0, value);
|
|
2242
|
+
this._commitChange();
|
|
2243
|
+
}
|
|
2244
|
+
_changeUnit(newUnit) {
|
|
2245
|
+
// Convert all dimensions from old unit to new unit via px
|
|
2246
|
+
const oldUnit = this._layout.pageSize.unit;
|
|
2247
|
+
if (oldUnit === newUnit)
|
|
2248
|
+
return;
|
|
2249
|
+
const convert = (val) => {
|
|
2250
|
+
// old unit -> px -> new unit
|
|
2251
|
+
const px = toPx(val, oldUnit);
|
|
2252
|
+
switch (newUnit) {
|
|
2253
|
+
case 'mm': return Math.round((px * 25.4 / 96) * 100) / 100;
|
|
2254
|
+
case 'in': return Math.round((px / 96) * 1000) / 1000;
|
|
2255
|
+
case 'pt': return Math.round((px * 72 / 96) * 100) / 100;
|
|
2256
|
+
case 'px': return Math.round(px);
|
|
2257
|
+
default: return val;
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
this._layout.pageSize.width = convert(this._layout.pageSize.width);
|
|
2261
|
+
this._layout.pageSize.height = convert(this._layout.pageSize.height);
|
|
2262
|
+
this._layout.pageSize.unit = newUnit;
|
|
2263
|
+
this._layout.margins.top = convert(this._layout.margins.top);
|
|
2264
|
+
this._layout.margins.right = convert(this._layout.margins.right);
|
|
2265
|
+
this._layout.margins.bottom = convert(this._layout.margins.bottom);
|
|
2266
|
+
this._layout.margins.left = convert(this._layout.margins.left);
|
|
2267
|
+
// Convert band heights and element positions/sizes
|
|
2268
|
+
for (const band of this._layout.bands) {
|
|
2269
|
+
band.height = convert(band.height);
|
|
2270
|
+
for (const el of band.elements) {
|
|
2271
|
+
el.x = convert(el.x);
|
|
2272
|
+
el.y = convert(el.y);
|
|
2273
|
+
el.width = convert(el.width);
|
|
2274
|
+
el.height = convert(el.height);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
this._commitChange();
|
|
2278
|
+
}
|
|
2279
|
+
// ─── Smart Alignment Guides ──────────────────────────────────
|
|
2280
|
+
_calculateGuides(movingEl, band) {
|
|
2281
|
+
const SNAP_THRESHOLD = 2; // layout units
|
|
2282
|
+
const guidesX = [];
|
|
2283
|
+
const guidesY = [];
|
|
2284
|
+
const movLeft = movingEl.x;
|
|
2285
|
+
const movRight = movingEl.x + movingEl.width;
|
|
2286
|
+
const movCenterX = movingEl.x + movingEl.width / 2;
|
|
2287
|
+
const movTop = movingEl.y;
|
|
2288
|
+
const movBottom = movingEl.y + movingEl.height;
|
|
2289
|
+
const movCenterY = movingEl.y + movingEl.height / 2;
|
|
2290
|
+
// Check against other elements in the same band
|
|
2291
|
+
for (const other of band.elements) {
|
|
2292
|
+
if (this._selectedElementIds.has(other.id))
|
|
2293
|
+
continue;
|
|
2294
|
+
const edges = [
|
|
2295
|
+
{ val: other.x, movEdges: [movLeft, movRight, movCenterX] },
|
|
2296
|
+
{ val: other.x + other.width, movEdges: [movLeft, movRight, movCenterX] },
|
|
2297
|
+
{ val: other.x + other.width / 2, movEdges: [movCenterX] },
|
|
2298
|
+
];
|
|
2299
|
+
for (const edge of edges) {
|
|
2300
|
+
for (const me of edge.movEdges) {
|
|
2301
|
+
if (Math.abs(me - edge.val) <= SNAP_THRESHOLD) {
|
|
2302
|
+
guidesX.push(edge.val);
|
|
2303
|
+
// Snap the element
|
|
2304
|
+
const diff = edge.val - me;
|
|
2305
|
+
if (me === movLeft)
|
|
2306
|
+
movingEl.x += diff;
|
|
2307
|
+
else if (me === movRight)
|
|
2308
|
+
movingEl.x += diff;
|
|
2309
|
+
else if (me === movCenterX)
|
|
2310
|
+
movingEl.x += diff;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
const yEdges = [
|
|
2315
|
+
{ val: other.y, movEdges: [movTop, movBottom, movCenterY] },
|
|
2316
|
+
{ val: other.y + other.height, movEdges: [movTop, movBottom, movCenterY] },
|
|
2317
|
+
{ val: other.y + other.height / 2, movEdges: [movCenterY] },
|
|
2318
|
+
];
|
|
2319
|
+
for (const edge of yEdges) {
|
|
2320
|
+
for (const me of edge.movEdges) {
|
|
2321
|
+
if (Math.abs(me - edge.val) <= SNAP_THRESHOLD) {
|
|
2322
|
+
guidesY.push(edge.val);
|
|
2323
|
+
const diff = edge.val - me;
|
|
2324
|
+
if (me === movTop)
|
|
2325
|
+
movingEl.y += diff;
|
|
2326
|
+
else if (me === movBottom)
|
|
2327
|
+
movingEl.y += diff;
|
|
2328
|
+
else if (me === movCenterY)
|
|
2329
|
+
movingEl.y += diff;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
// Check against band center
|
|
2335
|
+
const bandCenterX = (this._layout.pageSize.width - this._layout.margins.left - this._layout.margins.right) / 2 + this._layout.margins.left;
|
|
2336
|
+
if (Math.abs(movCenterX - bandCenterX) <= SNAP_THRESHOLD) {
|
|
2337
|
+
guidesX.push(bandCenterX);
|
|
2338
|
+
movingEl.x += bandCenterX - movCenterX;
|
|
2339
|
+
}
|
|
2340
|
+
// Check against page margins
|
|
2341
|
+
const ml = this._layout.margins.left;
|
|
2342
|
+
const mr = this._layout.pageSize.width - this._layout.margins.right;
|
|
2343
|
+
if (Math.abs(movLeft - ml) <= SNAP_THRESHOLD) {
|
|
2344
|
+
guidesX.push(ml);
|
|
2345
|
+
movingEl.x = ml;
|
|
2346
|
+
}
|
|
2347
|
+
if (Math.abs(movRight - mr) <= SNAP_THRESHOLD) {
|
|
2348
|
+
guidesX.push(mr);
|
|
2349
|
+
movingEl.x = mr - movingEl.width;
|
|
2350
|
+
}
|
|
2351
|
+
// Deduplicate
|
|
2352
|
+
this._guides = {
|
|
2353
|
+
x: [...new Set(guidesX)],
|
|
2354
|
+
y: [...new Set(guidesY)],
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
1311
2357
|
// ─── Top Toolbar ──────────────────────────────────────────────
|
|
1312
2358
|
_renderTopToolbar() {
|
|
1313
2359
|
return html `
|
|
@@ -1329,28 +2375,31 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1329
2375
|
: html `<span class="report-name" @click=${this._startEditingReportName}
|
|
1330
2376
|
title="Click to edit report name">${this._layout.name}</span>`}
|
|
1331
2377
|
<span class="spacer"></span>
|
|
1332
|
-
<button @click=${this._undo} ?disabled=${this._undoStack.length <= 1} title="Ctrl+Z"
|
|
1333
|
-
<button @click=${this._redo} ?disabled=${this._redoStack.length === 0} title="Ctrl+Y"
|
|
1334
|
-
<button @click=${
|
|
1335
|
-
<
|
|
2378
|
+
<button @click=${this._undo} ?disabled=${this._undoStack.length <= 1} title="Undo (Ctrl+Z)">\u21A9 Undo</button>
|
|
2379
|
+
<button @click=${this._redo} ?disabled=${this._redoStack.length === 0} title="Redo (Ctrl+Y)">\u21AA Redo</button>
|
|
2380
|
+
<button @click=${this._openPageSetup} title="Page size, margins, orientation">\u2699 Page Setup</button>
|
|
2381
|
+
<div class="band-menu-wrapper">
|
|
2382
|
+
<button @click=${(e) => { e.stopPropagation(); this._showBandMenu = !this._showBandMenu; }}>+ Band \u25BE</button>
|
|
2383
|
+
${this._showBandMenu ? this._renderBandMenu() : nothing}
|
|
2384
|
+
</div>
|
|
1336
2385
|
<button @click=${() => {
|
|
1337
2386
|
this.dispatchEvent(new CustomEvent('save', {
|
|
1338
2387
|
detail: { layout: structuredClone(this._layout) },
|
|
1339
2388
|
bubbles: true, composed: true,
|
|
1340
2389
|
}));
|
|
1341
|
-
}}
|
|
2390
|
+
}}>\uD83D\uDCBE Save</button>
|
|
1342
2391
|
<button @click=${() => {
|
|
1343
2392
|
this.showPreview = !this.showPreview;
|
|
1344
2393
|
this.dispatchEvent(new CustomEvent('toggle-preview', {
|
|
1345
2394
|
detail: { show: this.showPreview },
|
|
1346
2395
|
bubbles: true, composed: true,
|
|
1347
2396
|
}));
|
|
1348
|
-
}}
|
|
2397
|
+
}}>\uD83D\uDC41 ${this.showPreview ? 'Hide Preview' : 'Preview'}</button>
|
|
1349
2398
|
|
|
1350
2399
|
<!-- Zoom Controls -->
|
|
1351
2400
|
<div class="zoom-controls">
|
|
1352
|
-
<button class="zoom-btn" @click=${this._zoomOut} ?disabled=${this._zoom <= 0.5} title="Zoom Out"
|
|
1353
|
-
<span class="zoom-label">${Math.round(this._zoom * 100)}%</span>
|
|
2401
|
+
<button class="zoom-btn" @click=${this._zoomOut} ?disabled=${this._zoom <= 0.5} title="Zoom Out">\u2212</button>
|
|
2402
|
+
<span class="zoom-label" style="cursor:pointer;" @click=${this._zoomFit} title="Click to reset to 100%">${Math.round(this._zoom * 100)}%</span>
|
|
1354
2403
|
<button class="zoom-btn" @click=${this._zoomIn} ?disabled=${this._zoom >= 4} title="Zoom In">+</button>
|
|
1355
2404
|
<button @click=${this._zoomFit} title="Reset zoom to 100%">100%</button>
|
|
1356
2405
|
<button @click=${() => { this._zoom = 1.5; }} title="Zoom 150%">150%</button>
|
|
@@ -1433,7 +2482,12 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1433
2482
|
this._activePanel = 'properties';
|
|
1434
2483
|
this.requestUpdate();
|
|
1435
2484
|
}}>
|
|
2485
|
+
${band.elements.length === 0 ? html `<div class="band-empty-hint">Drag elements from Toolbox or drop fields from Data panel</div>` : nothing}
|
|
1436
2486
|
${band.elements.map(el => this._renderDesignElement(el, band.id))}
|
|
2487
|
+
${this._selectedBandId === band.id ? html `
|
|
2488
|
+
${this._guides.x.map(x => html `<div class="guide-line-v" style="left:${this._toPx(x)}px;"></div>`)}
|
|
2489
|
+
${this._guides.y.map(y => html `<div class="guide-line-h" style="top:${this._toPx(y)}px;"></div>`)}
|
|
2490
|
+
` : nothing}
|
|
1437
2491
|
</div>
|
|
1438
2492
|
<div class="band-resize-handle ${this._bandResize?.bandId === band.id ? 'active' : ''}"
|
|
1439
2493
|
@mousedown=${(e) => this._onBandResizeMouseDown(e, band.id)}>
|
|
@@ -1585,87 +2639,121 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1585
2639
|
_renderPropertiesPanel() {
|
|
1586
2640
|
const el = this._getSelectedElement();
|
|
1587
2641
|
const unitLabel = this._unit;
|
|
1588
|
-
|
|
2642
|
+
const collapsed = this._collapsedSections;
|
|
2643
|
+
const sectionHeader = (key, title) => html `
|
|
2644
|
+
<div class="prop-section-header" @click=${() => this._toggleSection(key)}>
|
|
2645
|
+
<h4>${title}</h4>
|
|
2646
|
+
<span class="collapse-icon ${collapsed.has(key) ? 'collapsed' : ''}">\u25BE</span>
|
|
2647
|
+
</div>
|
|
2648
|
+
`;
|
|
2649
|
+
// ── Multiple selection ──
|
|
1589
2650
|
if (this._selectedElementIds.size > 1) {
|
|
1590
2651
|
return html `
|
|
1591
2652
|
<div class="prop-section">
|
|
1592
|
-
<
|
|
1593
|
-
|
|
1594
|
-
|
|
2653
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
2654
|
+
<span class="element-type-badge" style="background:#fff3e0;color:#e65100;">\u2750 ${this._selectedElementIds.size} selected</span>
|
|
2655
|
+
</div>
|
|
2656
|
+
<p style="font-size:11px;color:var(--zrd-text-muted);">Use Ctrl+C/V to copy/paste, Del to delete.</p>
|
|
1595
2657
|
</div>
|
|
1596
|
-
<div class="prop-section"
|
|
2658
|
+
<div class="prop-section">
|
|
1597
2659
|
<button class="btn-danger" @click=${this._deleteSelectedElements}>Delete All Selected</button>
|
|
1598
2660
|
</div>
|
|
1599
2661
|
`;
|
|
1600
2662
|
}
|
|
1601
|
-
// Band properties
|
|
2663
|
+
// ── Band properties ──
|
|
1602
2664
|
if (!el && this._selectedBandId) {
|
|
1603
2665
|
const band = this._layout.bands.find(b => b.id === this._selectedBandId);
|
|
1604
2666
|
if (!band)
|
|
1605
2667
|
return html `<div style="padding:12px;color:var(--zrd-text-muted)">Select an element</div>`;
|
|
1606
2668
|
return html `
|
|
1607
2669
|
<div class="prop-section">
|
|
1608
|
-
<
|
|
1609
|
-
|
|
2670
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
2671
|
+
<span style="font-size:16px;">${BAND_ICONS[band.type]}</span>
|
|
2672
|
+
<div>
|
|
2673
|
+
<div style="font-weight:600;font-size:13px;">${BAND_LABELS[band.type]}</div>
|
|
2674
|
+
<div style="font-size:10px;color:var(--zrd-text-muted);">${band.elements.length} elements</div>
|
|
2675
|
+
</div>
|
|
2676
|
+
</div>
|
|
1610
2677
|
</div>
|
|
1611
2678
|
|
|
1612
|
-
<div class="prop-section">
|
|
1613
|
-
|
|
1614
|
-
<div class="prop-
|
|
1615
|
-
<
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2679
|
+
<div class="prop-section" data-section="position">
|
|
2680
|
+
${sectionHeader('band-dim', 'Dimensions')}
|
|
2681
|
+
<div class="prop-section-body ${collapsed.has('band-dim') ? 'collapsed' : ''}">
|
|
2682
|
+
<div class="prop-row">
|
|
2683
|
+
<span class="prop-label">Height</span>
|
|
2684
|
+
<div style="display:flex;align-items:center;gap:4px;">
|
|
2685
|
+
<input class="prop-input" type="number" step="0.5" .value=${String(band.height)}
|
|
2686
|
+
@change=${(e) => this._updateBandHeight(band.id, Number(e.target.value))} />
|
|
2687
|
+
<span class="prop-unit">${unitLabel}</span>
|
|
2688
|
+
</div>
|
|
2689
|
+
</div>
|
|
2690
|
+
<div class="prop-row">
|
|
2691
|
+
<span class="prop-label">Background</span>
|
|
2692
|
+
<div class="prop-color-row">
|
|
2693
|
+
<div class="prop-color-swatch" style="background:${band.backgroundColor || '#ffffff'}">
|
|
2694
|
+
<input type="color" .value=${band.backgroundColor || '#ffffff'}
|
|
2695
|
+
@change=${(e) => { band.backgroundColor = e.target.value; this._commitChange(); }} />
|
|
2696
|
+
</div>
|
|
2697
|
+
<input class="prop-color-hex" .value=${band.backgroundColor || '#ffffff'}
|
|
2698
|
+
@change=${(e) => { band.backgroundColor = e.target.value; this._commitChange(); }} />
|
|
2699
|
+
</div>
|
|
2700
|
+
</div>
|
|
1624
2701
|
</div>
|
|
1625
2702
|
</div>
|
|
1626
2703
|
|
|
1627
2704
|
${band.type === 'detail' || band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
|
|
1628
|
-
<div class="prop-section">
|
|
1629
|
-
|
|
1630
|
-
<div class="prop-
|
|
1631
|
-
<span class="prop-label">DataSource</span>
|
|
1632
|
-
<select class="prop-input" .value=${band.dataSource || ''}
|
|
1633
|
-
@change=${(e) => { band.dataSource = e.target.value; this._commitChange(); }}>
|
|
1634
|
-
<option value="">-- none --</option>
|
|
1635
|
-
${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
|
|
1636
|
-
</select>
|
|
1637
|
-
</div>
|
|
1638
|
-
${band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
|
|
2705
|
+
<div class="prop-section" data-section="data">
|
|
2706
|
+
${sectionHeader('band-data', 'Data')}
|
|
2707
|
+
<div class="prop-section-body ${collapsed.has('band-data') ? 'collapsed' : ''}">
|
|
1639
2708
|
<div class="prop-row">
|
|
1640
|
-
<span class="prop-label">
|
|
1641
|
-
<
|
|
1642
|
-
|
|
1643
|
-
|
|
2709
|
+
<span class="prop-label">DataSource</span>
|
|
2710
|
+
<select class="prop-input" .value=${band.dataSource || ''}
|
|
2711
|
+
@change=${(e) => { band.dataSource = e.target.value; this._commitChange(); }}>
|
|
2712
|
+
<option value="">-- none --</option>
|
|
2713
|
+
${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
|
|
2714
|
+
</select>
|
|
1644
2715
|
</div>
|
|
1645
|
-
|
|
2716
|
+
${band.type === 'groupHeader' || band.type === 'groupFooter' ? html `
|
|
2717
|
+
<div class="prop-row">
|
|
2718
|
+
<span class="prop-label">Group By</span>
|
|
2719
|
+
<input class="prop-input" .value=${band.groupField || ''}
|
|
2720
|
+
placeholder="field name"
|
|
2721
|
+
@change=${(e) => { band.groupField = e.target.value; this._commitChange(); }} />
|
|
2722
|
+
</div>
|
|
2723
|
+
` : nothing}
|
|
2724
|
+
</div>
|
|
1646
2725
|
</div>
|
|
1647
2726
|
` : nothing}
|
|
1648
2727
|
|
|
1649
|
-
<div class="prop-section">
|
|
1650
|
-
|
|
1651
|
-
<div class="prop-
|
|
1652
|
-
<
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2728
|
+
<div class="prop-section" data-section="visibility">
|
|
2729
|
+
${sectionHeader('band-expert', 'Section Expert')}
|
|
2730
|
+
<div class="prop-section-body ${collapsed.has('band-expert') ? 'collapsed' : ''}">
|
|
2731
|
+
<div class="prop-row">
|
|
2732
|
+
<span class="prop-label">Visible</span>
|
|
2733
|
+
<div class="prop-toggle">
|
|
2734
|
+
<div class="prop-switch ${band.visible !== false ? 'active' : ''}"
|
|
2735
|
+
@click=${() => { band.visible = !(band.visible !== false); this._commitChange(); }}></div>
|
|
2736
|
+
<span style="font-size:11px;color:var(--zrd-text-muted);">${band.visible !== false ? 'On' : 'Off'}</span>
|
|
2737
|
+
</div>
|
|
2738
|
+
</div>
|
|
2739
|
+
<div class="prop-row">
|
|
2740
|
+
<span class="prop-label">Keep Together</span>
|
|
2741
|
+
<div class="prop-toggle">
|
|
2742
|
+
<div class="prop-switch ${band.keepTogether === true ? 'active' : ''}"
|
|
2743
|
+
@click=${() => { band.keepTogether = !band.keepTogether; this._commitChange(); }}></div>
|
|
2744
|
+
</div>
|
|
2745
|
+
</div>
|
|
2746
|
+
<div class="prop-row">
|
|
2747
|
+
<span class="prop-label">Repeat Page</span>
|
|
2748
|
+
<div class="prop-toggle">
|
|
2749
|
+
<div class="prop-switch ${band.repeatOnEveryPage === true ? 'active' : ''}"
|
|
2750
|
+
@click=${() => { band.repeatOnEveryPage = !band.repeatOnEveryPage; this._commitChange(); }}></div>
|
|
2751
|
+
</div>
|
|
2752
|
+
</div>
|
|
1665
2753
|
</div>
|
|
1666
2754
|
</div>
|
|
1667
2755
|
|
|
1668
|
-
<div class="prop-section"
|
|
2756
|
+
<div class="prop-section">
|
|
1669
2757
|
<button class="btn-danger" @click=${() => {
|
|
1670
2758
|
this._layout.bands = this._layout.bands.filter(b => b.id !== band.id);
|
|
1671
2759
|
this._selectedBandId = null;
|
|
@@ -1674,246 +2762,341 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
1674
2762
|
</div>
|
|
1675
2763
|
`;
|
|
1676
2764
|
}
|
|
2765
|
+
// ── Nothing selected — help card ──
|
|
1677
2766
|
if (!el)
|
|
1678
|
-
return html
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
<
|
|
1682
|
-
<div
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
<
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
<
|
|
1690
|
-
<
|
|
1691
|
-
|
|
1692
|
-
<span class="prop-unit">${unitLabel}</span>
|
|
1693
|
-
</div>
|
|
1694
|
-
<div class="prop-row">
|
|
1695
|
-
<span class="prop-label">Width</span>
|
|
1696
|
-
<input class="prop-input" type="number" step="0.5" .value=${String(el.width)}
|
|
1697
|
-
@change=${(e) => this._updateElementProp('width', Number(e.target.value))} />
|
|
1698
|
-
<span class="prop-unit">${unitLabel}</span>
|
|
1699
|
-
</div>
|
|
1700
|
-
<div class="prop-row">
|
|
1701
|
-
<span class="prop-label">Height</span>
|
|
1702
|
-
<input class="prop-input" type="number" step="0.5" .value=${String(el.height)}
|
|
1703
|
-
@change=${(e) => this._updateElementProp('height', Number(e.target.value))} />
|
|
1704
|
-
<span class="prop-unit">${unitLabel}</span>
|
|
2767
|
+
return html `
|
|
2768
|
+
<div class="help-card">
|
|
2769
|
+
<h4>\uD83C\uDFAF Quick Start</h4>
|
|
2770
|
+
<div>1. Choose page size in the bar above</div>
|
|
2771
|
+
<div>2. Drag elements from Toolbox (left panel)</div>
|
|
2772
|
+
<div>3. Click any element to edit properties (here)</div>
|
|
2773
|
+
<div>4. Use Preview button to see the final result</div>
|
|
2774
|
+
<div class="help-card-section">
|
|
2775
|
+
<h4>\u2328 Shortcuts</h4>
|
|
2776
|
+
<div><kbd>Ctrl+Z</kbd> Undo <kbd>Ctrl+Y</kbd> Redo</div>
|
|
2777
|
+
<div><kbd>Delete</kbd> Remove selected</div>
|
|
2778
|
+
<div><kbd>Ctrl+C</kbd> / <kbd>Ctrl+V</kbd> Copy / Paste</div>
|
|
2779
|
+
<div><kbd>Ctrl+Scroll</kbd> Zoom</div>
|
|
2780
|
+
<div><kbd>Shift+Click</kbd> Multi-select</div>
|
|
1705
2781
|
</div>
|
|
1706
2782
|
</div>
|
|
2783
|
+
`;
|
|
2784
|
+
// ── Single element selected ──
|
|
2785
|
+
const typeBadgeColors = {
|
|
2786
|
+
text: 'background:#e3f2fd;color:#1565c0;',
|
|
2787
|
+
field: 'background:#e8f5e9;color:#2e7d32;',
|
|
2788
|
+
image: 'background:#f3e5f5;color:#7b1fa2;',
|
|
2789
|
+
line: 'background:#eceff1;color:#546e7a;',
|
|
2790
|
+
rect: 'background:#eceff1;color:#546e7a;',
|
|
2791
|
+
barcode: 'background:#fff3e0;color:#e65100;',
|
|
2792
|
+
pageNumber: 'background:#fce4ec;color:#c62828;',
|
|
2793
|
+
currentDate: 'background:#fff8e1;color:#f57f17;',
|
|
2794
|
+
};
|
|
2795
|
+
const badgeStyle = typeBadgeColors[el.type] || 'background:#f5f5f5;color:#333;';
|
|
2796
|
+
return html `
|
|
2797
|
+
<!-- Element type badge -->
|
|
2798
|
+
<div class="prop-section" style="display:flex;align-items:center;gap:8px;">
|
|
2799
|
+
<span class="element-type-badge" style="${badgeStyle}">${ELEMENT_TYPE_ICONS[el.type] || '?'} ${el.type}</span>
|
|
2800
|
+
</div>
|
|
1707
2801
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
<
|
|
1713
|
-
|
|
1714
|
-
|
|
2802
|
+
<!-- Position -->
|
|
2803
|
+
<div class="prop-section" data-section="position">
|
|
2804
|
+
${sectionHeader('el-pos', 'Position')}
|
|
2805
|
+
<div class="prop-section-body ${collapsed.has('el-pos') ? 'collapsed' : ''}">
|
|
2806
|
+
<div class="prop-grid-2x2">
|
|
2807
|
+
<div class="prop-grid-item">
|
|
2808
|
+
<span class="prop-grid-label">X</span>
|
|
2809
|
+
<input type="number" step="0.5" .value=${String(el.x)}
|
|
2810
|
+
@change=${(e) => this._updateElementProp('x', Number(e.target.value))} />
|
|
2811
|
+
<span class="prop-unit">${unitLabel}</span>
|
|
2812
|
+
</div>
|
|
2813
|
+
<div class="prop-grid-item">
|
|
2814
|
+
<span class="prop-grid-label">Y</span>
|
|
2815
|
+
<input type="number" step="0.5" .value=${String(el.y)}
|
|
2816
|
+
@change=${(e) => this._updateElementProp('y', Number(e.target.value))} />
|
|
2817
|
+
<span class="prop-unit">${unitLabel}</span>
|
|
2818
|
+
</div>
|
|
2819
|
+
<div class="prop-grid-item">
|
|
2820
|
+
<span class="prop-grid-label">W</span>
|
|
2821
|
+
<input type="number" step="0.5" .value=${String(el.width)}
|
|
2822
|
+
@change=${(e) => this._updateElementProp('width', Number(e.target.value))} />
|
|
2823
|
+
<span class="prop-unit">${unitLabel}</span>
|
|
2824
|
+
</div>
|
|
2825
|
+
<div class="prop-grid-item">
|
|
2826
|
+
<span class="prop-grid-label">H</span>
|
|
2827
|
+
<input type="number" step="0.5" .value=${String(el.height)}
|
|
2828
|
+
@change=${(e) => this._updateElementProp('height', Number(e.target.value))} />
|
|
2829
|
+
<span class="prop-unit">${unitLabel}</span>
|
|
2830
|
+
</div>
|
|
2831
|
+
</div>
|
|
1715
2832
|
</div>
|
|
1716
2833
|
</div>
|
|
1717
2834
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
<
|
|
1730
|
-
|
|
1731
|
-
</
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
<
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
<
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
</
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
<
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2835
|
+
<!-- Style -->
|
|
2836
|
+
<div class="prop-section" data-section="style">
|
|
2837
|
+
${sectionHeader('el-style', 'Style')}
|
|
2838
|
+
<div class="prop-section-body ${collapsed.has('el-style') ? 'collapsed' : ''}">
|
|
2839
|
+
<!-- Font family + size -->
|
|
2840
|
+
<div class="prop-font-row">
|
|
2841
|
+
<select class="prop-input" .value=${el.style?.fontFamily || 'Arial'}
|
|
2842
|
+
@change=${(e) => this._updateElementStyle('fontFamily', e.target.value)}>
|
|
2843
|
+
<option>Arial</option><option>Times New Roman</option><option>Courier New</option>
|
|
2844
|
+
<option>Verdana</option><option>Georgia</option><option>Helvetica</option>
|
|
2845
|
+
</select>
|
|
2846
|
+
<input class="prop-input" type="number" .value=${String(el.style?.fontSize || 10)}
|
|
2847
|
+
@change=${(e) => this._updateElementStyle('fontSize', Number(e.target.value))} />
|
|
2848
|
+
</div>
|
|
2849
|
+
|
|
2850
|
+
<!-- Bold / Italic / Underline segmented -->
|
|
2851
|
+
<div class="prop-row" style="grid-template-columns:68px 1fr;">
|
|
2852
|
+
<span class="prop-label">Format</span>
|
|
2853
|
+
<div class="prop-segmented">
|
|
2854
|
+
<button class="prop-seg-btn ${el.style?.fontWeight === 'bold' ? 'active' : ''}" style="font-weight:bold;"
|
|
2855
|
+
@click=${() => this._updateElementStyle('fontWeight', el.style?.fontWeight === 'bold' ? 'normal' : 'bold')}>B</button>
|
|
2856
|
+
<button class="prop-seg-btn ${el.style?.fontStyle === 'italic' ? 'active' : ''}" style="font-style:italic;"
|
|
2857
|
+
@click=${() => this._updateElementStyle('fontStyle', el.style?.fontStyle === 'italic' ? 'normal' : 'italic')}>I</button>
|
|
2858
|
+
<button class="prop-seg-btn ${el.style?.textDecoration === 'underline' ? 'active' : ''}" style="text-decoration:underline;"
|
|
2859
|
+
@click=${() => this._updateElementStyle('textDecoration', el.style?.textDecoration === 'underline' ? 'none' : 'underline')}>U</button>
|
|
2860
|
+
</div>
|
|
2861
|
+
</div>
|
|
2862
|
+
|
|
2863
|
+
<!-- Horizontal alignment segmented -->
|
|
2864
|
+
<div class="prop-row" style="grid-template-columns:68px 1fr;">
|
|
2865
|
+
<span class="prop-label">Align</span>
|
|
2866
|
+
<div class="prop-segmented">
|
|
2867
|
+
<button class="prop-seg-btn ${(el.style?.textAlign || 'left') === 'left' ? 'active' : ''}"
|
|
2868
|
+
@click=${() => this._updateElementStyle('textAlign', 'left')}>\u2190</button>
|
|
2869
|
+
<button class="prop-seg-btn ${el.style?.textAlign === 'center' ? 'active' : ''}"
|
|
2870
|
+
@click=${() => this._updateElementStyle('textAlign', 'center')}>\u2194</button>
|
|
2871
|
+
<button class="prop-seg-btn ${el.style?.textAlign === 'right' ? 'active' : ''}"
|
|
2872
|
+
@click=${() => this._updateElementStyle('textAlign', 'right')}>\u2192</button>
|
|
2873
|
+
</div>
|
|
2874
|
+
</div>
|
|
2875
|
+
|
|
2876
|
+
<!-- Vertical alignment segmented -->
|
|
2877
|
+
<div class="prop-row" style="grid-template-columns:68px 1fr;">
|
|
2878
|
+
<span class="prop-label">V.Align</span>
|
|
2879
|
+
<div class="prop-segmented">
|
|
2880
|
+
<button class="prop-seg-btn ${(el.style?.verticalAlign || 'top') === 'top' ? 'active' : ''}"
|
|
2881
|
+
@click=${() => this._updateElementStyle('verticalAlign', 'top')}>\u2191</button>
|
|
2882
|
+
<button class="prop-seg-btn ${el.style?.verticalAlign === 'middle' ? 'active' : ''}"
|
|
2883
|
+
@click=${() => this._updateElementStyle('verticalAlign', 'middle')}>\u2195</button>
|
|
2884
|
+
<button class="prop-seg-btn ${el.style?.verticalAlign === 'bottom' ? 'active' : ''}"
|
|
2885
|
+
@click=${() => this._updateElementStyle('verticalAlign', 'bottom')}>\u2193</button>
|
|
2886
|
+
</div>
|
|
2887
|
+
</div>
|
|
2888
|
+
|
|
2889
|
+
<!-- Text color -->
|
|
2890
|
+
<div class="prop-row">
|
|
2891
|
+
<span class="prop-label">Color</span>
|
|
2892
|
+
<div class="prop-color-row">
|
|
2893
|
+
<div class="prop-color-swatch" style="background:${el.style?.color || '#333333'}">
|
|
2894
|
+
<input type="color" .value=${el.style?.color || '#333333'}
|
|
2895
|
+
@change=${(e) => this._updateElementStyle('color', e.target.value)} />
|
|
2896
|
+
</div>
|
|
2897
|
+
<input class="prop-color-hex" .value=${el.style?.color || '#333333'}
|
|
2898
|
+
@change=${(e) => this._updateElementStyle('color', e.target.value)} />
|
|
2899
|
+
</div>
|
|
2900
|
+
</div>
|
|
2901
|
+
|
|
2902
|
+
<!-- Background color -->
|
|
2903
|
+
<div class="prop-row">
|
|
2904
|
+
<span class="prop-label">Bg Color</span>
|
|
2905
|
+
<div class="prop-color-row">
|
|
2906
|
+
<div class="prop-color-swatch" style="background:${el.style?.backgroundColor || '#ffffff'}">
|
|
2907
|
+
<input type="color" .value=${el.style?.backgroundColor || '#ffffff'}
|
|
2908
|
+
@change=${(e) => this._updateElementStyle('backgroundColor', e.target.value)} />
|
|
2909
|
+
</div>
|
|
2910
|
+
<input class="prop-color-hex" .value=${el.style?.backgroundColor || '#ffffff'}
|
|
2911
|
+
@change=${(e) => this._updateElementStyle('backgroundColor', e.target.value)} />
|
|
2912
|
+
</div>
|
|
2913
|
+
</div>
|
|
2914
|
+
|
|
2915
|
+
<!-- Border -->
|
|
2916
|
+
<div class="prop-row">
|
|
2917
|
+
<span class="prop-label">Border</span>
|
|
2918
|
+
<input class="prop-input" .value=${el.style?.borderBottom || ''}
|
|
2919
|
+
placeholder="1px solid #ccc"
|
|
2920
|
+
@change=${(e) => {
|
|
1777
2921
|
const v = e.target.value;
|
|
1778
2922
|
this._updateElementStyle('borderTop', v);
|
|
1779
2923
|
this._updateElementStyle('borderRight', v);
|
|
1780
2924
|
this._updateElementStyle('borderBottom', v);
|
|
1781
2925
|
this._updateElementStyle('borderLeft', v);
|
|
1782
2926
|
}} />
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
<
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2927
|
+
</div>
|
|
2928
|
+
|
|
2929
|
+
<!-- Padding -->
|
|
2930
|
+
<div class="prop-row">
|
|
2931
|
+
<span class="prop-label">Padding</span>
|
|
2932
|
+
<input class="prop-input" type="number" .value=${String(el.style?.padding || 0)}
|
|
2933
|
+
@change=${(e) => this._updateElementStyle('padding', Number(e.target.value))} />
|
|
2934
|
+
</div>
|
|
2935
|
+
|
|
2936
|
+
<!-- Word wrap toggle -->
|
|
2937
|
+
<div class="prop-row">
|
|
2938
|
+
<span class="prop-label">Word Wrap</span>
|
|
2939
|
+
<div class="prop-toggle">
|
|
2940
|
+
<div class="prop-switch ${el.style?.wordWrap === true ? 'active' : ''}"
|
|
2941
|
+
@click=${() => this._updateElementStyle('wordWrap', !el.style?.wordWrap)}></div>
|
|
2942
|
+
<span style="font-size:11px;color:var(--zrd-text-muted);">${el.style?.wordWrap ? 'On' : 'Off'}</span>
|
|
2943
|
+
</div>
|
|
2944
|
+
</div>
|
|
1793
2945
|
</div>
|
|
1794
2946
|
</div>
|
|
1795
2947
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
<
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
<
|
|
1810
|
-
<
|
|
1811
|
-
|
|
1812
|
-
|
|
2948
|
+
<!-- Visibility -->
|
|
2949
|
+
<div class="prop-section" data-section="visibility">
|
|
2950
|
+
${sectionHeader('el-vis', 'Visibility')}
|
|
2951
|
+
<div class="prop-section-body ${collapsed.has('el-vis') ? 'collapsed' : ''}">
|
|
2952
|
+
<div class="prop-row">
|
|
2953
|
+
<span class="prop-label">Visible</span>
|
|
2954
|
+
<div class="prop-toggle">
|
|
2955
|
+
<div class="prop-switch ${el.visible !== false ? 'active' : ''}"
|
|
2956
|
+
@click=${() => this._updateElementProp('visible', !(el.visible !== false))}></div>
|
|
2957
|
+
<span style="font-size:11px;color:var(--zrd-text-muted);">${el.visible !== false ? 'On' : 'Off'}</span>
|
|
2958
|
+
</div>
|
|
2959
|
+
</div>
|
|
2960
|
+
<div class="prop-row">
|
|
2961
|
+
<span class="prop-label">Print On</span>
|
|
2962
|
+
<select class="prop-input" .value=${el.printOn || 'all'}
|
|
2963
|
+
@change=${(e) => this._updateElementProp('printOn', e.target.value)}>
|
|
2964
|
+
<option value="all">All Pages</option>
|
|
2965
|
+
<option value="first">First Page Only</option>
|
|
2966
|
+
<option value="last">Last Page Only</option>
|
|
2967
|
+
<option value="odd">Odd Pages</option>
|
|
2968
|
+
<option value="even">Even Pages</option>
|
|
2969
|
+
</select>
|
|
2970
|
+
</div>
|
|
1813
2971
|
</div>
|
|
1814
2972
|
</div>
|
|
1815
2973
|
|
|
2974
|
+
<!-- Type-specific sections -->
|
|
1816
2975
|
${el.type === 'text' ? html `
|
|
1817
|
-
<div class="prop-section">
|
|
1818
|
-
|
|
1819
|
-
<div class="prop-
|
|
1820
|
-
<textarea class="prop-input"
|
|
2976
|
+
<div class="prop-section" data-section="data">
|
|
2977
|
+
${sectionHeader('el-content', 'Content')}
|
|
2978
|
+
<div class="prop-section-body ${collapsed.has('el-content') ? 'collapsed' : ''}">
|
|
2979
|
+
<textarea class="prop-input" .value=${el.content}
|
|
1821
2980
|
@change=${(e) => this._updateElementProp('content', e.target.value)}></textarea>
|
|
1822
2981
|
</div>
|
|
1823
2982
|
</div>
|
|
1824
2983
|
` : nothing}
|
|
1825
2984
|
|
|
1826
2985
|
${el.type === 'field' ? html `
|
|
1827
|
-
<div class="prop-section">
|
|
1828
|
-
|
|
1829
|
-
<div class="prop-
|
|
1830
|
-
<
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
<
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
<
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
<
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2986
|
+
<div class="prop-section" data-section="data">
|
|
2987
|
+
${sectionHeader('el-field', 'Field Binding')}
|
|
2988
|
+
<div class="prop-section-body ${collapsed.has('el-field') ? 'collapsed' : ''}">
|
|
2989
|
+
<div class="prop-row">
|
|
2990
|
+
<span class="prop-label">Source</span>
|
|
2991
|
+
<select class="prop-input" .value=${el.dataSource || ''}
|
|
2992
|
+
@change=${(e) => this._updateElementProp('dataSource', e.target.value)}>
|
|
2993
|
+
<option value="">-- select --</option>
|
|
2994
|
+
${this._layout.dataSources.map(ds => html `<option value="${ds.id}">${ds.name}</option>`)}
|
|
2995
|
+
</select>
|
|
2996
|
+
</div>
|
|
2997
|
+
<div class="prop-row">
|
|
2998
|
+
<span class="prop-label">Field</span>
|
|
2999
|
+
${this._renderFieldSelect(el)}
|
|
3000
|
+
</div>
|
|
3001
|
+
<div class="prop-row">
|
|
3002
|
+
<span class="prop-label">Format</span>
|
|
3003
|
+
<input class="prop-input" .value=${el.format || ''}
|
|
3004
|
+
placeholder="$#,##0.00"
|
|
3005
|
+
@change=${(e) => this._updateElementProp('format', e.target.value)} />
|
|
3006
|
+
</div>
|
|
3007
|
+
<div class="prop-row">
|
|
3008
|
+
<span class="prop-label">Aggregate</span>
|
|
3009
|
+
<select class="prop-input" .value=${el.aggregate || ''}
|
|
3010
|
+
@change=${(e) => this._updateElementProp('aggregate', e.target.value || undefined)}>
|
|
3011
|
+
<option value="">None</option>
|
|
3012
|
+
<option>sum</option><option>avg</option><option>count</option><option>min</option><option>max</option>
|
|
3013
|
+
</select>
|
|
3014
|
+
</div>
|
|
1854
3015
|
</div>
|
|
1855
3016
|
</div>
|
|
1856
3017
|
` : nothing}
|
|
1857
3018
|
|
|
1858
3019
|
${el.type === 'image' ? html `
|
|
1859
|
-
<div class="prop-section">
|
|
1860
|
-
|
|
1861
|
-
<div class="prop-
|
|
1862
|
-
<
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
<
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
3020
|
+
<div class="prop-section" data-section="data">
|
|
3021
|
+
${sectionHeader('el-image', 'Image')}
|
|
3022
|
+
<div class="prop-section-body ${collapsed.has('el-image') ? 'collapsed' : ''}">
|
|
3023
|
+
<div class="prop-row">
|
|
3024
|
+
<span class="prop-label">URL/Expr</span>
|
|
3025
|
+
<input class="prop-input" .value=${el.src || ''}
|
|
3026
|
+
@change=${(e) => this._updateElementProp('src', e.target.value)} />
|
|
3027
|
+
</div>
|
|
3028
|
+
<div class="prop-row">
|
|
3029
|
+
<span class="prop-label">Fit</span>
|
|
3030
|
+
<select class="prop-input" .value=${el.fit || 'contain'}
|
|
3031
|
+
@change=${(e) => this._updateElementProp('fit', e.target.value)}>
|
|
3032
|
+
<option>contain</option><option>cover</option><option>fill</option><option>none</option>
|
|
3033
|
+
</select>
|
|
3034
|
+
</div>
|
|
1872
3035
|
</div>
|
|
1873
3036
|
</div>
|
|
1874
3037
|
` : nothing}
|
|
1875
3038
|
|
|
1876
3039
|
${el.type === 'barcode' ? html `
|
|
1877
|
-
<div class="prop-section">
|
|
1878
|
-
|
|
1879
|
-
<div class="prop-
|
|
1880
|
-
<
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
<
|
|
1888
|
-
|
|
1889
|
-
|
|
3040
|
+
<div class="prop-section" data-section="data">
|
|
3041
|
+
${sectionHeader('el-barcode', 'Barcode')}
|
|
3042
|
+
<div class="prop-section-body ${collapsed.has('el-barcode') ? 'collapsed' : ''}">
|
|
3043
|
+
<div class="prop-row">
|
|
3044
|
+
<span class="prop-label">Type</span>
|
|
3045
|
+
<select class="prop-input" .value=${el.barcodeType || 'qr'}
|
|
3046
|
+
@change=${(e) => this._updateElementProp('barcodeType', e.target.value)}>
|
|
3047
|
+
<option>qr</option><option>code128</option><option>ean13</option><option>code39</option>
|
|
3048
|
+
</select>
|
|
3049
|
+
</div>
|
|
3050
|
+
<div class="prop-row">
|
|
3051
|
+
<span class="prop-label">Value</span>
|
|
3052
|
+
<input class="prop-input" .value=${el.value || ''}
|
|
3053
|
+
@change=${(e) => this._updateElementProp('value', e.target.value)} />
|
|
3054
|
+
</div>
|
|
1890
3055
|
</div>
|
|
1891
3056
|
</div>
|
|
1892
3057
|
` : nothing}
|
|
1893
3058
|
|
|
1894
3059
|
${el.type === 'pageNumber' ? html `
|
|
1895
|
-
<div class="prop-section">
|
|
1896
|
-
|
|
1897
|
-
<div class="prop-
|
|
1898
|
-
<
|
|
1899
|
-
|
|
1900
|
-
|
|
3060
|
+
<div class="prop-section" data-section="data">
|
|
3061
|
+
${sectionHeader('el-pagenum', 'Page Number')}
|
|
3062
|
+
<div class="prop-section-body ${collapsed.has('el-pagenum') ? 'collapsed' : ''}">
|
|
3063
|
+
<div class="prop-row">
|
|
3064
|
+
<span class="prop-label">Format</span>
|
|
3065
|
+
<input class="prop-input" .value=${el.format || 'Page {page} of {pages}'}
|
|
3066
|
+
@change=${(e) => this._updateElementProp('format', e.target.value)} />
|
|
3067
|
+
</div>
|
|
1901
3068
|
</div>
|
|
1902
3069
|
</div>
|
|
1903
3070
|
` : nothing}
|
|
1904
3071
|
|
|
1905
3072
|
${el.type === 'currentDate' ? html `
|
|
1906
|
-
<div class="prop-section">
|
|
1907
|
-
|
|
1908
|
-
<div class="prop-
|
|
1909
|
-
<
|
|
1910
|
-
|
|
1911
|
-
|
|
3073
|
+
<div class="prop-section" data-section="data">
|
|
3074
|
+
${sectionHeader('el-date', 'Date Format')}
|
|
3075
|
+
<div class="prop-section-body ${collapsed.has('el-date') ? 'collapsed' : ''}">
|
|
3076
|
+
<div class="prop-row">
|
|
3077
|
+
<span class="prop-label">Format</span>
|
|
3078
|
+
<input class="prop-input" .value=${el.format || 'dd/MM/yyyy'}
|
|
3079
|
+
@change=${(e) => this._updateElementProp('format', e.target.value)} />
|
|
3080
|
+
</div>
|
|
1912
3081
|
</div>
|
|
1913
3082
|
</div>
|
|
1914
3083
|
` : nothing}
|
|
1915
3084
|
|
|
1916
|
-
|
|
3085
|
+
<!-- Z-Order -->
|
|
3086
|
+
<div class="prop-section">
|
|
3087
|
+
${sectionHeader('el-zorder', 'Z-Order')}
|
|
3088
|
+
<div class="prop-section-body ${collapsed.has('el-zorder') ? 'collapsed' : ''}">
|
|
3089
|
+
<div class="z-order-buttons">
|
|
3090
|
+
<button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'front')} title="Bring to Front">\u2B06 Front</button>
|
|
3091
|
+
<button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'forward')} title="Bring Forward">\u2191 Fwd</button>
|
|
3092
|
+
<button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'backward')} title="Send Backward">\u2193 Bwd</button>
|
|
3093
|
+
<button class="btn-small" @click=${() => this._changeZOrder(el.id, this._selectedBandId, 'back')} title="Send to Back">\u2B07 Back</button>
|
|
3094
|
+
</div>
|
|
3095
|
+
</div>
|
|
3096
|
+
</div>
|
|
3097
|
+
|
|
3098
|
+
<!-- Delete -->
|
|
3099
|
+
<div class="prop-section">
|
|
1917
3100
|
<button class="btn-danger" @click=${this._deleteSelectedElement}>Delete Element</button>
|
|
1918
3101
|
</div>
|
|
1919
3102
|
`;
|
|
@@ -1996,6 +3179,27 @@ __decorate([
|
|
|
1996
3179
|
__decorate([
|
|
1997
3180
|
state()
|
|
1998
3181
|
], ZenttoReportDesigner.prototype, "_zoom", void 0);
|
|
3182
|
+
__decorate([
|
|
3183
|
+
state()
|
|
3184
|
+
], ZenttoReportDesigner.prototype, "_showPageSetup", void 0);
|
|
3185
|
+
__decorate([
|
|
3186
|
+
state()
|
|
3187
|
+
], ZenttoReportDesigner.prototype, "_showBandMenu", void 0);
|
|
3188
|
+
__decorate([
|
|
3189
|
+
state()
|
|
3190
|
+
], ZenttoReportDesigner.prototype, "_mobileLeftOpen", void 0);
|
|
3191
|
+
__decorate([
|
|
3192
|
+
state()
|
|
3193
|
+
], ZenttoReportDesigner.prototype, "_mobileRightOpen", void 0);
|
|
3194
|
+
__decorate([
|
|
3195
|
+
state()
|
|
3196
|
+
], ZenttoReportDesigner.prototype, "_guides", void 0);
|
|
3197
|
+
__decorate([
|
|
3198
|
+
state()
|
|
3199
|
+
], ZenttoReportDesigner.prototype, "_collapsedSections", void 0);
|
|
3200
|
+
__decorate([
|
|
3201
|
+
state()
|
|
3202
|
+
], ZenttoReportDesigner.prototype, "_pageSetupDraft", void 0);
|
|
1999
3203
|
ZenttoReportDesigner = __decorate([
|
|
2000
3204
|
customElement('zentto-report-designer')
|
|
2001
3205
|
], ZenttoReportDesigner);
|