retold-facto 0.0.4

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.
Files changed (92) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.dockerignore +8 -0
  3. package/.quackage.json +19 -0
  4. package/Dockerfile +26 -0
  5. package/bin/retold-facto.js +909 -0
  6. package/examples/facto-government-data.sqlite +0 -0
  7. package/examples/government-data-catalog.json +137 -0
  8. package/examples/government-data-loader.js +1432 -0
  9. package/package.json +91 -0
  10. package/scripts/facto-download.js +425 -0
  11. package/source/Retold-Facto.js +1042 -0
  12. package/source/services/Retold-Facto-BeaconProvider.js +511 -0
  13. package/source/services/Retold-Facto-CatalogManager.js +1252 -0
  14. package/source/services/Retold-Facto-DataLakeService.js +1642 -0
  15. package/source/services/Retold-Facto-DatasetManager.js +417 -0
  16. package/source/services/Retold-Facto-IngestEngine.js +1315 -0
  17. package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
  18. package/source/services/Retold-Facto-RecordManager.js +360 -0
  19. package/source/services/Retold-Facto-SchemaManager.js +1110 -0
  20. package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
  21. package/source/services/Retold-Facto-SourceManager.js +730 -0
  22. package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
  23. package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
  24. package/source/services/web-app/codemirror-entry.js +7 -0
  25. package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
  26. package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
  27. package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
  28. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
  29. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
  30. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
  31. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
  32. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
  33. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
  34. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
  35. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
  36. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
  37. package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
  38. package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
  39. package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
  40. package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
  41. package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
  42. package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
  43. package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
  44. package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
  45. package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
  46. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
  47. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
  48. package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
  49. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
  50. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
  51. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
  52. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
  53. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
  54. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
  55. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
  56. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
  57. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
  58. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
  59. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
  60. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
  61. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
  62. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
  63. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
  64. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
  65. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
  66. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
  67. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
  68. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
  69. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
  70. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
  71. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
  72. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
  73. package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
  74. package/source/services/web-app/web/chart.min.js +20 -0
  75. package/source/services/web-app/web/codemirror-bundle.js +30099 -0
  76. package/source/services/web-app/web/css/facto-themes.css +467 -0
  77. package/source/services/web-app/web/css/facto.css +502 -0
  78. package/source/services/web-app/web/index.html +28 -0
  79. package/source/services/web-app/web/retold-facto.js +12138 -0
  80. package/source/services/web-app/web/retold-facto.js.map +1 -0
  81. package/source/services/web-app/web/retold-facto.min.js +2 -0
  82. package/source/services/web-app/web/retold-facto.min.js.map +1 -0
  83. package/source/services/web-app/web/simple/index.html +17 -0
  84. package/test/Facto_Browser_Integration_tests.js +798 -0
  85. package/test/RetoldFacto_tests.js +4117 -0
  86. package/test/fixtures/weather-readings.csv +17 -0
  87. package/test/fixtures/weather-stations.csv +9 -0
  88. package/test/model/MeadowModel-Extended.json +8497 -0
  89. package/test/model/MeadowModel-PICT.json +1 -0
  90. package/test/model/MeadowModel.json +1355 -0
  91. package/test/model/ddl/Facto.ddl +225 -0
  92. package/test/model/fable-configuration.json +14 -0
