pict-section-formeditor 1.0.10 → 1.0.12

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.
@@ -40,6 +40,10 @@
40
40
  {
41
41
  "from": "../../user-documentation/solverfunctions/*",
42
42
  "to": "./dist/docs/solverfunctions/"
43
+ },
44
+ {
45
+ "from": "../../user-documentation/solveroperators/*",
46
+ "to": "./dist/docs/solveroperators/"
43
47
  }
44
48
  ],
45
49
  "dependencies": {
@@ -40,6 +40,10 @@
40
40
  {
41
41
  "from": "../../user-documentation/solverfunctions/*",
42
42
  "to": "./dist/docs/solverfunctions/"
43
+ },
44
+ {
45
+ "from": "../../user-documentation/solveroperators/*",
46
+ "to": "./dist/docs/solveroperators/"
43
47
  }
44
48
  ],
45
49
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-formeditor",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Pict visual editor for pict-section-form configurations",
5
5
  "main": "source/Pict-Section-FormEditor.js",
6
6
  "scripts": {
@@ -24,16 +24,16 @@
24
24
  },
25
25
  "homepage": "https://github.com/stevenvelozo/pict-section-formeditor#readme",
26
26
  "dependencies": {
27
- "pict-section-code": "^1.0.3",
28
- "pict-section-content": "^0.0.7",
29
- "pict-section-form": "^1.0.193",
30
- "pict-section-markdowneditor": "^1.0.1",
27
+ "pict-section-code": "^1.0.4",
28
+ "pict-section-content": "^0.0.11",
29
+ "pict-section-form": "^1.0.195",
30
+ "pict-section-markdowneditor": "^1.0.7",
31
31
  "pict-section-objecteditor": "^1.0.1",
32
32
  "pict-view": "^1.0.67"
33
33
  },
34
34
  "devDependencies": {
35
- "pict": "^1.0.355",
36
- "quackage": "^1.0.59"
35
+ "pict": "^1.0.357",
36
+ "quackage": "^1.0.64"
37
37
  },
