pict-section-formeditor 1.0.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.
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +162 -0
- package/docs/_sidebar.md +23 -0
- package/docs/_topbar.md +5 -0
- package/docs/cover.md +12 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +224 -0
- package/docs/retold-keyword-index.json +46846 -0
- package/example_applications/form_editor/.quackage.json +10 -0
- package/example_applications/form_editor/FormEditor-Example-Application.js +226 -0
- package/example_applications/form_editor/html/icon-chooser.html +375 -0
- package/example_applications/form_editor/html/index.html +54 -0
- package/example_applications/form_editor/package.json +50 -0
- package/package.json +55 -0
- package/sample_manifests/Complex-Table.json +974 -0
- package/sample_manifests/Distill-Example.json +200 -0
- package/sample_manifests/Gradebook-Assignment.json +38 -0
- package/sample_manifests/Gradebook-Student.json +40 -0
- package/sample_manifests/Manyfest-Editor.json +347 -0
- package/sample_manifests/Simple-Form.json +232 -0
- package/sample_manifests/Simple-Table.json +79 -0
- package/source/Pict-Section-FormEditor-DefaultConfiguration.js +3321 -0
- package/source/Pict-Section-FormEditor.js +35 -0
- package/source/providers/Pict-Provider-ChildPictManager-Application.js +40 -0
- package/source/providers/Pict-Provider-ChildPictManager.js +238 -0
- package/source/providers/Pict-Provider-FormEditorDocumentation.js +356 -0
- package/source/providers/Pict-Provider-FormEditorDragDrop.js +535 -0
- package/source/providers/Pict-Provider-FormEditorIconography.js +1002 -0
- package/source/providers/Pict-Provider-FormEditorManifestOps.js +1443 -0
- package/source/providers/Pict-Provider-FormEditorRendering.js +730 -0
- package/source/providers/Pict-Provider-FormEditorUtilities.js +862 -0
- package/source/providers/Pict-Provider-PreviewCSS.js +42 -0
- package/source/views/PictView-FormEditor-InlineEditing.js +309 -0
- package/source/views/PictView-FormEditor-InputTypePicker.js +532 -0
- package/source/views/PictView-FormEditor-PropertiesPanel.js +7730 -0
- package/source/views/PictView-FormEditor.js +681 -0
- package/test/Pict-Section-FormEditor_tests.js +4102 -0
- package/user-documentation/.pict_documentation_topics.json +695 -0
- package/user-documentation/Getting-Started.md +32 -0
- package/user-documentation/Groups.md +52 -0
- package/user-documentation/Inputs.md +98 -0
- package/user-documentation/Sections.md +36 -0
- package/user-documentation/Shortcuts.md +44 -0
- package/user-documentation/Solver-Expression-Walkthrough.md +176 -0
- package/user-documentation/Solver-Expressions-Advanced.md +344 -0
- package/user-documentation/Solver-Functions.md +213 -0
- package/user-documentation/Solvers.md +81 -0
- package/user-documentation/ToC.md +18 -0
- package/user-documentation/solverfunctions/abs.md +84 -0
- package/user-documentation/solverfunctions/aggregationhistogram.md +83 -0
- package/user-documentation/solverfunctions/aggregationhistogrambyobject.md +64 -0
- package/user-documentation/solverfunctions/arrayconcat.md +64 -0
- package/user-documentation/solverfunctions/avg.md +81 -0
- package/user-documentation/solverfunctions/bucketset.md +69 -0
- package/user-documentation/solverfunctions/ceil.md +70 -0
- package/user-documentation/solverfunctions/cleanvaluearray.md +66 -0
- package/user-documentation/solverfunctions/cleanvalueobject.md +68 -0
- package/user-documentation/solverfunctions/colorgroupbackground.md +60 -0
- package/user-documentation/solverfunctions/colorinputbackground.md +62 -0
- package/user-documentation/solverfunctions/colorinputbackgroundtabular.md +64 -0
- package/user-documentation/solverfunctions/colorsectionbackground.md +59 -0
- package/user-documentation/solverfunctions/compare.md +72 -0
- package/user-documentation/solverfunctions/concat.md +73 -0
- package/user-documentation/solverfunctions/concatraw.md +73 -0
- package/user-documentation/solverfunctions/cos.md +75 -0
- package/user-documentation/solverfunctions/count.md +73 -0
- package/user-documentation/solverfunctions/countset.md +65 -0
- package/user-documentation/solverfunctions/countsetelements.md +63 -0
- package/user-documentation/solverfunctions/createarrayfromabsolutevalues.md +63 -0
- package/user-documentation/solverfunctions/createvalueobjectbyhashes.md +69 -0
- package/user-documentation/solverfunctions/cumulativesummation.md +96 -0
- package/user-documentation/solverfunctions/dateadddays.md +79 -0
- package/user-documentation/solverfunctions/dateaddhours.md +74 -0
- package/user-documentation/solverfunctions/dateaddmilliseconds.md +65 -0
- package/user-documentation/solverfunctions/dateaddminutes.md +72 -0
- package/user-documentation/solverfunctions/dateaddmonths.md +74 -0
- package/user-documentation/solverfunctions/dateaddseconds.md +66 -0
- package/user-documentation/solverfunctions/dateaddweeks.md +73 -0
- package/user-documentation/solverfunctions/dateaddyears.md +74 -0
- package/user-documentation/solverfunctions/datedaydifference.md +84 -0
- package/user-documentation/solverfunctions/datefromparts.md +81 -0
- package/user-documentation/solverfunctions/datehourdifference.md +64 -0
- package/user-documentation/solverfunctions/datemathadd.md +72 -0
- package/user-documentation/solverfunctions/datemilliseconddifference.md +64 -0
- package/user-documentation/solverfunctions/dateminutedifference.md +64 -0
- package/user-documentation/solverfunctions/datemonthdifference.md +66 -0
- package/user-documentation/solverfunctions/dateseconddifference.md +64 -0
- package/user-documentation/solverfunctions/dateweekdifference.md +65 -0
- package/user-documentation/solverfunctions/dateyeardifference.md +64 -0
- package/user-documentation/solverfunctions/differencearrays.md +59 -0
- package/user-documentation/solverfunctions/disablesolverordinal.md +58 -0
- package/user-documentation/solverfunctions/distributionhistogram.md +96 -0
- package/user-documentation/solverfunctions/distributionhistogrambyobject.md +64 -0
- package/user-documentation/solverfunctions/enablesolverordinal.md +57 -0
- package/user-documentation/solverfunctions/entryinset.md +72 -0
- package/user-documentation/solverfunctions/euler.md +77 -0
- package/user-documentation/solverfunctions/exp.md +74 -0
- package/user-documentation/solverfunctions/findfirstvaluebyexactmatch.md +67 -0
- package/user-documentation/solverfunctions/findfirstvaluebystringincludes.md +67 -0
- package/user-documentation/solverfunctions/flatten.md +76 -0
- package/user-documentation/solverfunctions/floor.md +70 -0
- package/user-documentation/solverfunctions/gaussianelimination.md +75 -0
- package/user-documentation/solverfunctions/generatearrayofobjectsfromsets.md +70 -0
- package/user-documentation/solverfunctions/generatehtmlhexcolor.md +67 -0
- package/user-documentation/solverfunctions/getvalue.md +90 -0
- package/user-documentation/solverfunctions/getvaluearray.md +64 -0
- package/user-documentation/solverfunctions/getvalueobject.md +67 -0
- package/user-documentation/solverfunctions/hidesections.md +58 -0
- package/user-documentation/solverfunctions/if.md +109 -0
- package/user-documentation/solverfunctions/iterativeseries.md +107 -0
- package/user-documentation/solverfunctions/join.md +75 -0
- package/user-documentation/solverfunctions/joinraw.md +64 -0
- package/user-documentation/solverfunctions/largestinset.md +63 -0
- package/user-documentation/solverfunctions/leastsquares.md +66 -0
- package/user-documentation/solverfunctions/linest.md +58 -0
- package/user-documentation/solverfunctions/log.md +74 -0
- package/user-documentation/solverfunctions/logvalues.md +65 -0
- package/user-documentation/solverfunctions/match.md +71 -0
- package/user-documentation/solverfunctions/matrixinverse.md +67 -0
- package/user-documentation/solverfunctions/matrixmultiply.md +71 -0
- package/user-documentation/solverfunctions/matrixtranspose.md +72 -0
- package/user-documentation/solverfunctions/matrixvectormultiply.md +69 -0
- package/user-documentation/solverfunctions/max.md +73 -0
- package/user-documentation/solverfunctions/mean.md +63 -0
- package/user-documentation/solverfunctions/median.md +79 -0
- package/user-documentation/solverfunctions/min.md +73 -0
- package/user-documentation/solverfunctions/mode.md +66 -0
- package/user-documentation/solverfunctions/objectkeystoarray.md +66 -0
- package/user-documentation/solverfunctions/objectvaluessortbyexternalobjectarray.md +65 -0
- package/user-documentation/solverfunctions/objectvaluestoarray.md +67 -0
- package/user-documentation/solverfunctions/percent.md +75 -0
- package/user-documentation/solverfunctions/pi.md +77 -0
- package/user-documentation/solverfunctions/polynomialregression.md +69 -0
- package/user-documentation/solverfunctions/predict.md +71 -0
- package/user-documentation/solverfunctions/rad.md +85 -0
- package/user-documentation/solverfunctions/randomfloat.md +63 -0
- package/user-documentation/solverfunctions/randomfloatbetween.md +72 -0
- package/user-documentation/solverfunctions/randomfloatupto.md +65 -0
- package/user-documentation/solverfunctions/randominteger.md +56 -0
- package/user-documentation/solverfunctions/randomintegerbetween.md +72 -0
- package/user-documentation/solverfunctions/randomintegerupto.md +64 -0
- package/user-documentation/solverfunctions/refreshtabularsection.md +57 -0
- package/user-documentation/solverfunctions/resolvehtmlentities.md +64 -0
- package/user-documentation/solverfunctions/round.md +111 -0
- package/user-documentation/solverfunctions/runsolvers.md +49 -0
- package/user-documentation/solverfunctions/setconcatenate.md +64 -0
- package/user-documentation/solverfunctions/setgroupvisibility.md +60 -0
- package/user-documentation/solverfunctions/setsectionvisibility.md +59 -0
- package/user-documentation/solverfunctions/setsolverordinalenabled.md +59 -0
- package/user-documentation/solverfunctions/settabularrowlength.md +57 -0
- package/user-documentation/solverfunctions/setvalue.md +65 -0
- package/user-documentation/solverfunctions/showsections.md +58 -0
- package/user-documentation/solverfunctions/sin.md +83 -0
- package/user-documentation/solverfunctions/slice.md +80 -0
- package/user-documentation/solverfunctions/smallestinset.md +63 -0
- package/user-documentation/solverfunctions/sortarray.md +58 -0
- package/user-documentation/solverfunctions/sorthistogram.md +70 -0
- package/user-documentation/solverfunctions/sorthistogrambykeys.md +69 -0
- package/user-documentation/solverfunctions/sortset.md +75 -0
- package/user-documentation/solverfunctions/sqrt.md +85 -0
- package/user-documentation/solverfunctions/stdev.md +81 -0
- package/user-documentation/solverfunctions/stdeva.md +58 -0
- package/user-documentation/solverfunctions/stdevp.md +83 -0
- package/user-documentation/solverfunctions/stringcountsegments.md +66 -0
- package/user-documentation/solverfunctions/stringgetsegments.md +74 -0
- package/user-documentation/solverfunctions/subtractingsummation.md +66 -0
- package/user-documentation/solverfunctions/sum.md +78 -0
- package/user-documentation/solverfunctions/tan.md +78 -0
- package/user-documentation/solverfunctions/tofixed.md +75 -0
- package/user-documentation/solverfunctions/unionarrays.md +59 -0
- package/user-documentation/solverfunctions/uniquearray.md +58 -0
- package/user-documentation/solverfunctions/var.md +67 -0
- package/user-documentation/solverfunctions/vara.md +58 -0
- package/user-documentation/solverfunctions/varp.md +66 -0
- package/user-documentation/solverfunctions/when.md +98 -0
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
const libPictProvider = require('pict-provider');
|
|
2
|
+
|
|
3
|
+
class FormEditorUtilities extends libPictProvider
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
|
|
9
|
+
this.serviceType = 'PictProvider';
|
|
10
|
+
|
|
11
|
+
// Back-reference to the parent FormEditor view (set after construction)
|
|
12
|
+
this._ParentFormEditor = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sanitize a string into a valid object key.
|
|
17
|
+
*
|
|
18
|
+
* Strips non-alphanumeric characters (except underscore), collapses runs of
|
|
19
|
+
* underscores, and trims leading/trailing underscores so the result is a
|
|
20
|
+
* clean, human-readable identifier.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} pString - The string to sanitize
|
|
23
|
+
* @return {string} A sanitized key, or 'INVALID' if the input is unusable
|
|
24
|
+
*/
|
|
25
|
+
sanitizeObjectKey(pString)
|
|
26
|
+
{
|
|
27
|
+
if (typeof pString !== 'string' || pString.length < 1)
|
|
28
|
+
{
|
|
29
|
+
return 'INVALID';
|
|
30
|
+
}
|
|
31
|
+
return pString
|
|
32
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
33
|
+
.replace(/_+/g, '_')
|
|
34
|
+
.replace(/^_|_$/g, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check whether a hash is an auto-generated section hash (S{n}).
|
|
39
|
+
*/
|
|
40
|
+
_isAutoGeneratedSectionHash(pHash)
|
|
41
|
+
{
|
|
42
|
+
if (typeof pHash !== 'string')
|
|
43
|
+
{
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return /^S\d+$/.test(pHash);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check whether a hash is an auto-generated input hash.
|
|
51
|
+
* Auto-generated input hashes end with _Input{n}.
|
|
52
|
+
*/
|
|
53
|
+
_isAutoGeneratedInputHash(pHash)
|
|
54
|
+
{
|
|
55
|
+
if (typeof pHash !== 'string')
|
|
56
|
+
{
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return /_Input\d+$/.test(pHash);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_escapeHTML(pString)
|
|
63
|
+
{
|
|
64
|
+
if (typeof pString !== 'string')
|
|
65
|
+
{
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
return pString
|
|
69
|
+
.replace(/&/g, '&')
|
|
70
|
+
.replace(/</g, '<')
|
|
71
|
+
.replace(/>/g, '>')
|
|
72
|
+
.replace(/"/g, '"');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_escapeAttr(pString)
|
|
76
|
+
{
|
|
77
|
+
if (typeof pString !== 'string')
|
|
78
|
+
{
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
return pString
|
|
82
|
+
.replace(/&/g, '&')
|
|
83
|
+
.replace(/"/g, '"')
|
|
84
|
+
.replace(/'/g, ''');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_truncateMiddle(pString, pMaxLength)
|
|
88
|
+
{
|
|
89
|
+
if (typeof pString !== 'string')
|
|
90
|
+
{
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
if (!pMaxLength || pString.length <= pMaxLength)
|
|
94
|
+
{
|
|
95
|
+
return pString;
|
|
96
|
+
}
|
|
97
|
+
// Show more of the beginning than the end
|
|
98
|
+
let tmpEndLength = Math.floor((pMaxLength - 1) / 3);
|
|
99
|
+
let tmpStartLength = pMaxLength - 1 - tmpEndLength;
|
|
100
|
+
return pString.substring(0, tmpStartLength) + '\u2026' + pString.substring(pString.length - tmpEndLength);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build the merged list of InputType definitions from built-in defaults and
|
|
105
|
+
* any embedder-provided overrides/additions in options.InputTypeDefinitions.
|
|
106
|
+
*
|
|
107
|
+
* Each definition is an object with:
|
|
108
|
+
* Hash — The InputType value stored in PictForm.InputType
|
|
109
|
+
* Name — A human-readable display name
|
|
110
|
+
* Description — A short description of what this type does
|
|
111
|
+
* Category — A grouping category for the picker UI
|
|
112
|
+
*
|
|
113
|
+
* Embedders can extend or override by passing InputTypeDefinitions in the
|
|
114
|
+
* view options. Entries with matching Hash values override the default;
|
|
115
|
+
* entries with new Hash values are appended.
|
|
116
|
+
*
|
|
117
|
+
* @param {object} pOptions - The merged view options
|
|
118
|
+
* @return {Array} An array of InputType definition objects
|
|
119
|
+
*/
|
|
120
|
+
_buildInputTypeDefinitions(pOptions)
|
|
121
|
+
{
|
|
122
|
+
// Built-in InputType definitions categorized by function.
|
|
123
|
+
// Each definition can include a Manifest with Descriptors for
|
|
124
|
+
// InputType-specific PictForm properties, rendered in the properties panel.
|
|
125
|
+
let tmpDefaults =
|
|
126
|
+
[
|
|
127
|
+
// Text & Content
|
|
128
|
+
{ Hash: 'TextArea', Name: 'Text Area', Description: 'Multi-line text input', Category: 'Text & Content' },
|
|
129
|
+
{ Hash: 'Markdown', Name: 'Markdown', Description: 'Markdown-formatted text editor', Category: 'Text & Content' },
|
|
130
|
+
{ Hash: 'HTML', Name: 'HTML', Description: 'Rich HTML content block', Category: 'Text & Content' },
|
|
131
|
+
|
|
132
|
+
// Selection
|
|
133
|
+
{
|
|
134
|
+
Hash: 'Option', Name: 'Option', Description: 'Dropdown select from a set of choices', Category: 'Selection',
|
|
135
|
+
Manifest:
|
|
136
|
+
{
|
|
137
|
+
Descriptors:
|
|
138
|
+
{
|
|
139
|
+
'SelectOptions': { Name: 'Select Options', Hash: 'SelectOptions', DataType: 'String', Description: 'JSON array of {id, text} option objects' },
|
|
140
|
+
'SelectOptionsPickList': { Name: 'Pick List Name', Hash: 'SelectOptionsPickList', DataType: 'String', Description: 'Dynamic pick list name from AppData' }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ Hash: 'Boolean', Name: 'Boolean', Description: 'Checkbox or toggle for true/false values', Category: 'Selection' },
|
|
145
|
+
{ Hash: 'Color', Name: 'Color', Description: 'Color picker input', Category: 'Selection' },
|
|
146
|
+
|
|
147
|
+
// Display
|
|
148
|
+
{ Hash: 'DisplayOnly', Name: 'Display Only', Description: 'Read-only display of the value with no input control', Category: 'Display', Prominent: true },
|
|
149
|
+
{ Hash: 'ReadOnly', Name: 'Read Only', Description: 'Input-styled read-only field', Category: 'Display', Prominent: true },
|
|
150
|
+
{
|
|
151
|
+
Hash: 'PreciseNumberReadOnly', Name: 'Precise Number (Read Only)', Description: 'Formatted precise number display with optional prefix/postfix', Category: 'Display', Prominent: true,
|
|
152
|
+
Manifest:
|
|
153
|
+
{
|
|
154
|
+
Descriptors:
|
|
155
|
+
{
|
|
156
|
+
'DecimalPrecision': { Name: 'Decimal Precision', Hash: 'DecimalPrecision', DataType: 'Number', Description: 'Number of decimal places to display' },
|
|
157
|
+
'AddCommas': { Name: 'Add Commas', Hash: 'AddCommas', DataType: 'Boolean', Description: 'Add thousand-separator commas to the number' },
|
|
158
|
+
'DigitsPrefix': { Name: 'Prefix', Hash: 'DigitsPrefix', DataType: 'String', Description: 'Prefix string prepended to the value (e.g. "$")' },
|
|
159
|
+
'DigitsPostfix': { Name: 'Postfix', Hash: 'DigitsPostfix', DataType: 'String', Description: 'Postfix string appended to the value (e.g. " USD")' }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{ Hash: 'Hidden', Name: 'Hidden', Description: 'Hidden input, not visible to the user', Category: 'Display' },
|
|
164
|
+
{
|
|
165
|
+
Hash: 'Chart', Name: 'Chart', Description: 'Data visualization chart', Category: 'Display',
|
|
166
|
+
Manifest:
|
|
167
|
+
{
|
|
168
|
+
Descriptors:
|
|
169
|
+
{
|
|
170
|
+
'ChartType': { Name: 'Chart Type', Hash: 'ChartType', DataType: 'String', Description: 'Chart.js type (bar, line, pie, doughnut, radar, polarArea)' },
|
|
171
|
+
'ChartLabelsAddress': { Name: 'Labels Address', Hash: 'ChartLabelsAddress', DataType: 'String', Description: 'AppData address to resolve chart labels from' },
|
|
172
|
+
'ChartLabelsSolver': { Name: 'Labels Solver', Hash: 'ChartLabelsSolver', DataType: 'String', Description: 'Fable solver expression for chart labels' },
|
|
173
|
+
'ChartDatasetsAddress': { Name: 'Datasets Address', Hash: 'ChartDatasetsAddress', DataType: 'String', Description: 'AppData address to resolve datasets from' }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{ Hash: 'Link', Name: 'Link', Description: 'Clickable hyperlink display', Category: 'Display' },
|
|
178
|
+
|
|
179
|
+
// Navigation
|
|
180
|
+
{
|
|
181
|
+
Hash: 'TabSectionSelector', Name: 'Tab Section Selector', Description: 'Selector that controls which sections are displayed as tabs', Category: 'Navigation',
|
|
182
|
+
Manifest:
|
|
183
|
+
{
|
|
184
|
+
Descriptors:
|
|
185
|
+
{
|
|
186
|
+
'TabSectionSet': { Name: 'Section Set', Hash: 'TabSectionSet', DataType: 'String', Description: 'JSON array of section hashes to show as tabs' },
|
|
187
|
+
'DefaultTabSectionHash': { Name: 'Default Tab', Hash: 'DefaultTabSectionHash', DataType: 'String', Description: 'Hash of the initially selected tab' },
|
|
188
|
+
'DefaultFromData': { Name: 'Default From Data', Hash: 'DefaultFromData', DataType: 'Boolean', Description: 'Use the data value to determine the default tab' }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
Hash: 'TabGroupSelector', Name: 'Tab Group Selector', Description: 'Selector that controls which groups are displayed as tabs', Category: 'Navigation',
|
|
194
|
+
Manifest:
|
|
195
|
+
{
|
|
196
|
+
Descriptors:
|
|
197
|
+
{
|
|
198
|
+
'TabGroupSet': { Name: 'Group Set', Hash: 'TabGroupSet', DataType: 'String', Description: 'JSON array of group hashes to show as tabs' },
|
|
199
|
+
'DefaultTabGroupHash': { Name: 'Default Tab', Hash: 'DefaultTabGroupHash', DataType: 'String', Description: 'Hash of the initially selected tab' },
|
|
200
|
+
'DefaultFromData': { Name: 'Default From Data', Hash: 'DefaultFromData', DataType: 'Boolean', Description: 'Use the data value to determine the default tab' }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// Advanced
|
|
206
|
+
{
|
|
207
|
+
Hash: 'Templated', Name: 'Templated', Description: 'Custom template-driven input rendering', Category: 'Advanced',
|
|
208
|
+
Manifest:
|
|
209
|
+
{
|
|
210
|
+
Descriptors:
|
|
211
|
+
{
|
|
212
|
+
'Template': { Name: 'Template', Hash: 'Template', DataType: 'String', Description: 'Template string for custom rendering' }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
Hash: 'TemplatedEntityLookup', Name: 'Templated Entity Lookup', Description: 'Template-driven entity search and selection', Category: 'Advanced',
|
|
218
|
+
Manifest:
|
|
219
|
+
{
|
|
220
|
+
Descriptors:
|
|
221
|
+
{
|
|
222
|
+
'Template': { Name: 'Template', Hash: 'Template', DataType: 'String', Description: 'Template string for rendering the entity display' }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
// If the embedder provided custom InputTypeDefinitions, merge them
|
|
229
|
+
let tmpCustomDefinitions = (pOptions && Array.isArray(pOptions.InputTypeDefinitions)) ? pOptions.InputTypeDefinitions : [];
|
|
230
|
+
|
|
231
|
+
if (tmpCustomDefinitions.length === 0)
|
|
232
|
+
{
|
|
233
|
+
return tmpDefaults;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Index defaults by Hash for quick lookup
|
|
237
|
+
let tmpDefaultMap = {};
|
|
238
|
+
for (let i = 0; i < tmpDefaults.length; i++)
|
|
239
|
+
{
|
|
240
|
+
tmpDefaultMap[tmpDefaults[i].Hash] = i;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Merge: override existing entries by Hash, append new ones
|
|
244
|
+
for (let i = 0; i < tmpCustomDefinitions.length; i++)
|
|
245
|
+
{
|
|
246
|
+
let tmpCustom = tmpCustomDefinitions[i];
|
|
247
|
+
if (!tmpCustom || !tmpCustom.Hash)
|
|
248
|
+
{
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (tmpDefaultMap.hasOwnProperty(tmpCustom.Hash))
|
|
253
|
+
{
|
|
254
|
+
// Override the default entry
|
|
255
|
+
let tmpIndex = tmpDefaultMap[tmpCustom.Hash];
|
|
256
|
+
tmpDefaults[tmpIndex] = Object.assign({}, tmpDefaults[tmpIndex], tmpCustom);
|
|
257
|
+
}
|
|
258
|
+
else
|
|
259
|
+
{
|
|
260
|
+
// Append as a new entry
|
|
261
|
+
tmpDefaults.push(tmpCustom);
|
|
262
|
+
tmpDefaultMap[tmpCustom.Hash] = tmpDefaults.length - 1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return tmpDefaults;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get the distinct list of InputType categories in display order.
|
|
271
|
+
*
|
|
272
|
+
* @return {Array} An array of category name strings
|
|
273
|
+
*/
|
|
274
|
+
_getInputTypeCategories()
|
|
275
|
+
{
|
|
276
|
+
let tmpCategories = [];
|
|
277
|
+
let tmpSeen = {};
|
|
278
|
+
|
|
279
|
+
for (let i = 0; i < this._ParentFormEditor._InputTypeDefinitions.length; i++)
|
|
280
|
+
{
|
|
281
|
+
let tmpCategory = this._ParentFormEditor._InputTypeDefinitions[i].Category || 'Other';
|
|
282
|
+
if (!tmpSeen[tmpCategory])
|
|
283
|
+
{
|
|
284
|
+
tmpSeen[tmpCategory] = true;
|
|
285
|
+
tmpCategories.push(tmpCategory);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return tmpCategories;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get the Manifest for a given InputType hash.
|
|
294
|
+
*
|
|
295
|
+
* Returns the Manifest object (with Descriptors) or null if the InputType
|
|
296
|
+
* has no configurable PictForm properties.
|
|
297
|
+
*
|
|
298
|
+
* @param {string} pInputTypeHash - The InputType hash (e.g. 'Option', 'Chart')
|
|
299
|
+
* @return {object|null} The Manifest object or null
|
|
300
|
+
*/
|
|
301
|
+
_getInputTypeManifest(pInputTypeHash)
|
|
302
|
+
{
|
|
303
|
+
if (!pInputTypeHash || typeof pInputTypeHash !== 'string')
|
|
304
|
+
{
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < this._ParentFormEditor._InputTypeDefinitions.length; i++)
|
|
309
|
+
{
|
|
310
|
+
if (this._ParentFormEditor._InputTypeDefinitions[i].Hash === pInputTypeHash)
|
|
311
|
+
{
|
|
312
|
+
return this._ParentFormEditor._InputTypeDefinitions[i].Manifest || null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get InputType definitions filtered by a search query.
|
|
321
|
+
*
|
|
322
|
+
* The search is case-insensitive and matches against Hash, Name,
|
|
323
|
+
* Description, and Category.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} pQuery - The search query (empty string returns all)
|
|
326
|
+
* @return {Array} Filtered InputType definition objects
|
|
327
|
+
*/
|
|
328
|
+
_filterInputTypeDefinitions(pQuery)
|
|
329
|
+
{
|
|
330
|
+
if (!pQuery || typeof pQuery !== 'string' || pQuery.trim().length === 0)
|
|
331
|
+
{
|
|
332
|
+
return this._ParentFormEditor._InputTypeDefinitions;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let tmpQuery = pQuery.trim().toLowerCase();
|
|
336
|
+
|
|
337
|
+
return this._ParentFormEditor._InputTypeDefinitions.filter(function(pDef)
|
|
338
|
+
{
|
|
339
|
+
return (
|
|
340
|
+
(pDef.Hash && pDef.Hash.toLowerCase().indexOf(tmpQuery) >= 0) ||
|
|
341
|
+
(pDef.Name && pDef.Name.toLowerCase().indexOf(tmpQuery) >= 0) ||
|
|
342
|
+
(pDef.Description && pDef.Description.toLowerCase().indexOf(tmpQuery) >= 0) ||
|
|
343
|
+
(pDef.Category && pDef.Category.toLowerCase().indexOf(tmpQuery) >= 0)
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Select an input to open it in the properties panel.
|
|
350
|
+
*
|
|
351
|
+
* @param {number} pSectionIndex - Index of the section
|
|
352
|
+
* @param {number} pGroupIndex - Index of the group
|
|
353
|
+
* @param {number} pRowIndex - Index of the row
|
|
354
|
+
* @param {number} pInputIndex - Index of the input within the row
|
|
355
|
+
*/
|
|
356
|
+
selectInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex)
|
|
357
|
+
{
|
|
358
|
+
this._ParentFormEditor._SelectedInputIndices = [pSectionIndex, pGroupIndex, pRowIndex, pInputIndex];
|
|
359
|
+
// Clear submanifest column selection
|
|
360
|
+
this._ParentFormEditor._SelectedTabularColumn = null;
|
|
361
|
+
// Also select the containing section and group
|
|
362
|
+
this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
|
|
363
|
+
this._ParentFormEditor._SelectedGroupIndices = { SectionIndex: pSectionIndex, GroupIndex: pGroupIndex };
|
|
364
|
+
|
|
365
|
+
if (this._ParentFormEditor._PropertiesPanelView)
|
|
366
|
+
{
|
|
367
|
+
this._ParentFormEditor._PropertiesPanelView.selectInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Auto-switch to properties tab and expand panel
|
|
371
|
+
this._ParentFormEditor._PanelActiveTab = 'properties';
|
|
372
|
+
if (this._ParentFormEditor._PanelCollapsed)
|
|
373
|
+
{
|
|
374
|
+
this._ParentFormEditor._PanelCollapsed = false;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
this._ParentFormEditor.renderVisualEditor();
|
|
378
|
+
|
|
379
|
+
// Align the properties panel with the selected input after layout settles
|
|
380
|
+
let tmpSelf = this;
|
|
381
|
+
setTimeout(function () { tmpSelf._alignPanelToSelection(); }, 0);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Deselect the current input. The panel stays open.
|
|
386
|
+
*/
|
|
387
|
+
deselectInput()
|
|
388
|
+
{
|
|
389
|
+
this._ParentFormEditor._SelectedInputIndices = null;
|
|
390
|
+
this._ParentFormEditor._SelectedTabularColumn = null;
|
|
391
|
+
this._ParentFormEditor._SelectedSectionIndex = null;
|
|
392
|
+
this._ParentFormEditor._SelectedGroupIndices = null;
|
|
393
|
+
|
|
394
|
+
if (this._ParentFormEditor._PropertiesPanelView)
|
|
395
|
+
{
|
|
396
|
+
this._ParentFormEditor._PropertiesPanelView.deselectAll();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this._ParentFormEditor.renderVisualEditor();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Select a section to open it in the properties panel.
|
|
404
|
+
*
|
|
405
|
+
* @param {number} pSectionIndex - Index of the section
|
|
406
|
+
*/
|
|
407
|
+
selectSection(pSectionIndex)
|
|
408
|
+
{
|
|
409
|
+
this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
|
|
410
|
+
// Clear input, tabular, and group selections
|
|
411
|
+
this._ParentFormEditor._SelectedInputIndices = null;
|
|
412
|
+
this._ParentFormEditor._SelectedTabularColumn = null;
|
|
413
|
+
this._ParentFormEditor._SelectedGroupIndices = null;
|
|
414
|
+
|
|
415
|
+
if (this._ParentFormEditor._PropertiesPanelView)
|
|
416
|
+
{
|
|
417
|
+
this._ParentFormEditor._PropertiesPanelView.selectSection(pSectionIndex);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Auto-switch to section tab and expand panel
|
|
421
|
+
this._ParentFormEditor._PanelActiveTab = 'section';
|
|
422
|
+
if (this._ParentFormEditor._PanelCollapsed)
|
|
423
|
+
{
|
|
424
|
+
this._ParentFormEditor._PanelCollapsed = false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this._ParentFormEditor.renderVisualEditor();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Select a group to open it in the properties panel.
|
|
432
|
+
*
|
|
433
|
+
* @param {number} pSectionIndex - Index of the section
|
|
434
|
+
* @param {number} pGroupIndex - Index of the group
|
|
435
|
+
*/
|
|
436
|
+
selectGroup(pSectionIndex, pGroupIndex)
|
|
437
|
+
{
|
|
438
|
+
this._ParentFormEditor._SelectedGroupIndices = { SectionIndex: pSectionIndex, GroupIndex: pGroupIndex };
|
|
439
|
+
// Clear input and tabular selections
|
|
440
|
+
this._ParentFormEditor._SelectedInputIndices = null;
|
|
441
|
+
this._ParentFormEditor._SelectedTabularColumn = null;
|
|
442
|
+
// Also select the containing section
|
|
443
|
+
this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
|
|
444
|
+
|
|
445
|
+
if (this._ParentFormEditor._PropertiesPanelView)
|
|
446
|
+
{
|
|
447
|
+
this._ParentFormEditor._PropertiesPanelView.selectGroup(pSectionIndex, pGroupIndex);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Auto-switch to group tab and expand panel
|
|
451
|
+
this._ParentFormEditor._PanelActiveTab = 'group';
|
|
452
|
+
if (this._ParentFormEditor._PanelCollapsed)
|
|
453
|
+
{
|
|
454
|
+
this._ParentFormEditor._PanelCollapsed = false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this._ParentFormEditor.renderVisualEditor();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Set the input display mode for all input cards.
|
|
462
|
+
*
|
|
463
|
+
* @param {string} pMode - 'name' or 'hash'
|
|
464
|
+
*/
|
|
465
|
+
setInputDisplayMode(pMode)
|
|
466
|
+
{
|
|
467
|
+
if (pMode === 'name' || pMode === 'hash')
|
|
468
|
+
{
|
|
469
|
+
this._ParentFormEditor._InputDisplayMode = pMode;
|
|
470
|
+
this._ParentFormEditor.renderVisualEditor();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Toggle the properties panel collapsed/expanded state.
|
|
476
|
+
*/
|
|
477
|
+
togglePropertiesPanel()
|
|
478
|
+
{
|
|
479
|
+
this._ParentFormEditor._PanelCollapsed = !this._ParentFormEditor._PanelCollapsed;
|
|
480
|
+
|
|
481
|
+
if (typeof document !== 'undefined')
|
|
482
|
+
{
|
|
483
|
+
let tmpPanelEl = document.getElementById('FormEditor-PropertiesPanel-' + this._ParentFormEditor.Hash);
|
|
484
|
+
if (tmpPanelEl)
|
|
485
|
+
{
|
|
486
|
+
if (this._ParentFormEditor._PanelCollapsed)
|
|
487
|
+
{
|
|
488
|
+
tmpPanelEl.className = 'pict-fe-properties-panel';
|
|
489
|
+
tmpPanelEl.style.width = '';
|
|
490
|
+
}
|
|
491
|
+
else
|
|
492
|
+
{
|
|
493
|
+
tmpPanelEl.className = 'pict-fe-properties-panel pict-fe-properties-panel-open';
|
|
494
|
+
tmpPanelEl.style.width = this._ParentFormEditor._PanelWidth + 'px';
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Begin resizing the properties panel by dragging the toggle bar.
|
|
502
|
+
*
|
|
503
|
+
* @param {MouseEvent} pEvent
|
|
504
|
+
*/
|
|
505
|
+
onPanelResizeStart(pEvent)
|
|
506
|
+
{
|
|
507
|
+
if (typeof document === 'undefined')
|
|
508
|
+
{
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Don't start resize if panel is collapsed — just toggle instead
|
|
513
|
+
if (this._ParentFormEditor._PanelCollapsed)
|
|
514
|
+
{
|
|
515
|
+
this.togglePropertiesPanel();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
pEvent.preventDefault();
|
|
520
|
+
this._ParentFormEditor._PanelResizing = true;
|
|
521
|
+
|
|
522
|
+
let tmpSelf = this;
|
|
523
|
+
let tmpStartX = pEvent.clientX;
|
|
524
|
+
let tmpStartWidth = this._ParentFormEditor._PanelWidth;
|
|
525
|
+
let tmpPanelEl = document.getElementById('FormEditor-PropertiesPanel-' + this._ParentFormEditor.Hash);
|
|
526
|
+
|
|
527
|
+
if (!tmpPanelEl)
|
|
528
|
+
{
|
|
529
|
+
this._ParentFormEditor._PanelResizing = false;
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Find the content area to enforce its minimum width
|
|
534
|
+
let tmpContentEl = document.querySelector('.pict-fe-editor-content');
|
|
535
|
+
let tmpContainerEl = document.querySelector('.pict-fe-editor-layout');
|
|
536
|
+
|
|
537
|
+
let tmpOnMouseMove = function(pMoveEvent)
|
|
538
|
+
{
|
|
539
|
+
if (!tmpSelf._ParentFormEditor._PanelResizing)
|
|
540
|
+
{
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Moving left increases panel width, moving right decreases it
|
|
545
|
+
let tmpDelta = tmpStartX - pMoveEvent.clientX;
|
|
546
|
+
let tmpNewWidth = Math.max(240, tmpStartWidth + tmpDelta);
|
|
547
|
+
|
|
548
|
+
// Limit so the content area doesn't shrink below its min-width (300px)
|
|
549
|
+
if (tmpContainerEl)
|
|
550
|
+
{
|
|
551
|
+
let tmpAvailable = tmpContainerEl.clientWidth - 10; // 10px for the grip
|
|
552
|
+
let tmpContentMin = 300;
|
|
553
|
+
if (tmpNewWidth > tmpAvailable - tmpContentMin)
|
|
554
|
+
{
|
|
555
|
+
tmpNewWidth = tmpAvailable - tmpContentMin;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
tmpSelf._ParentFormEditor._PanelWidth = tmpNewWidth;
|
|
560
|
+
tmpPanelEl.style.width = tmpNewWidth + 'px';
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
let tmpOnMouseUp = function()
|
|
564
|
+
{
|
|
565
|
+
tmpSelf._ParentFormEditor._PanelResizing = false;
|
|
566
|
+
document.removeEventListener('mousemove', tmpOnMouseMove);
|
|
567
|
+
document.removeEventListener('mouseup', tmpOnMouseUp);
|
|
568
|
+
|
|
569
|
+
// Persist the panel width preference
|
|
570
|
+
if (typeof localStorage !== 'undefined')
|
|
571
|
+
{
|
|
572
|
+
try
|
|
573
|
+
{
|
|
574
|
+
localStorage.setItem('pict-fe-panel-width', String(tmpSelf._ParentFormEditor._PanelWidth));
|
|
575
|
+
}
|
|
576
|
+
catch (pError)
|
|
577
|
+
{
|
|
578
|
+
// localStorage may throw in restrictive environments
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
document.addEventListener('mousemove', tmpOnMouseMove);
|
|
584
|
+
document.addEventListener('mouseup', tmpOnMouseUp);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Switch the active tab in the properties panel.
|
|
589
|
+
*
|
|
590
|
+
* @param {string} pTabName - 'form', 'section', 'group', 'properties', 'options', or 'help'
|
|
591
|
+
*/
|
|
592
|
+
setPanelTab(pTabName)
|
|
593
|
+
{
|
|
594
|
+
if (pTabName === 'form' || pTabName === 'properties' || pTabName === 'section' || pTabName === 'group' || pTabName === 'options' || pTabName === 'help')
|
|
595
|
+
{
|
|
596
|
+
this._ParentFormEditor._PanelActiveTab = pTabName;
|
|
597
|
+
// Re-render only the panel content, not the entire visual editor
|
|
598
|
+
if (this._ParentFormEditor._PropertiesPanelView)
|
|
599
|
+
{
|
|
600
|
+
this._ParentFormEditor._PropertiesPanelView.renderPanel();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Compute summary statistics about the form manifest.
|
|
607
|
+
*
|
|
608
|
+
* @returns {object} { Sections, Groups, Inputs, Descriptors }
|
|
609
|
+
*/
|
|
610
|
+
getFormStats()
|
|
611
|
+
{
|
|
612
|
+
let tmpManifest = this._ParentFormEditor._resolveManifestData();
|
|
613
|
+
let tmpStats =
|
|
614
|
+
{
|
|
615
|
+
Sections: 0,
|
|
616
|
+
Groups: 0,
|
|
617
|
+
Inputs: 0,
|
|
618
|
+
Descriptors: 0,
|
|
619
|
+
ReferenceManifests: 0,
|
|
620
|
+
TabularColumns: 0,
|
|
621
|
+
DataTypes: {},
|
|
622
|
+
InputTypes: {}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
if (!tmpManifest)
|
|
626
|
+
{
|
|
627
|
+
return tmpStats;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (Array.isArray(tmpManifest.Sections))
|
|
631
|
+
{
|
|
632
|
+
tmpStats.Sections = tmpManifest.Sections.length;
|
|
633
|
+
for (let i = 0; i < tmpManifest.Sections.length; i++)
|
|
634
|
+
{
|
|
635
|
+
let tmpSection = tmpManifest.Sections[i];
|
|
636
|
+
if (Array.isArray(tmpSection.Groups))
|
|
637
|
+
{
|
|
638
|
+
tmpStats.Groups += tmpSection.Groups.length;
|
|
639
|
+
for (let j = 0; j < tmpSection.Groups.length; j++)
|
|
640
|
+
{
|
|
641
|
+
let tmpGroup = tmpSection.Groups[j];
|
|
642
|
+
if (Array.isArray(tmpGroup.Rows))
|
|
643
|
+
{
|
|
644
|
+
for (let k = 0; k < tmpGroup.Rows.length; k++)
|
|
645
|
+
{
|
|
646
|
+
let tmpRow = tmpGroup.Rows[k];
|
|
647
|
+
if (Array.isArray(tmpRow.Inputs))
|
|
648
|
+
{
|
|
649
|
+
tmpStats.Inputs += tmpRow.Inputs.length;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (tmpManifest.Descriptors && typeof tmpManifest.Descriptors === 'object')
|
|
659
|
+
{
|
|
660
|
+
let tmpDescriptorKeys = Object.keys(tmpManifest.Descriptors);
|
|
661
|
+
tmpStats.Descriptors = tmpDescriptorKeys.length;
|
|
662
|
+
|
|
663
|
+
// Build DataType and InputType histograms
|
|
664
|
+
for (let d = 0; d < tmpDescriptorKeys.length; d++)
|
|
665
|
+
{
|
|
666
|
+
let tmpDesc = tmpManifest.Descriptors[tmpDescriptorKeys[d]];
|
|
667
|
+
if (tmpDesc)
|
|
668
|
+
{
|
|
669
|
+
let tmpDataType = tmpDesc.DataType || 'String';
|
|
670
|
+
tmpStats.DataTypes[tmpDataType] = (tmpStats.DataTypes[tmpDataType] || 0) + 1;
|
|
671
|
+
|
|
672
|
+
if (tmpDesc.PictForm && tmpDesc.PictForm.InputType)
|
|
673
|
+
{
|
|
674
|
+
let tmpInputType = tmpDesc.PictForm.InputType;
|
|
675
|
+
tmpStats.InputTypes[tmpInputType] = (tmpStats.InputTypes[tmpInputType] || 0) + 1;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (tmpManifest.ReferenceManifests && typeof tmpManifest.ReferenceManifests === 'object')
|
|
682
|
+
{
|
|
683
|
+
let tmpRefNames = Object.keys(tmpManifest.ReferenceManifests);
|
|
684
|
+
tmpStats.ReferenceManifests = tmpRefNames.length;
|
|
685
|
+
for (let r = 0; r < tmpRefNames.length; r++)
|
|
686
|
+
{
|
|
687
|
+
let tmpRefManifest = tmpManifest.ReferenceManifests[tmpRefNames[r]];
|
|
688
|
+
if (tmpRefManifest && tmpRefManifest.Descriptors && typeof tmpRefManifest.Descriptors === 'object')
|
|
689
|
+
{
|
|
690
|
+
tmpStats.TabularColumns += Object.keys(tmpRefManifest.Descriptors).length;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return tmpStats;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Enumerate all inputs in the manifest with their indices and labels.
|
|
700
|
+
*
|
|
701
|
+
* @returns {Array} Array of { SectionIndex, GroupIndex, RowIndex, InputIndex, Address, Label, SectionName }
|
|
702
|
+
*/
|
|
703
|
+
getAllInputEntries()
|
|
704
|
+
{
|
|
705
|
+
let tmpManifest = this._ParentFormEditor._resolveManifestData();
|
|
706
|
+
let tmpEntries = [];
|
|
707
|
+
|
|
708
|
+
if (!tmpManifest || !Array.isArray(tmpManifest.Sections))
|
|
709
|
+
{
|
|
710
|
+
return tmpEntries;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
for (let s = 0; s < tmpManifest.Sections.length; s++)
|
|
714
|
+
{
|
|
715
|
+
let tmpSection = tmpManifest.Sections[s];
|
|
716
|
+
if (!Array.isArray(tmpSection.Groups))
|
|
717
|
+
{
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
for (let g = 0; g < tmpSection.Groups.length; g++)
|
|
721
|
+
{
|
|
722
|
+
let tmpGroup = tmpSection.Groups[g];
|
|
723
|
+
let tmpGroupLayout = tmpGroup.Layout || 'Record';
|
|
724
|
+
|
|
725
|
+
// Record layout: enumerate rows and inputs
|
|
726
|
+
if (tmpGroupLayout === 'Record' && Array.isArray(tmpGroup.Rows))
|
|
727
|
+
{
|
|
728
|
+
for (let r = 0; r < tmpGroup.Rows.length; r++)
|
|
729
|
+
{
|
|
730
|
+
let tmpRow = tmpGroup.Rows[r];
|
|
731
|
+
if (!Array.isArray(tmpRow.Inputs))
|
|
732
|
+
{
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
for (let i = 0; i < tmpRow.Inputs.length; i++)
|
|
736
|
+
{
|
|
737
|
+
let tmpAddress = tmpRow.Inputs[i];
|
|
738
|
+
let tmpDescriptor = (tmpManifest.Descriptors && tmpManifest.Descriptors[tmpAddress]) ? tmpManifest.Descriptors[tmpAddress] : null;
|
|
739
|
+
let tmpLabel = tmpDescriptor ? (tmpDescriptor.Name || tmpDescriptor.Hash || tmpAddress) : tmpAddress;
|
|
740
|
+
let tmpHash = tmpDescriptor ? (tmpDescriptor.Hash || '') : '';
|
|
741
|
+
let tmpDataType = tmpDescriptor ? (tmpDescriptor.DataType || '') : '';
|
|
742
|
+
|
|
743
|
+
tmpEntries.push(
|
|
744
|
+
{
|
|
745
|
+
SectionIndex: s,
|
|
746
|
+
GroupIndex: g,
|
|
747
|
+
RowIndex: r,
|
|
748
|
+
InputIndex: i,
|
|
749
|
+
Address: tmpAddress,
|
|
750
|
+
Hash: tmpHash,
|
|
751
|
+
Label: tmpLabel,
|
|
752
|
+
DataType: tmpDataType,
|
|
753
|
+
SectionName: tmpSection.Name || tmpSection.Hash || ('Section ' + (s + 1)),
|
|
754
|
+
GroupName: tmpGroup.Name || tmpGroup.Hash || ('Group ' + (g + 1)),
|
|
755
|
+
RowNumber: r + 1
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Tabular / RecordSet: enumerate submanifest columns
|
|
761
|
+
else if ((tmpGroupLayout === 'Tabular' || tmpGroupLayout === 'RecordSet') && tmpGroup.RecordManifest)
|
|
762
|
+
{
|
|
763
|
+
let tmpRefManifest = this._ParentFormEditor._ManifestOpsProvider._resolveReferenceManifest(tmpGroup.RecordManifest);
|
|
764
|
+
if (tmpRefManifest && tmpRefManifest.Descriptors && typeof tmpRefManifest.Descriptors === 'object')
|
|
765
|
+
{
|
|
766
|
+
let tmpColKeys = Object.keys(tmpRefManifest.Descriptors);
|
|
767
|
+
for (let c = 0; c < tmpColKeys.length; c++)
|
|
768
|
+
{
|
|
769
|
+
let tmpColDescriptor = tmpRefManifest.Descriptors[tmpColKeys[c]];
|
|
770
|
+
let tmpLabel = tmpColDescriptor ? (tmpColDescriptor.Name || tmpColDescriptor.Hash || tmpColKeys[c]) : tmpColKeys[c];
|
|
771
|
+
let tmpHash = tmpColDescriptor ? (tmpColDescriptor.Hash || '') : '';
|
|
772
|
+
let tmpDataType = tmpColDescriptor ? (tmpColDescriptor.DataType || '') : '';
|
|
773
|
+
|
|
774
|
+
tmpEntries.push(
|
|
775
|
+
{
|
|
776
|
+
SectionIndex: s,
|
|
777
|
+
GroupIndex: g,
|
|
778
|
+
Address: tmpColKeys[c],
|
|
779
|
+
Hash: tmpHash,
|
|
780
|
+
Label: tmpLabel,
|
|
781
|
+
DataType: tmpDataType,
|
|
782
|
+
SectionName: tmpSection.Name || tmpSection.Hash || ('Section ' + (s + 1)),
|
|
783
|
+
GroupName: tmpGroup.Name || tmpGroup.Hash || ('Group ' + (g + 1)),
|
|
784
|
+
IsTabular: true
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return tmpEntries;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Scroll a specific input card into view in the visual editor.
|
|
797
|
+
*
|
|
798
|
+
* @param {number} pSectionIndex
|
|
799
|
+
* @param {number} pGroupIndex
|
|
800
|
+
* @param {number} pRowIndex
|
|
801
|
+
* @param {number} pInputIndex
|
|
802
|
+
*/
|
|
803
|
+
scrollToInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex)
|
|
804
|
+
{
|
|
805
|
+
if (typeof document === 'undefined')
|
|
806
|
+
{
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let tmpInputId = `FormEditor-Input-${this._ParentFormEditor.Hash}-${pSectionIndex}-${pGroupIndex}-${pRowIndex}-${pInputIndex}`;
|
|
811
|
+
let tmpElement = document.getElementById(tmpInputId);
|
|
812
|
+
|
|
813
|
+
if (tmpElement)
|
|
814
|
+
{
|
|
815
|
+
tmpElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Align the properties panel vertically with the currently selected input
|
|
821
|
+
* or tabular column. Also scrolls the selected element into view in the
|
|
822
|
+
* main content area if it is not already visible.
|
|
823
|
+
*
|
|
824
|
+
* Should be called after renderVisualEditor() inside a setTimeout so the
|
|
825
|
+
* browser has computed layout before we measure positions.
|
|
826
|
+
*/
|
|
827
|
+
_alignPanelToSelection()
|
|
828
|
+
{
|
|
829
|
+
// No-op: the properties panel now has a fixed header and tab bar with
|
|
830
|
+
// independently scrolling tab content. The previous approach of adding
|
|
831
|
+
// paddingTop to align the panel with the selected input caused the panel
|
|
832
|
+
// to jump. The searchable selector dropdowns provide easy navigation.
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
_updateCodeEditor()
|
|
836
|
+
{
|
|
837
|
+
let tmpManifest = this._ParentFormEditor._resolveManifestData();
|
|
838
|
+
let tmpJSON = JSON.stringify(tmpManifest, null, '\t');
|
|
839
|
+
|
|
840
|
+
if (this._ParentFormEditor._CodeEditorView)
|
|
841
|
+
{
|
|
842
|
+
if (this._ParentFormEditor._CodeEditorView.codeJar)
|
|
843
|
+
{
|
|
844
|
+
// Code editor already initialized — just update the code
|
|
845
|
+
this._ParentFormEditor._CodeEditorView.setCode(tmpJSON);
|
|
846
|
+
}
|
|
847
|
+
else
|
|
848
|
+
{
|
|
849
|
+
// First time switching to JSON tab — render the code editor
|
|
850
|
+
this._ParentFormEditor._CodeEditorView.render();
|
|
851
|
+
// After render, setCode with current manifest
|
|
852
|
+
if (this._ParentFormEditor._CodeEditorView.codeJar)
|
|
853
|
+
{
|
|
854
|
+
this._ParentFormEditor._CodeEditorView.setCode(tmpJSON);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
module.exports = FormEditorUtilities;
|
|
862
|
+
module.exports.default_configuration = {};
|