@@ -0,0 +1,636 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const libProjectionConstants = require('./projections/Facto-Projections-Constants.js');
4
+
5
+ const MICRODDL_TYPE_MAP = libProjectionConstants.MICRODDL_TYPE_MAP;
6
+ const DATATYPE_TO_SYMBOL = libProjectionConstants.DATATYPE_TO_SYMBOL;
7
+
8
+ const _ViewConfiguration =
9
+ {
10
+ ViewIdentifier: "Facto-Full-SchemaEditor",
11
+
12
+ DefaultRenderable: "Facto-Full-SchemaEditor-Content",
13
+ DefaultDestinationAddress: "#Facto-Proj-Schema-Editor-Container",
14
+
15
+ AutoRender: false,
16
+
17
+ CSS: /*css*/`
18
+ /* Schema editor */
19
+ .facto-schema-editor {
20
+ display: none;
21
+ }
22
+ .facto-schema-editor.active {
23
+ display: block;
24
+ }
25
+ .facto-schema-header {
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 1em;
29
+ margin-bottom: 1em;
30
+ }
31
+ .facto-schema-header h3 {
32
+ margin: 0;
33
+ flex: 1;
34
+ }
35
+ .facto-schema-mode-tabs {
36
+ display: flex;
37
+ gap: 0;
38
+ border: 1px solid var(--facto-border, #d6c8ae);
39
+ border-radius: 6px;
40
+ overflow: hidden;
41
+ }
42
+ .facto-schema-mode-tab {
43
+ padding: 0.35em 0.8em;
44
+ font-size: 0.78em;
45
+ cursor: pointer;
46
+ border: none;
47
+ background: transparent;
48
+ color: var(--facto-text-secondary, #786848);
49
+ transition: background 0.15s, color 0.15s;
50
+ }
51
+ .facto-schema-mode-tab:not(:last-child) {
52
+ border-right: 1px solid var(--facto-border, #d6c8ae);
53
+ }
54
+ .facto-schema-mode-tab.active {
55
+ background: var(--facto-brand-a12, rgba(24,165,160,0.12));
56
+ color: var(--facto-brand, #18a5a0);
57
+ }
58
+
59
+ /* Column builder table */
60
+ .facto-col-builder {
61
+ width: 100%;
62
+ border-collapse: collapse;
63
+ margin-bottom: 0.75em;
64
+ }
65
+ .facto-col-builder th {
66
+ text-align: left;
67
+ font-size: 0.72em;
68
+ font-weight: 600;
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.5px;
71
+ color: var(--facto-text-tertiary, #a09070);
72
+ padding: 0.5em 0.4em;
73
+ border-bottom: 1px solid var(--facto-border, #d6c8ae);
74
+ }
75
+ .facto-col-builder td {
76
+ padding: 0.35em 0.4em;
77
+ border-bottom: 1px solid var(--facto-border-subtle, #e8ddc8);
78
+ vertical-align: middle;
79
+ }
80
+ .facto-col-builder input,
81
+ .facto-col-builder select {
82
+ width: 100%;
83
+ padding: 0.3em 0.5em;
84
+ font-size: 0.85em;
85
+ border: 1px solid var(--facto-border, #d6c8ae);
86
+ border-radius: 4px;
87
+ background: var(--facto-bg-input, #fcf8f0);
88
+ color: var(--facto-text, #3a3020);
89
+ }
90
+ .facto-col-builder .facto-col-size {
91
+ width: 80px;
92
+ }
93
+ .facto-col-remove-btn {
94
+ background: transparent;
95
+ border: none;
96
+ color: var(--facto-error, #c44836);
97
+ cursor: pointer;
98
+ font-size: 1.1em;
99
+ padding: 0.2em 0.4em;
100
+ border-radius: 4px;
101
+ }
102
+ .facto-col-remove-btn:hover {
103
+ background: rgba(196, 72, 54, 0.1);
104
+ }
105
+
106
+ /* DDL editor */
107
+ .facto-ddl-editor {
108
+ width: 100%;
109
+ min-height: 160px;
110
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
111
+ font-size: 0.85em;
112
+ padding: 0.75em;
113
+ border: 1px solid var(--facto-border, #d6c8ae);
114
+ border-radius: 6px;
115
+ background: var(--facto-bg-input, #fcf8f0);
116
+ color: var(--facto-text, #3a3020);
117
+ resize: vertical;
118
+ tab-size: 4;
119
+ }
120
+
121
+ /* Schema preview */
122
+ .facto-schema-preview {
123
+ padding: 0.6em 0.8em;
124
+ font-family: 'SF Mono', 'Fira Code', monospace;
125
+ font-size: 0.78em;
126
+ background: var(--facto-bg-code, #f0e8d8);
127
+ border: 1px solid var(--facto-border-subtle, #e8ddc8);
128
+ border-radius: 6px;
129
+ white-space: pre-wrap;
130
+ max-height: 200px;
131
+ overflow: auto;
132
+ color: var(--facto-text-secondary, #786848);
133
+ margin-top: 0.5em;
134
+ }
135
+
136
+ `,
137
+
138
+ Templates:
139
+ [
140
+ {
141
+ Hash: "Facto-Full-SchemaEditor-Template",
142
+ Template: /*html*/`
143
+ <div>
144
+ <div id="Facto-Proj-Schema-Editor" class="facto-schema-editor">
145
+ <div class="facto-schema-header">
146
+ <button class="facto-btn facto-btn-secondary facto-btn-small" onclick="{~P~}.views['Facto-Full-ProjectionDetail'].closeSchemaEditor()">&larr; Back</button>
147
+ <h3 id="Facto-Proj-Schema-Title">Schema Editor</h3>
148
+ <div class="facto-schema-mode-tabs">
149
+ <button class="facto-schema-mode-tab active" id="Facto-Proj-ModeTab-Visual" onclick="{~P~}.views['Facto-Full-SchemaEditor'].switchEditorMode('visual')">Visual Builder</button>
150
+ <button class="facto-schema-mode-tab" id="Facto-Proj-ModeTab-DDL" onclick="{~P~}.views['Facto-Full-SchemaEditor'].switchEditorMode('ddl')">MicroDDL</button>
151
+ </div>
152
+ </div>
153
+
154
+ <!-- Visual Builder -->
155
+ <div id="Facto-Proj-Visual-Wrap">
156
+ <table class="facto-col-builder">
157
+ <thead>
158
+ <tr>
159
+ <th style="width:35%;">Column Name</th>
160
+ <th style="width:25%;">Data Type</th>
161
+ <th style="width:15%;">Size</th>
162
+ <th style="width:15%;"></th>
163
+ </tr>
164
+ </thead>
165
+ <tbody id="Facto-Proj-ColBuilder-Body"></tbody>
166
+ </table>
167
+ <div style="display:flex; gap:0.5em; flex-wrap:wrap; margin-bottom:0.5em;">
168
+ <button class="facto-btn facto-btn-secondary facto-btn-small" onclick="{~P~}.views['Facto-Full-SchemaEditor'].addColumn()">+ Add Column</button>
169
+ <button class="facto-btn facto-btn-secondary facto-btn-small" id="Facto-Proj-AuditBtn" onclick="{~P~}.views['Facto-Full-SchemaEditor'].toggleAuditingColumns()">+ Add Auditing</button>
170
+ <button class="facto-btn facto-btn-secondary facto-btn-small" id="Facto-Proj-SoftDeleteBtn" onclick="{~P~}.views['Facto-Full-SchemaEditor'].toggleSoftDeleteColumns()">+ Add Soft Deletes</button>
171
+ </div>
172
+ <div class="facto-section-title" style="margin-top:1em; font-size:0.72em;">Generated MicroDDL</div>
173
+ <div class="facto-schema-preview" id="Facto-Proj-DDL-Preview"></div>
174
+ </div>
175
+
176
+ <!-- DDL Editor -->
177
+ <div id="Facto-Proj-DDL-Wrap" style="display:none;">
178
+ <textarea class="facto-ddl-editor" id="Facto-Proj-DDL-Textarea" placeholder="!MyTable&#10;@IDMyTable&#10;$Name 200&#10;#Count&#10;&CreatedDate"></textarea>
179
+ <div style="margin-top:0.5em;">
180
+ <button class="facto-btn facto-btn-secondary facto-btn-small" onclick="{~P~}.views['Facto-Full-SchemaEditor'].compileDDL()">Compile &amp; Preview</button>
181
+ </div>
182
+ <div class="facto-schema-preview" id="Facto-Proj-Schema-Preview" style="display:none;"></div>
183
+ </div>
184
+
185
+ <div style="margin-top:1em;">
186
+ <button class="facto-btn facto-btn-primary" onclick="{~P~}.views['Facto-Full-SchemaEditor'].saveSchema()">Save Schema</button>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ `
191
+ }
192
+ ],
193
+
194
+ Renderables:
195
+ [
196
+ {
197
+ RenderableHash: "Facto-Full-SchemaEditor-Content",
198
+ TemplateHash: "Facto-Full-SchemaEditor-Template",
199
+ DestinationAddress: "#Facto-Proj-Schema-Editor-Container",
200
+ RenderMethod: "replace"
201
+ }
202
+ ]
203
+ };
204
+
205
+ class FactoFullSchemaEditorView extends libPictView
206
+ {
207
+ constructor(pFable, pOptions, pServiceHash)
208
+ {
209
+ super(pFable, pOptions, pServiceHash);
210
+
211
+ this._EditingIDDataset = 0;
212
+ this._EditingName = '';
213
+ this._Columns = [];
214
+ this._EditorMode = 'visual';
215
+ }
216
+
217
+ editSchema(pIDDataset, pName)
218
+ {
219
+ this._EditingIDDataset = pIDDataset;
220
+ this._EditingName = pName || '';
221
+
222
+ // Render the sub-view so its DOM exists
223
+ this.render();
224
+
225
+ let tmpEditor = document.getElementById('Facto-Proj-Schema-Editor');
226
+ let tmpTitle = document.getElementById('Facto-Proj-Schema-Title');
227
+
228
+ if (tmpEditor) tmpEditor.classList.add('active');
229
+ if (tmpTitle) tmpTitle.textContent = 'Schema: ' + (pName || 'Untitled');
230
+
231
+ // Load existing schema
232
+ this.pict.providers.Facto.loadProjectionSchema(pIDDataset).then(
233
+ (pResponse) =>
234
+ {
235
+ let tmpDDL = (pResponse && pResponse.SchemaDefinition) ? pResponse.SchemaDefinition : '';
236
+ let tmpCleanName = (pName || 'Record').replace(/[^a-zA-Z0-9]/g, '');
237
+ let tmpIDColName = 'ID' + tmpCleanName;
238
+ let tmpGUIDColName = 'GUID' + tmpCleanName;
239
+
240
+ if (tmpDDL)
241
+ {
242
+ this._Columns = libProjectionConstants.microDDLToColumns(tmpDDL);
243
+ }
244
+ else
245
+ {
246
+ // Start with default ID and GUID columns for the entity
247
+ this._Columns =
248
+ [
249
+ { Name: tmpIDColName, DataType: 'AutoIdentity', Size: '' },
250
+ { Name: tmpGUIDColName, DataType: 'GUID', Size: '' }
251
+ ];
252
+ }
253
+
254
+ // Ensure the entity ID and GUID columns always exist
255
+ let tmpHasID = this._Columns.some((c) => { return c.Name === tmpIDColName; });
256
+ let tmpHasGUID = this._Columns.some((c) => { return c.Name === tmpGUIDColName; });
257
+
258
+ if (!tmpHasID)
259
+ {
260
+ this._Columns.unshift({ Name: tmpIDColName, DataType: 'AutoIdentity', Size: '' });
261
+ }
262
+ if (!tmpHasGUID)
263
+ {
264
+ // Insert GUID right after the ID column
265
+ let tmpIDIndex = this._Columns.findIndex((c) => { return c.Name === tmpIDColName; });
266
+ this._Columns.splice(tmpIDIndex + 1, 0, { Name: tmpGUIDColName, DataType: 'GUID', Size: '' });
267
+ }
268
+
269
+ this.refreshColumnBuilder();
270
+ this.updateDDLPreview();
271
+
272
+ // Also set the DDL textarea
273
+ let tmpTextarea = document.getElementById('Facto-Proj-DDL-Textarea');
274
+ if (tmpTextarea)
275
+ {
276
+ tmpTextarea.value = tmpDDL || libProjectionConstants.columnsToMicroDDL(this._Columns, this._EditingName);
277
+ }
278
+ });
279
+ }
280
+
281
+ switchEditorMode(pMode)
282
+ {
283
+ this._EditorMode = pMode;
284
+
285
+ let tmpVisualWrap = document.getElementById('Facto-Proj-Visual-Wrap');
286
+ let tmpDDLWrap = document.getElementById('Facto-Proj-DDL-Wrap');
287
+ let tmpVisualTab = document.getElementById('Facto-Proj-ModeTab-Visual');
288
+ let tmpDDLTab = document.getElementById('Facto-Proj-ModeTab-DDL');
289
+
290
+ if (pMode === 'visual')
291
+ {
292
+ if (tmpVisualWrap) tmpVisualWrap.style.display = '';
293
+ if (tmpDDLWrap) tmpDDLWrap.style.display = 'none';
294
+ if (tmpVisualTab) tmpVisualTab.classList.add('active');
295
+ if (tmpDDLTab) tmpDDLTab.classList.remove('active');
296
+
297
+ // Sync: parse DDL textarea back to columns
298
+ let tmpTextarea = document.getElementById('Facto-Proj-DDL-Textarea');
299
+ if (tmpTextarea && tmpTextarea.value.trim())
300
+ {
301
+ this._Columns = libProjectionConstants.microDDLToColumns(tmpTextarea.value);
302
+ this.refreshColumnBuilder();
303
+ this.updateDDLPreview();
304
+ }
305
+ }
306
+ else
307
+ {
308
+ if (tmpVisualWrap) tmpVisualWrap.style.display = 'none';
309
+ if (tmpDDLWrap) tmpDDLWrap.style.display = '';
310
+ if (tmpVisualTab) tmpVisualTab.classList.remove('active');
311
+ if (tmpDDLTab) tmpDDLTab.classList.add('active');
312
+
313
+ // Sync: marshal columns to DDL textarea
314
+ this._marshalColumnsFromUI();
315
+ let tmpTextarea = document.getElementById('Facto-Proj-DDL-Textarea');
316
+ if (tmpTextarea)
317
+ {
318
+ tmpTextarea.value = libProjectionConstants.columnsToMicroDDL(this._Columns, this._EditingName);
319
+ }
320
+ }
321
+ }
322
+
323
+ addColumn()
324
+ {
325
+ this._marshalColumnsFromUI();
326
+ this._Columns.push({ Name: '', DataType: 'String', Size: '200' });
327
+ this.refreshColumnBuilder();
328
+ this.updateDDLPreview();
329
+ }
330
+
331
+ removeColumn(pIndex)
332
+ {
333
+ this._marshalColumnsFromUI();
334
+ this._Columns.splice(pIndex, 1);
335
+ this.refreshColumnBuilder();
336
+ this.updateDDLPreview();
337
+ }
338
+
339
+ _hasColumn(pName)
340
+ {
341
+ for (let i = 0; i < this._Columns.length; i++)
342
+ {
343
+ if (this._Columns[i].Name && this._Columns[i].Name.toLowerCase() === pName.toLowerCase())
344
+ {
345
+ return true;
346
+ }
347
+ }
348
+ return false;
349
+ }
350
+
351
+ _removeColumnByName(pName)
352
+ {
353
+ let tmpLower = pName.toLowerCase();
354
+ this._Columns = this._Columns.filter(
355
+ function(c) { return !c.Name || c.Name.toLowerCase() !== tmpLower; });
356
+ }
357
+
358
+ _hasAuditingColumns()
359
+ {
360
+ return this._hasColumn('CreateDate') || this._hasColumn('UpdateDate');
361
+ }
362
+
363
+ _hasSoftDeleteColumns()
364
+ {
365
+ return this._hasColumn('Deleted') || this._hasColumn('DeleteDate');
366
+ }
367
+
368
+ _updateTrackingButtons()
369
+ {
370
+ let tmpAuditBtn = document.getElementById('Facto-Proj-AuditBtn');
371
+ let tmpDeleteBtn = document.getElementById('Facto-Proj-SoftDeleteBtn');
372
+
373
+ if (tmpAuditBtn)
374
+ {
375
+ if (this._hasAuditingColumns())
376
+ {
377
+ tmpAuditBtn.textContent = '\u2715 Remove Auditing';
378
+ }
379
+ else
380
+ {
381
+ tmpAuditBtn.textContent = '+ Add Auditing';
382
+ }
383
+ }
384
+
385
+ if (tmpDeleteBtn)
386
+ {
387
+ if (this._hasSoftDeleteColumns())
388
+ {
389
+ tmpDeleteBtn.textContent = '\u2715 Remove Soft Deletes';
390
+ }
391
+ else
392
+ {
393
+ tmpDeleteBtn.textContent = '+ Add Soft Deletes';
394
+ }
395
+ }
396
+ }
397
+
398
+ toggleAuditingColumns()
399
+ {
400
+ this._marshalColumnsFromUI();
401
+
402
+ if (this._hasAuditingColumns())
403
+ {
404
+ // Remove auditing columns
405
+ this._removeColumnByName('CreateDate');
406
+ this._removeColumnByName('CreatingIDUser');
407
+ this._removeColumnByName('UpdateDate');
408
+ this._removeColumnByName('UpdatingIDUser');
409
+ }
410
+ else
411
+ {
412
+ // Add auditing columns
413
+ if (!this._hasColumn('CreateDate'))
414
+ this._Columns.push({ Name: 'CreateDate', DataType: 'DateTime', Size: '' });
415
+ if (!this._hasColumn('CreatingIDUser'))
416
+ this._Columns.push({ Name: 'CreatingIDUser', DataType: 'Numeric', Size: '' });
417
+ if (!this._hasColumn('UpdateDate'))
418
+ this._Columns.push({ Name: 'UpdateDate', DataType: 'DateTime', Size: '' });
419
+ if (!this._hasColumn('UpdatingIDUser'))
420
+ this._Columns.push({ Name: 'UpdatingIDUser', DataType: 'Numeric', Size: '' });
421
+ }
422
+
423
+ this.refreshColumnBuilder();
424
+ this.updateDDLPreview();
425
+ }
426
+
427
+ toggleSoftDeleteColumns()
428
+ {
429
+ this._marshalColumnsFromUI();
430
+
431
+ if (this._hasSoftDeleteColumns())
432
+ {
433
+ // Remove soft delete columns
434
+ this._removeColumnByName('Deleted');
435
+ this._removeColumnByName('DeleteDate');
436
+ this._removeColumnByName('DeletingIDUser');
437
+ }
438
+ else
439
+ {
440
+ // Add soft delete columns
441
+ if (!this._hasColumn('Deleted'))
442
+ this._Columns.push({ Name: 'Deleted', DataType: 'Boolean', Size: '' });
443
+ if (!this._hasColumn('DeleteDate'))
444
+ this._Columns.push({ Name: 'DeleteDate', DataType: 'DateTime', Size: '' });
445
+ if (!this._hasColumn('DeletingIDUser'))
446
+ this._Columns.push({ Name: 'DeletingIDUser', DataType: 'Numeric', Size: '' });
447
+ }
448
+
449
+ this.refreshColumnBuilder();
450
+ this.updateDDLPreview();
451
+ }
452
+
453
+ refreshColumnBuilder()
454
+ {
455
+ let tmpBody = document.getElementById('Facto-Proj-ColBuilder-Body');
456
+ if (!tmpBody) return;
457
+
458
+ let tmpHtml = '';
459
+
460
+ for (let i = 0; i < this._Columns.length; i++)
461
+ {
462
+ let tmpCol = this._Columns[i];
463
+ let tmpSizeDisabled = !MICRODDL_TYPE_MAP[DATATYPE_TO_SYMBOL[tmpCol.DataType] || '$'].HasSize;
464
+
465
+ tmpHtml += '<tr>';
466
+ tmpHtml += '<td><input type="text" value="' + (tmpCol.Name || '').replace(/"/g, '&quot;') + '" data-col-index="' + i + '" data-col-field="Name"></td>';
467
+ tmpHtml += '<td><select data-col-index="' + i + '" data-col-field="DataType" onchange="pict.views[\'Facto-Full-SchemaEditor\']._onColumnTypeChange(' + i + ', this.value)">';
468
+
469
+ for (let tmpSym in MICRODDL_TYPE_MAP)
470
+ {
471
+ let tmpType = MICRODDL_TYPE_MAP[tmpSym];
472
+ let tmpSelected = (tmpType.DataType === tmpCol.DataType) ? ' selected' : '';
473
+ tmpHtml += '<option value="' + tmpType.DataType + '"' + tmpSelected + '>' + tmpType.Label + '</option>';
474
+ }
475
+
476
+ tmpHtml += '</select></td>';
477
+ tmpHtml += '<td><input type="text" class="facto-col-size" value="' + (tmpCol.Size || '').replace(/"/g, '&quot;') + '" data-col-index="' + i + '" data-col-field="Size"' + (tmpSizeDisabled ? ' disabled' : '') + '></td>';
478
+ tmpHtml += '<td><button class="facto-col-remove-btn" onclick="pict.views[\'Facto-Full-SchemaEditor\'].removeColumn(' + i + ')" title="Remove column">&times;</button></td>';
479
+ tmpHtml += '</tr>';
480
+ }
481
+
482
+ tmpBody.innerHTML = tmpHtml;
483
+ this._updateTrackingButtons();
484
+ }
485
+
486
+ _onColumnTypeChange(pIndex, pDataType)
487
+ {
488
+ this._marshalColumnsFromUI();
489
+ let tmpSymbol = DATATYPE_TO_SYMBOL[pDataType] || '$';
490
+ let tmpHasSize = MICRODDL_TYPE_MAP[tmpSymbol].HasSize;
491
+
492
+ if (!tmpHasSize)
493
+ {
494
+ this._Columns[pIndex].Size = '';
495
+ }
496
+ else if (!this._Columns[pIndex].Size)
497
+ {
498
+ this._Columns[pIndex].Size = pDataType === 'String' ? '200' : '8,2';
499
+ }
500
+
501
+ this._Columns[pIndex].DataType = pDataType;
502
+ this.refreshColumnBuilder();
503
+ this.updateDDLPreview();
504
+ }
505
+
506
+ /**
507
+ * Read column values from the DOM inputs back into the _Columns array.
508
+ */
509
+ _marshalColumnsFromUI()
510
+ {
511
+ let tmpInputs = document.querySelectorAll('#Facto-Proj-ColBuilder-Body input[data-col-field="Name"], #Facto-Proj-ColBuilder-Body input[data-col-field="Size"], #Facto-Proj-ColBuilder-Body select[data-col-field="DataType"]');
512
+
513
+ for (let i = 0; i < tmpInputs.length; i++)
514
+ {
515
+ let tmpInput = tmpInputs[i];
516
+ let tmpIndex = parseInt(tmpInput.getAttribute('data-col-index'), 10);
517
+ let tmpField = tmpInput.getAttribute('data-col-field');
518
+
519
+ if (tmpIndex >= 0 && tmpIndex < this._Columns.length && tmpField)
520
+ {
521
+ this._Columns[tmpIndex][tmpField] = tmpInput.value;
522
+ }
523
+ }
524
+ }
525
+
526
+ updateDDLPreview()
527
+ {
528
+ let tmpPreview = document.getElementById('Facto-Proj-DDL-Preview');
529
+ if (tmpPreview)
530
+ {
531
+ tmpPreview.textContent = libProjectionConstants.columnsToMicroDDL(this._Columns, this._EditingName);
532
+ }
533
+ }
534
+
535
+ compileDDL()
536
+ {
537
+ let tmpTextarea = document.getElementById('Facto-Proj-DDL-Textarea');
538
+ if (!tmpTextarea) return;
539
+
540
+ let tmpDDL = tmpTextarea.value.trim();
541
+ if (!tmpDDL)
542
+ {
543
+ this.pict.views['Pict-Section-Modal'].toast('Enter MicroDDL text first.', {type: 'warning'});
544
+ return;
545
+ }
546
+
547
+ this.pict.providers.Facto.compileMicroDDL(tmpDDL).then(
548
+ (pResponse) =>
549
+ {
550
+ let tmpPreview = document.getElementById('Facto-Proj-Schema-Preview');
551
+ if (tmpPreview)
552
+ {
553
+ tmpPreview.style.display = 'block';
554
+ if (pResponse && pResponse.Schema)
555
+ {
556
+ tmpPreview.textContent = JSON.stringify(pResponse.Schema, null, 2);
557
+ }
558
+ else
559
+ {
560
+ tmpPreview.textContent = 'Error: ' + (pResponse && pResponse.Error ? pResponse.Error : 'Unknown error');
561
+ }
562
+ }
563
+
564
+ // Update columns from compiled schema
565
+ if (pResponse && pResponse.Schema && pResponse.Schema.Tables)
566
+ {
567
+ let tmpTableKeys = Object.keys(pResponse.Schema.Tables);
568
+ if (tmpTableKeys.length > 0)
569
+ {
570
+ let tmpTable = pResponse.Schema.Tables[tmpTableKeys[0]];
571
+ this._Columns = [];
572
+ for (let j = 0; j < tmpTable.Columns.length; j++)
573
+ {
574
+ let tmpCol = tmpTable.Columns[j];
575
+ this._Columns.push(
576
+ {
577
+ Name: tmpCol.Column,
578
+ DataType: tmpCol.DataType || 'String',
579
+ Size: tmpCol.Size || ''
580
+ });
581
+ }
582
+ }
583
+ }
584
+ });
585
+ }
586
+
587
+ saveSchema()
588
+ {
589
+ if (!this._EditingIDDataset)
590
+ {
591
+ this.pict.views['Pict-Section-Modal'].toast('No projection selected.', {type: 'warning'});
592
+ return;
593
+ }
594
+
595
+ // Marshal current state to DDL text
596
+ if (this._EditorMode === 'visual')
597
+ {
598
+ this._marshalColumnsFromUI();
599
+ }
600
+
601
+ let tmpDDL;
602
+ if (this._EditorMode === 'ddl')
603
+ {
604
+ let tmpTextarea = document.getElementById('Facto-Proj-DDL-Textarea');
605
+ tmpDDL = tmpTextarea ? tmpTextarea.value : '';
606
+ }
607
+ else
608
+ {
609
+ tmpDDL = libProjectionConstants.columnsToMicroDDL(this._Columns, this._EditingName);
610
+ }
611
+
612
+ this.pict.providers.Facto.saveProjectionSchema(this._EditingIDDataset, tmpDDL).then(
613
+ (pResponse) =>
614
+ {
615
+ if (pResponse && pResponse.Error)
616
+ {
617
+ this.pict.views['Pict-Section-Modal'].toast('Error saving schema: ' + pResponse.Error, {type: 'error'});
618
+ return;
619
+ }
620
+
621
+ this.pict.views['Pict-Section-Modal'].toast('Schema saved (v' + (pResponse.SchemaVersion || 0) + ')', {type: 'success'});
622
+
623
+ // Notify the detail view about the schema update
624
+ let tmpDetailView = this.pict.views['Facto-Full-ProjectionDetail'];
625
+ if (tmpDetailView && typeof tmpDetailView.onSchemaUpdated === 'function')
626
+ {
627
+ tmpDetailView.onSchemaUpdated(pResponse.SchemaVersion);
628
+ }
629
+ });
630
+ }
631
+
632
+ }
633
+
634
+ module.exports = FactoFullSchemaEditorView;
635
+
636
+ module.exports.default_configuration = _ViewConfiguration;