38
38
  "mocha": {
39
39
  "diff": true,
@@ -10,8 +10,8 @@ module.exports = (
10
10
  // Address in AppData where the form configuration manifest lives
11
11
  ManifestDataAddress: false,
12
12
 
13
- // Which tab is active by default: 'visual', 'objecteditor', 'json'
14
- ActiveTab: 'visual',
13
+ // Which tab is active by default: 'formoverview', 'visual', 'objecteditor', 'json', etc.
14
+ ActiveTab: 'formoverview',
15
15
 
16
16
  // Extended descriptor properties to display in the Input properties panel.
17
17
  // Each entry defines a custom field that maps to a dot-notation address
@@ -3740,6 +3740,258 @@ module.exports = (
3740
3740
  .pict-fe-help-body .pict-content-code-wrap .attr-name { color: #986801 !important; }
3741
3741
  .pict-fe-help-body .pict-content .pict-content-code-wrap .attr-value,
3742
3742
  .pict-fe-help-body .pict-content-code-wrap .attr-value { color: #50A14F !important; }
3743
+
3744
+ /* ---- JSON Tab Header ---- */
3745
+ .pict-fe-json-header
3746
+ {
3747
+ display: flex;
3748
+ align-items: center;
3749
+ padding: 6px 12px;
3750
+ border-bottom: 1px solid #E8E0D4;
3751
+ background: #FDFCFA;
3752
+ }
3753
+ .pict-fe-json-readonly-label
3754
+ {
3755
+ display: flex;
3756
+ align-items: center;
3757
+ gap: 6px;
3758
+ font-size: 12px;
3759
+ color: #3D3229;
3760
+ cursor: pointer;
3761
+ user-select: none;
3762
+ }
3763
+ .pict-fe-json-readonly-label input[type="checkbox"]
3764
+ {
3765
+ cursor: pointer;
3766
+ }
3767
+
3768
+ /* ---- Form Overview Tab ---- */
3769
+ .pict-fe-overview-header
3770
+ {
3771
+ display: flex;
3772
+ align-items: center;
3773
+ justify-content: space-between;
3774
+ padding: 8px 12px;
3775
+ border-bottom: 1px solid #E8E0D4;
3776
+ }
3777
+ .pict-fe-overview-title
3778
+ {
3779
+ font-size: 15px;
3780
+ font-weight: 600;
3781
+ color: #3D3229;
3782
+ }
3783
+ .pict-fe-overview-labels
3784
+ {
3785
+ display: flex;
3786
+ align-items: center;
3787
+ gap: 6px;
3788
+ padding: 6px 6px 2px 6px;
3789
+ }
3790
+ .pict-fe-overview-label
3791
+ {
3792
+ font-size: 10px;
3793
+ font-weight: 600;
3794
+ text-transform: uppercase;
3795
+ letter-spacing: 0.5px;
3796
+ color: #8A7F72;
3797
+ padding: 0 6px;
3798
+ }
3799
+ .pict-fe-overview-actions-spacer
3800
+ {
3801
+ width: 64px;
3802
+ flex-shrink: 0;
3803
+ }
3804
+ .pict-fe-overview-tree
3805
+ {
3806
+ display: flex;
3807
+ flex-direction: column;
3808
+ padding: 4px 6px;
3809
+ }
3810
+ .pict-fe-overview-row
3811
+ {
3812
+ display: flex;
3813
+ align-items: center;
3814
+ gap: 6px;
3815
+ padding: 3px 6px;
3816
+ border-radius: 3px;
3817
+ transition: background 0.1s;
3818
+ }
3819
+ .pict-fe-overview-row:hover
3820
+ {
3821
+ background: #F5F0E8;
3822
+ }
3823
+ .pict-fe-overview-section
3824
+ {
3825
+ font-weight: 600;
3826
+ margin-top: 6px;
3827
+ }
3828
+ .pict-fe-overview-section:first-child
3829
+ {
3830
+ margin-top: 0;
3831
+ }
3832
+ .pict-fe-overview-group
3833
+ {
3834
+ font-weight: 500;
3835
+ }
3836
+ .pict-fe-overview-input
3837
+ {
3838
+ font-weight: 400;
3839
+ }
3840
+ .pict-fe-overview-indent
3841
+ {
3842
+ flex-shrink: 0;
3843
+ }
3844
+ .pict-fe-overview-depth-0
3845
+ {
3846
+ width: 0px;
3847
+ }
3848
+ .pict-fe-overview-depth-1
3849
+ {
3850
+ width: 20px;
3851
+ border-left: 2px solid #E8E0D4;
3852
+ margin-left: 6px;
3853
+ height: 100%;
3854
+ }
3855
+ .pict-fe-overview-depth-2
3856
+ {
3857
+ width: 40px;
3858
+ border-left: 2px solid #E8E0D4;
3859
+ margin-left: 26px;
3860
+ height: 100%;
3861
+ }
3862
+ .pict-fe-overview-icon
3863
+ {
3864
+ flex-shrink: 0;
3865
+ display: flex;
3866
+ align-items: center;
3867
+ width: 16px;
3868
+ }
3869
+ .pict-fe-overview-field
3870
+ {
3871
+ padding: 3px 6px;
3872
+ border: 1px solid #E8E3DA;
3873
+ border-radius: 3px;
3874
+ font-size: 12px;
3875
+ font-family: inherit;
3876
+ color: #3D3229;
3877
+ background: #FFF;
3878
+ box-sizing: border-box;
3879
+ transition: border-color 0.15s, box-shadow 0.15s;
3880
+ min-width: 0;
3881
+ }
3882
+ .pict-fe-overview-field:focus
3883
+ {
3884
+ outline: none;
3885
+ border-color: #9E6B47;
3886
+ box-shadow: 0 0 0 2px rgba(158, 107, 71, 0.15);
3887
+ }
3888
+ .pict-fe-overview-field-name
3889
+ {
3890
+ flex: 2;
3891
+ min-width: 80px;
3892
+ }
3893
+ .pict-fe-overview-field-hash
3894
+ {
3895
+ flex: 2;
3896
+ min-width: 60px;
3897
+ font-family: monospace;
3898
+ font-size: 11px;
3899
+ color: #8A7F72;
3900
+ }
3901
+ .pict-fe-overview-field-address
3902
+ {
3903
+ flex: 3;
3904
+ min-width: 80px;
3905
+ font-family: monospace;
3906
+ font-size: 11px;
3907
+ color: #6B7F5A;
3908
+ }
3909
+ .pict-fe-overview-actions
3910
+ {
3911
+ display: flex;
3912
+ gap: 4px;
3913
+ flex-shrink: 0;
3914
+ }
3915
+ .pict-fe-overview-empty
3916
+ {
3917
+ padding: 24px;
3918
+ text-align: center;
3919
+ color: #8A7F72;
3920
+ font-size: 13px;
3921
+ }
3922
+ .pict-fe-overview-empty-inline
3923
+ {
3924
+ color: #8A7F72;
3925
+ font-size: 12px;
3926
+ font-style: italic;
3927
+ padding: 4px 0;
3928
+ }
3929
+ .pict-fe-overview-row-separator
3930
+ {
3931
+ display: flex;
3932
+ align-items: center;
3933
+ gap: 6px;
3934
+ padding: 2px 6px;
3935
+ margin-top: 2px;
3936
+ }
3937
+ .pict-fe-overview-row-separator-label
3938
+ {
3939
+ font-size: 10px;
3940
+ color: #8A7F72;
3941
+ text-transform: uppercase;
3942
+ letter-spacing: 0.5px;
3943
+ white-space: nowrap;
3944
+ font-weight: 600;
3945
+ }
3946
+ .pict-fe-overview-row-separator-line
3947
+ {
3948
+ flex: 1;
3949
+ height: 1px;
3950
+ background: #D4C9B8;
3951
+ border: none;
3952
+ }
3953
+ .pict-fe-overview-column
3954
+ {
3955
+ font-weight: 400;
3956
+ }
3957
+ .pict-fe-overview-layout-badge
3958
+ {
3959
+ display: inline-block;
3960
+ font-size: 10px;
3961
+ padding: 1px 6px;
3962
+ border-radius: 3px;
3963
+ background: #E8E0D4;
3964
+ color: #6B5D4F;
3965
+ white-space: nowrap;
3966
+ font-weight: 600;
3967
+ }
3968
+ .pict-fe-overview-column-empty
3969
+ {
3970
+ display: flex;
3971
+ align-items: center;
3972
+ gap: 6px;
3973
+ padding: 2px 6px;
3974
+ }
3975
+ .pict-fe-overview-row[draggable]
3976
+ {
3977
+ cursor: grab;
3978
+ }
3979
+ .pict-fe-overview-row[draggable]:active
3980
+ {
3981
+ cursor: grabbing;
3982
+ }
3983
+ .pict-fe-overview-dragging
3984
+ {
3985
+ opacity: 0.4;
3986
+ }
3987
+ .pict-fe-overview-drop-above
3988
+ {
3989
+ border-top: 2px solid #8B6914;
3990
+ }
3991
+ .pict-fe-overview-drop-below
3992
+ {
3993
+ border-bottom: 2px solid #8B6914;
3994
+ }
3743
3995
  `,
3744
3996
 
3745
3997
  Templates:
@@ -1406,6 +1406,35 @@ class FormEditorManifestOps extends libPictProvider
1406
1406
  }
1407
1407
  }
1408
1408
 
1409
+ /**
1410
+ * Normalize row numbers across all groups in the manifest.
1411
+ * Fixes gaps in row numbering (e.g. rows 5, 8, 12 become 1, 2, 3).
1412
+ * Called after importing or pasting JSON to clean up messy manifests.
1413
+ */
1414
+ normalizeAllRowNumbers()
1415
+ {
1416
+ let tmpManifest = this._resolveManifestData();
1417
+ if (!tmpManifest || !Array.isArray(tmpManifest.Sections))
1418
+ {
1419
+ return;
1420
+ }
1421
+
1422
+ this._ensureSectionGroups();
1423
+
1424
+ for (let i = 0; i < tmpManifest.Sections.length; i++)
1425
+ {
1426
+ let tmpSection = tmpManifest.Sections[i];
1427
+ if (!tmpSection || !Array.isArray(tmpSection.Groups))
1428
+ {
1429
+ continue;
1430
+ }
1431
+ for (let j = 0; j < tmpSection.Groups.length; j++)
1432
+ {
1433
+ this._reindexGroupRows(i, j);
1434
+ }
1435
+ }
1436
+ }
1437
+
1409
1438
  /**
1410
1439
  * Swap two Descriptor keys in the manifest's Descriptors object to
1411
1440
  * control intra-row input ordering (Object.keys insertion order).
@@ -22,15 +22,17 @@ class FormEditorRendering extends libPictProvider
22
22
 
23
23
  // Tab bar
24
24
  tmpHTML += '<div class="pict-fe-tabbar">';
25
- tmpHTML += `<button class="pict-fe-tab pict-fe-tab-active" id="FormEditor-Tab-Visual-${tmpHash}" onclick="${tmpViewRef}.switchTab('visual')">Visual Editor</button>`;
25
+ tmpHTML += `<button class="pict-fe-tab pict-fe-tab-active" id="FormEditor-Tab-FormOverview-${tmpHash}" onclick="${tmpViewRef}.switchTab('formoverview')">Form Overview</button>`;
26
+ tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-Visual-${tmpHash}" onclick="${tmpViewRef}.switchTab('visual')">Visual Editor</button>`;
26
27
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-SolverEditor-${tmpHash}" onclick="${tmpViewRef}.switchTab('solvereditor')">Solver Editor</button>`;
27
28
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-Solvers-${tmpHash}" onclick="${tmpViewRef}.switchTab('solvers')">Solvers</button>`;
28
29
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-ListData-${tmpHash}" onclick="${tmpViewRef}.switchTab('listdata')">List Data</button>`;
29
30
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-EntityData-${tmpHash}" onclick="${tmpViewRef}.switchTab('entitydata')">Providers</button>`;
30
- tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-ObjectEditor-${tmpHash}" onclick="${tmpViewRef}.switchTab('objecteditor')">Object Editor</button>`;
31
31
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-JSON-${tmpHash}" onclick="${tmpViewRef}.switchTab('json')">JSON</button>`;
32
32
  tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-Import-${tmpHash}" onclick="${tmpViewRef}.switchTab('import')">Import</button>`;
33
- tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-Preview-${tmpHash}" onclick="${tmpViewRef}.switchTab('preview')">Preview</button>`;
33
+ // Object Editor and Preview tabs are hidden by default but panels remain for programmatic access
34
+ tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-ObjectEditor-${tmpHash}" onclick="${tmpViewRef}.switchTab('objecteditor')" style="display:none;">Object Editor</button>`;
35
+ tmpHTML += `<button class="pict-fe-tab" id="FormEditor-Tab-Preview-${tmpHash}" onclick="${tmpViewRef}.switchTab('preview')" style="display:none;">Preview</button>`;
34
36
  tmpHTML += '</div>';
35
37
 
36
38
  // Editor layout: tab content panels + resize handle + properties panel
@@ -39,8 +41,13 @@ class FormEditorRendering extends libPictProvider
39
41
  // Tab content panels (stacked, only one active at a time)
40
42
  tmpHTML += '<div class="pict-fe-editor-content">';
41
43
 
44
+ // Form Overview panel
45
+ tmpHTML += `<div class="pict-fe-tabcontent pict-fe-tabcontent-active" id="FormEditor-Panel-FormOverview-${tmpHash}">`;
46
+ tmpHTML += `<div id="FormEditor-FormOverviewTab-Container-${tmpHash}"></div>`;
47
+ tmpHTML += '</div>';
48
+
42
49
  // Visual editor panel
43
- tmpHTML += `<div class="pict-fe-tabcontent pict-fe-tabcontent-active" id="FormEditor-Panel-Visual-${tmpHash}"></div>`;
50
+ tmpHTML += `<div class="pict-fe-tabcontent" id="FormEditor-Panel-Visual-${tmpHash}"></div>`;
44
51
 
45
52
  // Solver editor tab panel
46
53
  tmpHTML += `<div class="pict-fe-tabcontent" id="FormEditor-Panel-SolverEditor-${tmpHash}">`;
@@ -69,6 +76,9 @@ class FormEditorRendering extends libPictProvider
69
76
 
70
77
  // JSON panel
71
78
  tmpHTML += `<div class="pict-fe-tabcontent" id="FormEditor-Panel-JSON-${tmpHash}">`;
79
+ tmpHTML += '<div class="pict-fe-json-header">';
80
+ tmpHTML += `<label class="pict-fe-json-readonly-label"><input type="checkbox" id="FormEditor-JSON-ReadOnly-${tmpHash}" checked onchange="${tmpViewRef}._toggleJSONReadOnly(this.checked)" /> Read Only</label>`;
81
+ tmpHTML += '</div>';
72
82
  tmpHTML += `<div id="FormEditor-CodeEditor-Container-${tmpHash}"></div>`;
73
83
  tmpHTML += '</div>';
74
84
 
@@ -663,6 +673,262 @@ class FormEditorRendering extends libPictProvider
663
673
  }
664
674
  }
665
675
 
676
+ renderFormOverviewTabPanel()
677
+ {
678
+ let tmpParent = this._ParentFormEditor;
679
+ let tmpHash = tmpParent.Hash;
680
+ let tmpViewRef = tmpParent._browserViewRef();
681
+ let tmpManifest = tmpParent._resolveManifestData();
682
+
683
+ if (!tmpManifest)
684
+ {
685
+ return;
686
+ }
687
+
688
+ // Ensure Groups are populated on Sections (they may be auto-generated from Descriptors)
689
+ tmpParent._ManifestOpsProvider._ensureSectionGroups();
690
+
691
+ let tmpHTML = '';
692
+
693
+ // Header
694
+ tmpHTML += '<div class="pict-fe-overview-header">';
695
+ tmpHTML += '<div class="pict-fe-overview-title">Form Overview</div>';
696
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-primary" onclick="${tmpViewRef}._formOverviewAddSection()">`;
697
+ tmpHTML += `<span class="pict-fe-icon pict-fe-icon-add">${tmpParent._IconographyProvider.getIcon('Action', 'Add', 12)}</span> Add Section</button>`;
698
+ tmpHTML += '</div>';
699
+
700
+ // Column labels
701
+ tmpHTML += '<div class="pict-fe-overview-labels">';
702
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-0"></span>';
703
+ tmpHTML += '<span class="pict-fe-overview-icon"></span>';
704
+ tmpHTML += '<span class="pict-fe-overview-label pict-fe-overview-field-name">Name</span>';
705
+ tmpHTML += '<span class="pict-fe-overview-label pict-fe-overview-field-hash">Hash</span>';
706
+ tmpHTML += '<span class="pict-fe-overview-label pict-fe-overview-field-address">Address</span>';
707
+ tmpHTML += '<span class="pict-fe-overview-actions-spacer"></span>';
708
+ tmpHTML += '</div>';
709
+
710
+ // Tree body
711
+ tmpHTML += '<div class="pict-fe-overview-tree">';
712
+
713
+ let tmpSections = tmpManifest.Sections;
714
+ if (!Array.isArray(tmpSections) || tmpSections.length === 0)
715
+ {
716
+ tmpHTML += '<div class="pict-fe-overview-empty">No sections defined. Click "Add Section" to begin.</div>';
717
+ }
718
+ else
719
+ {
720
+ for (let i = 0; i < tmpSections.length; i++)
721
+ {
722
+ tmpHTML += this._renderOverviewSection(tmpSections[i], i, tmpManifest);
723
+ }
724
+ }
725
+
726
+ tmpHTML += '</div>'; // overview-tree
727
+
728
+ this.pict.ContentAssignment.assignContent(`#FormEditor-FormOverviewTab-Container-${tmpHash}`, tmpHTML);
729
+ }
730
+
731
+ _renderOverviewSection(pSection, pSectionIndex, pManifest)
732
+ {
733
+ let tmpParent = this._ParentFormEditor;
734
+ let tmpHash = tmpParent.Hash;
735
+ let tmpViewRef = tmpParent._browserViewRef();
736
+
737
+ let tmpSName = tmpParent._UtilitiesProvider._escapeAttr(pSection.Name || '');
738
+ let tmpSHash = tmpParent._UtilitiesProvider._escapeAttr(pSection.Hash || '');
739
+
740
+ let tmpHTML = '';
741
+
742
+ // Section row
743
+ tmpHTML += `<div class="pict-fe-overview-row pict-fe-overview-section" draggable="true" ondragstart="${tmpViewRef}._formOverviewDragStart(event,'section',${pSectionIndex})" ondragover="${tmpViewRef}._formOverviewDragOver(event,'section',${pSectionIndex})" ondrop="${tmpViewRef}._formOverviewDrop(event,'section',${pSectionIndex})" ondragend="${tmpViewRef}._formOverviewDragEnd(event)">`;
744
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-0"></span>';
745
+ tmpHTML += `<span class="pict-fe-overview-icon">${tmpParent._IconographyProvider.getIcon('Section', 'Default', 14)}</span>`;
746
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-name" id="FormEditor-Overview-Section-Name-${tmpHash}-${pSectionIndex}" type="text" value="${tmpSName}" placeholder="Section Name" onblur="${tmpViewRef}._formOverviewCommitField('section',${pSectionIndex},-1,-1,'Name',this.value)" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'section',${pSectionIndex},-1,-1,'Name')" />`;
747
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-hash" id="FormEditor-Overview-Section-Hash-${tmpHash}-${pSectionIndex}" type="text" value="${tmpSHash}" placeholder="Hash" onblur="${tmpViewRef}._formOverviewCommitField('section',${pSectionIndex},-1,-1,'Hash',this.value)" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'section',${pSectionIndex},-1,-1,'Hash')" />`;
748
+ tmpHTML += '<span class="pict-fe-overview-field-address"></span>'; // empty spacer to keep columns aligned
749
+ tmpHTML += '<div class="pict-fe-overview-actions">';
750
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm" onclick="${tmpViewRef}._formOverviewAddGroup(${pSectionIndex})" title="Add Group">+ Group</button>`;
751
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm pict-fe-btn-danger" onclick="${tmpViewRef}._formOverviewRemoveSection(${pSectionIndex})" title="Remove section">\u00D7</button>`;
752
+ tmpHTML += '</div>';
753
+ tmpHTML += '</div>';
754
+
755
+ // Groups for this section
756
+ let tmpGroups = pSection.Groups;
757
+ if (Array.isArray(tmpGroups))
758
+ {
759
+ for (let j = 0; j < tmpGroups.length; j++)
760
+ {
761
+ tmpHTML += this._renderOverviewGroup(tmpGroups[j], pSectionIndex, j, pManifest);
762
+ }
763
+ }
764
+
765
+ return tmpHTML;
766
+ }
767
+
768
+ _renderOverviewGroup(pGroup, pSectionIndex, pGroupIndex, pManifest)
769
+ {
770
+ let tmpParent = this._ParentFormEditor;
771
+ let tmpHash = tmpParent.Hash;
772
+ let tmpViewRef = tmpParent._browserViewRef();
773
+
774
+ let tmpGName = tmpParent._UtilitiesProvider._escapeAttr(pGroup.Name || '');
775
+ let tmpGHash = tmpParent._UtilitiesProvider._escapeAttr(pGroup.Hash || '');
776
+
777
+ let tmpIsTabular = (pGroup.Layout === 'Tabular' || pGroup.Layout === 'RecordSet');
778
+
779
+ let tmpHTML = '';
780
+
781
+ // Group row
782
+ tmpHTML += `<div class="pict-fe-overview-row pict-fe-overview-group" draggable="true" ondragstart="${tmpViewRef}._formOverviewDragStart(event,'group',${pSectionIndex},${pGroupIndex})" ondragover="${tmpViewRef}._formOverviewDragOver(event,'group',${pSectionIndex},${pGroupIndex})" ondrop="${tmpViewRef}._formOverviewDrop(event,'group',${pSectionIndex},${pGroupIndex})" ondragend="${tmpViewRef}._formOverviewDragEnd(event)">`;
783
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-1"></span>';
784
+ tmpHTML += `<span class="pict-fe-overview-icon">${tmpParent._IconographyProvider.getIcon('Group', 'Default', 14)}</span>`;
785
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-name" id="FormEditor-Overview-Group-Name-${tmpHash}-${pSectionIndex}-${pGroupIndex}" type="text" value="${tmpGName}" placeholder="Group Name" onblur="${tmpViewRef}._formOverviewCommitField('group',${pSectionIndex},${pGroupIndex},-1,'Name',this.value)" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'group',${pSectionIndex},${pGroupIndex},-1,'Name')" />`;
786
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-hash" id="FormEditor-Overview-Group-Hash-${tmpHash}-${pSectionIndex}-${pGroupIndex}" type="text" value="${tmpGHash}" placeholder="Hash" onblur="${tmpViewRef}._formOverviewCommitField('group',${pSectionIndex},${pGroupIndex},-1,'Hash',this.value)" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'group',${pSectionIndex},${pGroupIndex},-1,'Hash')" />`;
787
+ tmpHTML += '<span class="pict-fe-overview-field-address"></span>'; // empty spacer
788
+ tmpHTML += '<div class="pict-fe-overview-actions">';
789
+ if (tmpIsTabular)
790
+ {
791
+ tmpHTML += `<span class="pict-fe-overview-layout-badge">\u229E ${pGroup.Layout}</span>`;
792
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm" onclick="${tmpViewRef}._formOverviewAddColumn(${pSectionIndex},${pGroupIndex})" title="Add Column">+ Col</button>`;
793
+ }
794
+ else
795
+ {
796
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm" onclick="${tmpViewRef}._formOverviewAddRow(${pSectionIndex},${pGroupIndex})" title="Add Row">+ Row</button>`;
797
+ }
798
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm pict-fe-btn-danger" onclick="${tmpViewRef}._formOverviewRemoveGroup(${pSectionIndex},${pGroupIndex})" title="Remove group">\u00D7</button>`;
799
+ tmpHTML += '</div>';
800
+ tmpHTML += '</div>';
801
+
802
+ if (tmpIsTabular)
803
+ {
804
+ // Tabular/RecordSet group: render columns from the ReferenceManifest
805
+ tmpHTML += this._renderOverviewTabularColumns(pGroup, pSectionIndex, pGroupIndex);
806
+ }
807
+ else
808
+ {
809
+ // Regular group: render inputs grouped by rows
810
+ tmpHTML += this._renderOverviewGroupInputs(pGroup, pSectionIndex, pGroupIndex, pManifest);
811
+ }
812
+
813
+ return tmpHTML;
814
+ }
815
+
816
+ _renderOverviewGroupInputs(pGroup, pSectionIndex, pGroupIndex, pManifest)
817
+ {
818
+ let tmpParent = this._ParentFormEditor;
819
+ let tmpHash = tmpParent.Hash;
820
+ let tmpViewRef = tmpParent._browserViewRef();
821
+
822
+ let tmpHTML = '';
823
+
824
+ // Inputs for this group (derived from Descriptors via rows)
825
+ let tmpRows = tmpParent._ManifestOpsProvider.getRowsForGroupByIndex(pSectionIndex, pGroupIndex);
826
+ for (let k = 0; k < tmpRows.length; k++)
827
+ {
828
+ let tmpRow = tmpRows[k];
829
+ if (!tmpRow || !Array.isArray(tmpRow.Inputs))
830
+ {
831
+ continue;
832
+ }
833
+
834
+ // Row separator
835
+ tmpHTML += '<div class="pict-fe-overview-row-separator">';
836
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-2"></span>';
837
+ tmpHTML += `<span class="pict-fe-overview-row-separator-label">Row ${k + 1}</span>`;
838
+ tmpHTML += '<span class="pict-fe-overview-row-separator-line"></span>';
839
+ tmpHTML += '<div class="pict-fe-overview-actions">';
840
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm" onclick="${tmpViewRef}._formOverviewAddInputToRow(${pSectionIndex},${pGroupIndex},${k})" title="Add input to this row">+ Input</button>`;
841
+ tmpHTML += '</div>';
842
+ tmpHTML += '</div>';
843
+
844
+ for (let m = 0; m < tmpRow.Inputs.length; m++)
845
+ {
846
+ let tmpAddress = tmpRow.Inputs[m];
847
+ let tmpDescriptor = (pManifest.Descriptors && pManifest.Descriptors[tmpAddress]) ? pManifest.Descriptors[tmpAddress] : null;
848
+ let tmpIName = tmpParent._UtilitiesProvider._escapeAttr(tmpDescriptor ? (tmpDescriptor.Name || '') : '');
849
+ let tmpIHash = tmpParent._UtilitiesProvider._escapeAttr(tmpDescriptor ? (tmpDescriptor.Hash || tmpAddress) : tmpAddress);
850
+ let tmpIAddr = tmpParent._UtilitiesProvider._escapeAttr(tmpAddress);
851
+
852
+ tmpHTML += `<div class="pict-fe-overview-row pict-fe-overview-input" draggable="true" ondragstart="${tmpViewRef}._formOverviewDragStart(event,'input',${pSectionIndex},${pGroupIndex},${k},${m})" ondragover="${tmpViewRef}._formOverviewDragOver(event,'input',${pSectionIndex},${pGroupIndex},${k},${m})" ondrop="${tmpViewRef}._formOverviewDrop(event,'input',${pSectionIndex},${pGroupIndex},${k},${m})" ondragend="${tmpViewRef}._formOverviewDragEnd(event)">`;
853
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-2"></span>';
854
+ let tmpType = tmpDescriptor ? (tmpDescriptor.DataType || 'String') : 'String';
855
+ let tmpDTIcon = tmpParent._IconographyProvider.getDataTypeIcon(tmpType, 12);
856
+ if (!tmpDTIcon)
857
+ {
858
+ tmpDTIcon = tmpParent._IconographyProvider.getIcon('Input', 'Default', 12);
859
+ }
860
+ tmpHTML += `<span class="pict-fe-overview-icon">${tmpDTIcon}</span>`;
861
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-name" id="FormEditor-Overview-Input-Name-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${k}-${m}" type="text" value="${tmpIName}" placeholder="Input Name" onblur="${tmpViewRef}._formOverviewCommitField('input',${pSectionIndex},${pGroupIndex},${k},'Name',this.value,${m})" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'input',${pSectionIndex},${pGroupIndex},${k},'Name',${m})" />`;
862
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-hash" id="FormEditor-Overview-Input-Hash-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${k}-${m}" type="text" value="${tmpIHash}" placeholder="Hash" onblur="${tmpViewRef}._formOverviewCommitField('input',${pSectionIndex},${pGroupIndex},${k},'Hash',this.value,${m})" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'input',${pSectionIndex},${pGroupIndex},${k},'Hash',${m})" />`;
863
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-address" id="FormEditor-Overview-Input-Address-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${k}-${m}" type="text" value="${tmpIAddr}" placeholder="Data Address" onblur="${tmpViewRef}._formOverviewCommitField('input',${pSectionIndex},${pGroupIndex},${k},'Address',this.value,${m})" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'input',${pSectionIndex},${pGroupIndex},${k},'Address',${m})" />`;
864
+ tmpHTML += '<div class="pict-fe-overview-actions">';
865
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm pict-fe-btn-danger" onclick="${tmpViewRef}._formOverviewRemoveInput(${pSectionIndex},${pGroupIndex},${k},${m})" title="Remove input">\u00D7</button>`;
866
+ tmpHTML += '</div>';
867
+ tmpHTML += '</div>';
868
+ }
869
+ }
870
+
871
+ return tmpHTML;
872
+ }
873
+
874
+ _renderOverviewTabularColumns(pGroup, pSectionIndex, pGroupIndex)
875
+ {
876
+ let tmpParent = this._ParentFormEditor;
877
+ let tmpHash = tmpParent.Hash;
878
+ let tmpViewRef = tmpParent._browserViewRef();
879
+
880
+ let tmpHTML = '';
881
+
882
+ if (!pGroup.RecordManifest)
883
+ {
884
+ tmpHTML += '<div class="pict-fe-overview-row pict-fe-overview-column-empty">';
885
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-2"></span>';
886
+ tmpHTML += '<span class="pict-fe-overview-empty-inline">(No manifest bound)</span>';
887
+ tmpHTML += '</div>';
888
+ return tmpHTML;
889
+ }
890
+
891
+ let tmpRefManifest = tmpParent._ManifestOpsProvider._resolveReferenceManifest(pGroup.RecordManifest);
892
+ if (!tmpRefManifest || !tmpRefManifest.Descriptors)
893
+ {
894
+ tmpHTML += '<div class="pict-fe-overview-row pict-fe-overview-column-empty">';
895
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-2"></span>';
896
+ tmpHTML += `<span class="pict-fe-overview-empty-inline">(Manifest "${pGroup.RecordManifest}" not found)</span>`;
897
+ tmpHTML += '</div>';
898
+ return tmpHTML;
899
+ }
900
+
901
+ let tmpColumnAddresses = Object.keys(tmpRefManifest.Descriptors);
902
+ for (let c = 0; c < tmpColumnAddresses.length; c++)
903
+ {
904
+ let tmpColAddr = tmpColumnAddresses[c];
905
+ let tmpColDesc = tmpRefManifest.Descriptors[tmpColAddr];
906
+ let tmpColName = tmpParent._UtilitiesProvider._escapeAttr(tmpColDesc ? (tmpColDesc.Name || '') : '');
907
+ let tmpColHash = tmpParent._UtilitiesProvider._escapeAttr(tmpColDesc ? (tmpColDesc.Hash || tmpColAddr) : tmpColAddr);
908
+ let tmpEscColAddr = tmpParent._UtilitiesProvider._escapeAttr(tmpColAddr);
909
+
910
+ tmpHTML += `<div class="pict-fe-overview-row pict-fe-overview-column" draggable="true" ondragstart="${tmpViewRef}._formOverviewDragStart(event,'column',${pSectionIndex},${pGroupIndex},0,${c})" ondragover="${tmpViewRef}._formOverviewDragOver(event,'column',${pSectionIndex},${pGroupIndex},0,${c})" ondrop="${tmpViewRef}._formOverviewDrop(event,'column',${pSectionIndex},${pGroupIndex},0,${c})" ondragend="${tmpViewRef}._formOverviewDragEnd(event)">`;
911
+ tmpHTML += '<span class="pict-fe-overview-indent pict-fe-overview-depth-2"></span>';
912
+
913
+ let tmpType = tmpColDesc ? (tmpColDesc.DataType || 'String') : 'String';
914
+ let tmpDTIcon = tmpParent._IconographyProvider.getDataTypeIcon(tmpType, 12);
915
+ if (!tmpDTIcon)
916
+ {
917
+ tmpDTIcon = tmpParent._IconographyProvider.getIcon('Input', 'Default', 12);
918
+ }
919
+ tmpHTML += `<span class="pict-fe-overview-icon">${tmpDTIcon}</span>`;
920
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-name" id="FormEditor-Overview-Column-Name-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${c}" type="text" value="${tmpColName}" placeholder="Column Name" onblur="${tmpViewRef}._formOverviewCommitField('column',${pSectionIndex},${pGroupIndex},-1,'Name',this.value,${c})" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'column',${pSectionIndex},${pGroupIndex},-1,'Name',${c})" />`;
921
+ tmpHTML += `<input class="pict-fe-overview-field pict-fe-overview-field-hash" id="FormEditor-Overview-Column-Hash-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${c}" type="text" value="${tmpColHash}" placeholder="Hash" onblur="${tmpViewRef}._formOverviewCommitField('column',${pSectionIndex},${pGroupIndex},-1,'Hash',this.value,${c})" onkeydown="${tmpViewRef}._formOverviewHandleKeydown(event,'column',${pSectionIndex},${pGroupIndex},-1,'Hash',${c})" />`;
922
+ tmpHTML += '<span class="pict-fe-overview-field-address"></span>'; // spacer — columns use Descriptor key as address
923
+ tmpHTML += '<div class="pict-fe-overview-actions">';
924
+ tmpHTML += `<button class="pict-fe-btn pict-fe-btn-sm pict-fe-btn-danger" onclick="${tmpViewRef}._formOverviewRemoveColumn(${pSectionIndex},${pGroupIndex},'${tmpEscColAddr}')" title="Remove column">\u00D7</button>`;
925
+ tmpHTML += '</div>';
926
+ tmpHTML += '</div>';
927
+ }
928
+
929
+ return tmpHTML;
930
+ }
931
+
666
932
  _renderRow(pRow, pSectionIndex, pGroupIndex, pRowIndex)
667
933
  {
668
934
  let tmpParent = this._ParentFormEditor;