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,822 @@
1
+ const libPictView = require('pict-view');
2
+ const libPictSectionContent = require('pict-section-content');
3
+
4
+ const _ViewConfiguration =
5
+ {
6
+ ViewIdentifier: "Facto-Full-SourceDetail",
7
+
8
+ DefaultRenderable: "Facto-Full-SourceDetail-Content",
9
+ DefaultDestinationAddress: "#Facto-Full-Content-Container",
10
+
11
+ AutoRender: false,
12
+
13
+ CSS: /*css*/`
14
+ .facto-source-detail-back {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ gap: 0.35em;
18
+ color: var(--facto-text-secondary);
19
+ cursor: pointer;
20
+ font-size: 0.85em;
21
+ margin-bottom: 0.75em;
22
+ transition: color 0.15s;
23
+ }
24
+ .facto-source-detail-back:hover {
25
+ color: var(--facto-brand);
26
+ }
27
+
28
+ /* Research context section */
29
+ .facto-research-context {
30
+ background: var(--facto-bg-elevated, #1a1e2a);
31
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
32
+ border-radius: 8px;
33
+ padding: 1em;
34
+ margin-bottom: 1.5em;
35
+ }
36
+ .facto-research-context h3 {
37
+ margin: 0 0 0.5em;
38
+ font-size: 0.75em;
39
+ text-transform: uppercase;
40
+ letter-spacing: 0.05em;
41
+ color: var(--facto-text-tertiary, #888);
42
+ }
43
+ .facto-research-context-detail {
44
+ font-size: 0.85em;
45
+ color: var(--facto-text-secondary, #aaa);
46
+ line-height: 1.6;
47
+ }
48
+ .facto-research-context-detail strong {
49
+ color: var(--facto-text-heading, #eee);
50
+ }
51
+ .facto-research-context-note {
52
+ margin-top: 0.5em;
53
+ padding: 0.5em 0.75em;
54
+ background: var(--facto-brand-a08);
55
+ border-left: 3px solid var(--facto-brand, #4a90d9);
56
+ border-radius: 0 4px 4px 0;
57
+ font-size: 0.85em;
58
+ color: var(--facto-text-secondary, #aaa);
59
+ }
60
+
61
+ /* Dataset definitions table */
62
+ .facto-dataset-defs {
63
+ margin-bottom: 1.5em;
64
+ }
65
+ .facto-dataset-defs h2 {
66
+ font-size: 1em;
67
+ margin: 0 0 0.75em;
68
+ color: var(--facto-text-secondary, #aaa);
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.05em;
71
+ }
72
+
73
+ /* Documentation section */
74
+ .facto-doc-section {
75
+ margin-top: 1.5em;
76
+ }
77
+ .facto-doc-section-header {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 0.75em;
81
+ margin-bottom: 0.75em;
82
+ }
83
+ .facto-doc-section-header h2 {
84
+ font-size: 1em;
85
+ margin: 0;
86
+ color: var(--facto-text-secondary, #aaa);
87
+ text-transform: uppercase;
88
+ letter-spacing: 0.05em;
89
+ }
90
+ .facto-edit-toggle {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ gap: 0.35em;
94
+ padding: 0.25em 0.6em;
95
+ font-size: 0.75em;
96
+ border-radius: 4px;
97
+ cursor: pointer;
98
+ transition: background 0.15s, color 0.15s;
99
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
100
+ background: transparent;
101
+ color: var(--facto-text-tertiary, #888);
102
+ }
103
+ .facto-edit-toggle:hover {
104
+ border-color: var(--facto-brand, #4a90d9);
105
+ color: var(--facto-brand, #4a90d9);
106
+ }
107
+ .facto-edit-toggle.active {
108
+ background: var(--facto-brand-a15);
109
+ border-color: var(--facto-brand, #4a90d9);
110
+ color: var(--facto-brand, #4a90d9);
111
+ }
112
+
113
+ /* Read-only rendered content */
114
+ .facto-doc-content-wrap {
115
+ background: var(--facto-bg-elevated, #1a1e2a);
116
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
117
+ border-radius: 8px;
118
+ padding: 1.5em 2em;
119
+ min-height: 100px;
120
+ }
121
+ .facto-doc-content-wrap h1 {
122
+ font-size: 1.75em;
123
+ color: var(--facto-text-heading, #eee);
124
+ border-bottom: 1px solid var(--facto-border-subtle, #2a2e3a);
125
+ padding-bottom: 0.3em;
126
+ margin-top: 0;
127
+ }
128
+ .facto-doc-content-wrap h2 {
129
+ font-size: 1.4em;
130
+ color: var(--facto-text-heading, #eee);
131
+ border-bottom: 1px solid var(--facto-border-subtle, #2a2e3a);
132
+ padding-bottom: 0.25em;
133
+ margin-top: 1.5em;
134
+ }
135
+ .facto-doc-content-wrap h3 {
136
+ font-size: 1.15em;
137
+ color: var(--facto-text-heading, #eee);
138
+ margin-top: 1.25em;
139
+ }
140
+ .facto-doc-content-wrap h4,
141
+ .facto-doc-content-wrap h5,
142
+ .facto-doc-content-wrap h6 {
143
+ color: var(--facto-text-secondary, #ccc);
144
+ margin-top: 1em;
145
+ }
146
+ .facto-doc-content-wrap p {
147
+ line-height: 1.7;
148
+ color: var(--facto-text-secondary, #bbb);
149
+ margin: 0.75em 0;
150
+ }
151
+ .facto-doc-content-wrap a {
152
+ color: var(--facto-brand, #4a90d9);
153
+ }
154
+ .facto-doc-content-wrap code {
155
+ background: var(--facto-brand-a10);
156
+ color: var(--facto-brand, #4a90d9);
157
+ padding: 0.15em 0.35em;
158
+ border-radius: 3px;
159
+ font-size: 0.9em;
160
+ }
161
+ .facto-doc-content-wrap pre {
162
+ background: var(--facto-bg-input, #0d1117);
163
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
164
+ border-radius: 6px;
165
+ padding: 1em;
166
+ overflow-x: auto;
167
+ color: var(--facto-text-heading, #eee);
168
+ }
169
+ .facto-doc-content-wrap pre code {
170
+ background: transparent;
171
+ padding: 0;
172
+ color: inherit;
173
+ }
174
+ .facto-doc-content-wrap blockquote {
175
+ border-left: 3px solid var(--facto-brand, #4a90d9);
176
+ padding: 0.5em 1em;
177
+ margin: 1em 0;
178
+ color: var(--facto-text-tertiary, #888);
179
+ background: var(--facto-brand-a05);
180
+ border-radius: 0 4px 4px 0;
181
+ }
182
+ .facto-doc-content-wrap table {
183
+ width: 100%;
184
+ border-collapse: collapse;
185
+ margin: 1em 0;
186
+ }
187
+ .facto-doc-content-wrap table th,
188
+ .facto-doc-content-wrap table td {
189
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
190
+ padding: 0.5em 0.75em;
191
+ }
192
+ .facto-doc-content-wrap table th {
193
+ background: rgba(255, 255, 255, 0.03);
194
+ color: var(--facto-text-heading, #eee);
195
+ }
196
+ .facto-doc-content-wrap img {
197
+ max-width: 100%;
198
+ height: auto;
199
+ border-radius: 4px;
200
+ margin: 0.5em 0;
201
+ }
202
+ .facto-doc-content-wrap hr {
203
+ border: none;
204
+ border-top: 1px solid var(--facto-border-subtle, #2a2e3a);
205
+ margin: 1.5em 0;
206
+ }
207
+ .facto-doc-content-wrap ul,
208
+ .facto-doc-content-wrap ol {
209
+ color: var(--facto-text-secondary, #bbb);
210
+ padding-left: 1.5em;
211
+ line-height: 1.7;
212
+ }
213
+ .facto-doc-list {
214
+ display: flex;
215
+ flex-wrap: wrap;
216
+ gap: 0.5em;
217
+ margin-bottom: 1em;
218
+ }
219
+ .facto-doc-item {
220
+ padding: 0.4em 0.75em;
221
+ background: var(--facto-bg-elevated, #1a1e2a);
222
+ border: 1px solid var(--facto-border-subtle, #2a2e3a);
223
+ border-radius: 6px;
224
+ font-size: 0.85em;
225
+ cursor: pointer;
226
+ color: var(--facto-text-secondary, #aaa);
227
+ transition: border-color 0.15s, color 0.15s;
228
+ }
229
+ .facto-doc-item:hover {
230
+ border-color: var(--facto-brand, #4a90d9);
231
+ color: var(--facto-text-heading, #eee);
232
+ }
233
+ .facto-doc-item.active {
234
+ border-color: var(--facto-brand, #4a90d9);
235
+ color: var(--facto-brand, #4a90d9);
236
+ background: var(--facto-brand-a10);
237
+ }
238
+ .facto-doc-new-input {
239
+ display: flex;
240
+ gap: 0.5em;
241
+ align-items: center;
242
+ }
243
+ .facto-doc-new-input input {
244
+ width: 240px;
245
+ margin-bottom: 0;
246
+ }
247
+ `,
248
+
249
+ Templates:
250
+ [
251
+ {
252
+ Hash: "Facto-Full-SourceDetail-Template",
253
+ Template: /*html*/`
254
+ <div class="facto-content">
255
+ <div class="facto-source-detail-back" onclick="{~P~}.views['Facto-Full-SourceDetail'].goBack()">
256
+ &#8592; Back to Source Research
257
+ </div>
258
+
259
+ <div class="facto-content-header">
260
+ <h1 id="Facto-SourceDetail-Title">Source</h1>
261
+ </div>
262
+
263
+ <div id="Facto-SourceDetail-Loading" style="color:var(--facto-text-secondary);">Loading source...</div>
264
+ <div id="Facto-SourceDetail-Error" class="facto-status facto-status-error" style="display:none;"></div>
265
+
266
+ <div id="Facto-SourceDetail-Container" style="display:none;">
267
+ <div class="facto-record-meta" id="Facto-SourceDetail-Meta"></div>
268
+
269
+ <div id="Facto-SourceDetail-ResearchContext"></div>
270
+
271
+ <div class="facto-dataset-defs" id="Facto-SourceDetail-DatasetDefs"></div>
272
+
273
+ <div class="facto-doc-section">
274
+ <div class="facto-doc-section-header">
275
+ <h2>Documentation</h2>
276
+ <button class="facto-edit-toggle" id="Facto-SourceDetail-EditToggle" onclick="{~P~}.views['Facto-Full-SourceDetail'].toggleEditMode()">
277
+ &#9998; Edit
278
+ </button>
279
+ </div>
280
+ <div id="Facto-SourceDetail-DocListWrap"></div>
281
+ <div id="Facto-SourceDetail-ContentWrap" style="display:none;">
282
+ <div class="facto-doc-content-wrap" id="Facto-SourceDetail-ContentDisplay"></div>
283
+ </div>
284
+ <div id="Facto-SourceDetail-EditorWrap" style="display:none;">
285
+ <div id="Facto-SourceDetail-EditorContainer"></div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ `
291
+ }
292
+ ],
293
+
294
+ Renderables:
295
+ [
296
+ {
297
+ RenderableHash: "Facto-Full-SourceDetail-Content",
298
+ TemplateHash: "Facto-Full-SourceDetail-Template",
299
+ DestinationAddress: "#Facto-Full-Content-Container",
300
+ RenderMethod: "replace"
301
+ }
302
+ ]
303
+ };
304
+
305
+ class FactoFullSourceDetailView extends libPictView
306
+ {
307
+ constructor(pFable, pOptions, pServiceHash)
308
+ {
309
+ super(pFable, pOptions, pServiceHash);
310
+
311
+ this._CurrentIDSource = null;
312
+ this._CurrentIDDoc = null;
313
+ this._CurrentDocName = '';
314
+ this._CurrentDocContent = '';
315
+ this._Documentation = [];
316
+ this._EditMode = false;
317
+ }
318
+
319
+ onBeforeInitialize()
320
+ {
321
+ super.onBeforeInitialize();
322
+
323
+ // Register the Content provider for markdown rendering (read-only display)
324
+ if (!this.pict.providers.PictContent)
325
+ {
326
+ this.pict.addProvider('PictContent', { ProviderIdentifier: 'PictContent' }, libPictSectionContent.PictContentProvider);
327
+ }
328
+
329
+ return true;
330
+ }
331
+
332
+ /**
333
+ * Navigate to a specific source, optionally opening a document.
334
+ */
335
+ loadSource(pIDSource, pIDDoc)
336
+ {
337
+ this._CurrentIDSource = pIDSource;
338
+ this._CurrentIDDoc = pIDDoc || null;
339
+ this.render();
340
+ }
341
+
342
+ onAfterRender()
343
+ {
344
+ super.onAfterRender();
345
+
346
+ if (!this._CurrentIDSource)
347
+ {
348
+ let tmpLoading = document.getElementById('Facto-SourceDetail-Loading');
349
+ if (tmpLoading)
350
+ {
351
+ tmpLoading.textContent = 'No source selected.';
352
+ }
353
+ return;
354
+ }
355
+
356
+ this._fetchAndDisplaySource();
357
+ }
358
+
359
+ _fetchAndDisplaySource()
360
+ {
361
+ let tmpProvider = this.pict.providers.Facto;
362
+ let tmpLoadingEl = document.getElementById('Facto-SourceDetail-Loading');
363
+ let tmpErrorEl = document.getElementById('Facto-SourceDetail-Error');
364
+
365
+ let tmpSummary = null;
366
+ let tmpCatalogContext = null;
367
+ let tmpDocumentation = null;
368
+
369
+ let tmpSummaryPromise = tmpProvider.loadSourceSummary(this._CurrentIDSource);
370
+ let tmpCatalogPromise = tmpProvider.loadSourceCatalogContext(this._CurrentIDSource);
371
+ let tmpDocsPromise = tmpProvider.loadSourceDocumentation(this._CurrentIDSource);
372
+
373
+ tmpSummaryPromise.then(
374
+ (pResponse) =>
375
+ {
376
+ if (pResponse && pResponse.Error)
377
+ {
378
+ if (tmpLoadingEl) tmpLoadingEl.style.display = 'none';
379
+ if (tmpErrorEl)
380
+ {
381
+ tmpErrorEl.textContent = 'Error loading source: ' + pResponse.Error;
382
+ tmpErrorEl.style.display = 'block';
383
+ }
384
+ return;
385
+ }
386
+ tmpSummary = pResponse;
387
+ });
388
+
389
+ tmpCatalogPromise.then(
390
+ (pResponse) =>
391
+ {
392
+ tmpCatalogContext = pResponse || { CatalogEntries: [], DatasetDefinitions: [] };
393
+ });
394
+
395
+ tmpDocsPromise.then(
396
+ (pResponse) =>
397
+ {
398
+ tmpDocumentation = (pResponse && pResponse.Documentation) ? pResponse.Documentation : [];
399
+ });
400
+
401
+ Promise.all([tmpSummaryPromise, tmpCatalogPromise, tmpDocsPromise]).then(
402
+ () =>
403
+ {
404
+ if (!tmpSummary || !tmpSummary.Source)
405
+ {
406
+ if (tmpLoadingEl) tmpLoadingEl.style.display = 'none';
407
+ if (tmpErrorEl)
408
+ {
409
+ tmpErrorEl.textContent = 'Source not found';
410
+ tmpErrorEl.style.display = 'block';
411
+ }
412
+ return;
413
+ }
414
+
415
+ this._renderSourceDetail(tmpSummary, tmpCatalogContext, tmpDocumentation);
416
+ }).catch(
417
+ (pError) =>
418
+ {
419
+ if (tmpLoadingEl) tmpLoadingEl.style.display = 'none';
420
+ if (tmpErrorEl)
421
+ {
422
+ tmpErrorEl.textContent = 'Error loading source: ' + (pError.message || pError);
423
+ tmpErrorEl.style.display = 'block';
424
+ }
425
+ });
426
+ }
427
+
428
+ _renderSourceDetail(pSummary, pCatalogContext, pDocumentation)
429
+ {
430
+ let tmpLoadingEl = document.getElementById('Facto-SourceDetail-Loading');
431
+ let tmpContainer = document.getElementById('Facto-SourceDetail-Container');
432
+ let tmpTitleEl = document.getElementById('Facto-SourceDetail-Title');
433
+
434
+ if (tmpLoadingEl) tmpLoadingEl.style.display = 'none';
435
+ if (tmpContainer) tmpContainer.style.display = 'block';
436
+
437
+ let tmpSource = pSummary.Source;
438
+
439
+ // Title
440
+ if (tmpTitleEl)
441
+ {
442
+ tmpTitleEl.textContent = (tmpSource.Hash || tmpSource.Name || ('Source #' + tmpSource.IDSource));
443
+ }
444
+
445
+ // Metadata cards
446
+ let tmpMetaEl = document.getElementById('Facto-SourceDetail-Meta');
447
+ if (tmpMetaEl)
448
+ {
449
+ tmpMetaEl.innerHTML = this._buildMetaCards(tmpSource, pSummary);
450
+ }
451
+
452
+ // Research context
453
+ this._renderResearchContext(pCatalogContext);
454
+
455
+ // Dataset definitions
456
+ this._renderDatasetDefs(pCatalogContext);
457
+
458
+ // Documentation list
459
+ this._Documentation = pDocumentation;
460
+ this._renderDocList();
461
+
462
+ // Auto-open document if specified
463
+ if (this._CurrentIDDoc)
464
+ {
465
+ this.selectDocument(this._CurrentIDDoc);
466
+ }
467
+ }
468
+
469
+ _buildMetaCards(pSource, pSummary)
470
+ {
471
+ let tmpGUID = (pSource.GUIDSource || '').substring(0, 8) + '\u2026' + (pSource.GUIDSource || '').substring((pSource.GUIDSource || '').length - 4);
472
+ let tmpActive = pSource.Active
473
+ ? '<span class="facto-badge facto-badge-success">Active</span>'
474
+ : '<span class="facto-badge facto-badge-muted">Inactive</span>';
475
+
476
+ let tmpHtml = '';
477
+
478
+ // Source Identity card
479
+ tmpHtml += '<div class="facto-record-meta-card">';
480
+ tmpHtml += '<h3>Source Identity</h3>';
481
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">ID</span><span class="facto-record-meta-value">' + pSource.IDSource + '</span></div>';
482
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">GUID</span><span class="facto-record-meta-value">' + tmpGUID + '</span></div>';
483
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Hash</span><span class="facto-record-meta-value facto-hash-value">' + (pSource.Hash || '\u2014') + '</span></div>';
484
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Status</span><span class="facto-record-meta-value">' + tmpActive + '</span></div>';
485
+ tmpHtml += '</div>';
486
+
487
+ // Connection card
488
+ tmpHtml += '<div class="facto-record-meta-card">';
489
+ tmpHtml += '<h3>Connection</h3>';
490
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Name</span><span class="facto-record-meta-value">' + (pSource.Name || '\u2014') + '</span></div>';
491
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Type</span><span class="facto-record-meta-value">' + (pSource.Type || '\u2014') + '</span></div>';
492
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">URL</span><span class="facto-record-meta-value" title="' + (pSource.URL || '') + '">' + (pSource.URL || '\u2014') + '</span></div>';
493
+ tmpHtml += '</div>';
494
+
495
+ // Statistics card
496
+ tmpHtml += '<div class="facto-record-meta-card">';
497
+ tmpHtml += '<h3>Statistics</h3>';
498
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Records</span><span class="facto-record-meta-value">' + (pSummary.RecordCount || 0).toLocaleString() + '</span></div>';
499
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Datasets</span><span class="facto-record-meta-value">' + (pSummary.DatasetCount || 0) + '</span></div>';
500
+ tmpHtml += '<div class="facto-record-meta-row"><span class="facto-record-meta-label">Documents</span><span class="facto-record-meta-value">' + (pSummary.DocumentationCount || 0) + '</span></div>';
501
+ tmpHtml += '</div>';
502
+
503
+ return tmpHtml;
504
+ }
505
+
506
+ _renderResearchContext(pCatalogContext)
507
+ {
508
+ let tmpEl = document.getElementById('Facto-SourceDetail-ResearchContext');
509
+ if (!tmpEl) return;
510
+
511
+ if (!pCatalogContext || !pCatalogContext.CatalogEntries || pCatalogContext.CatalogEntries.length === 0)
512
+ {
513
+ tmpEl.innerHTML = '';
514
+ return;
515
+ }
516
+
517
+ let tmpHtml = '';
518
+ for (let i = 0; i < pCatalogContext.CatalogEntries.length; i++)
519
+ {
520
+ let tmpEntry = pCatalogContext.CatalogEntries[i];
521
+ tmpHtml += '<div class="facto-research-context">';
522
+ tmpHtml += '<h3>Research Context' + (pCatalogContext.CatalogEntries.length > 1 ? ' (' + (i + 1) + ')' : '') + '</h3>';
523
+ tmpHtml += '<div class="facto-research-context-detail">';
524
+ tmpHtml += '<strong>Agency:</strong> ' + (tmpEntry.Agency || '\u2014');
525
+ tmpHtml += ' &nbsp;\u00B7&nbsp; <strong>Category:</strong> ' + (tmpEntry.Category || '\u2014');
526
+ tmpHtml += ' &nbsp;\u00B7&nbsp; <strong>Region:</strong> ' + (tmpEntry.Region || '\u2014');
527
+ tmpHtml += ' &nbsp;\u00B7&nbsp; <strong>Update Frequency:</strong> ' + (tmpEntry.UpdateFrequency || '\u2014');
528
+ tmpHtml += ' &nbsp;\u00B7&nbsp; <strong>Verified:</strong> ' + (tmpEntry.Verified ? 'Yes' : 'No');
529
+ tmpHtml += '</div>';
530
+
531
+ if (tmpEntry.Description)
532
+ {
533
+ tmpHtml += '<div class="facto-research-context-detail" style="margin-top:0.5em;">' + tmpEntry.Description + '</div>';
534
+ }
535
+
536
+ if (tmpEntry.Notes)
537
+ {
538
+ tmpHtml += '<div class="facto-research-context-note">' + tmpEntry.Notes + '</div>';
539
+ }
540
+
541
+ tmpHtml += '</div>';
542
+ }
543
+
544
+ tmpEl.innerHTML = tmpHtml;
545
+ }
546
+
547
+ _renderDatasetDefs(pCatalogContext)
548
+ {
549
+ let tmpEl = document.getElementById('Facto-SourceDetail-DatasetDefs');
550
+ if (!tmpEl) return;
551
+
552
+ if (!pCatalogContext || !pCatalogContext.DatasetDefinitions || pCatalogContext.DatasetDefinitions.length === 0)
553
+ {
554
+ tmpEl.innerHTML = '';
555
+ return;
556
+ }
557
+
558
+ let tmpDefs = pCatalogContext.DatasetDefinitions;
559
+ let tmpHtml = '<h2>Dataset Definitions</h2>';
560
+ tmpHtml += '<table><thead><tr><th>Name</th><th>Format</th><th>Endpoint</th><th>Policy</th><th>Status</th></tr></thead><tbody>';
561
+
562
+ for (let i = 0; i < tmpDefs.length; i++)
563
+ {
564
+ let tmpDef = tmpDefs[i];
565
+ let tmpStatus = tmpDef.Provisioned
566
+ ? '<span class="facto-badge facto-badge-success">Provisioned</span>'
567
+ : '<span class="facto-badge facto-badge-muted">Not provisioned</span>';
568
+
569
+ tmpHtml += '<tr>';
570
+ tmpHtml += '<td>' + (tmpDef.Name || '') + '</td>';
571
+ tmpHtml += '<td><span class="facto-badge facto-badge-primary">' + (tmpDef.Format || '') + '</span></td>';
572
+ tmpHtml += '<td style="max-width:250px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">' + (tmpDef.EndpointURL || '') + '</td>';
573
+ tmpHtml += '<td>' + (tmpDef.VersionPolicy || 'Append') + '</td>';
574
+ tmpHtml += '<td>' + tmpStatus + '</td>';
575
+ tmpHtml += '</tr>';
576
+ }
577
+
578
+ tmpHtml += '</tbody></table>';
579
+ tmpEl.innerHTML = tmpHtml;
580
+ }
581
+
582
+ _renderDocList()
583
+ {
584
+ let tmpEl = document.getElementById('Facto-SourceDetail-DocListWrap');
585
+ if (!tmpEl) return;
586
+
587
+ let tmpHtml = '<div class="facto-doc-list">';
588
+
589
+ // New Document button
590
+ tmpHtml += '<div class="facto-doc-new-input" id="Facto-SourceDetail-NewDocWrap">';
591
+ tmpHtml += '<input type="text" id="Facto-SourceDetail-NewDocName" placeholder="Document name...">';
592
+ tmpHtml += '<button class="facto-btn facto-btn-success facto-btn-small" onclick="pict.views[\'Facto-Full-SourceDetail\'].createDocument()">New Document</button>';
593
+ tmpHtml += '</div>';
594
+
595
+ tmpHtml += '</div>';
596
+
597
+ // Document tabs
598
+ if (this._Documentation && this._Documentation.length > 0)
599
+ {
600
+ tmpHtml += '<div class="facto-doc-list">';
601
+ for (let i = 0; i < this._Documentation.length; i++)
602
+ {
603
+ let tmpDoc = this._Documentation[i];
604
+ let tmpActiveClass = (this._CurrentIDDoc && parseInt(this._CurrentIDDoc, 10) === tmpDoc.IDSourceDocumentation) ? ' active' : '';
605
+ tmpHtml += '<div class="facto-doc-item' + tmpActiveClass + '" onclick="pict.views[\'Facto-Full-SourceDetail\'].selectDocument(' + tmpDoc.IDSourceDocumentation + ')">';
606
+ tmpHtml += (tmpDoc.Name || 'Untitled');
607
+ tmpHtml += '</div>';
608
+ }
609
+ tmpHtml += '</div>';
610
+ }
611
+
612
+ tmpEl.innerHTML = tmpHtml;
613
+ }
614
+
615
+ selectDocument(pIDDoc)
616
+ {
617
+ let tmpProvider = this.pict.providers.Facto;
618
+ this._CurrentIDDoc = pIDDoc;
619
+
620
+ // Update URL hash without full re-render
621
+ if (window.history && window.history.replaceState)
622
+ {
623
+ window.history.replaceState(null, '', '#/Source/' + this._CurrentIDSource + '/Doc/' + pIDDoc);
624
+ }
625
+
626
+ // Re-render doc list to highlight active
627
+ this._renderDocList();
628
+
629
+ tmpProvider.loadSourceDocument(this._CurrentIDSource, pIDDoc).then(
630
+ (pResponse) =>
631
+ {
632
+ if (pResponse && pResponse.Error)
633
+ {
634
+ this.pict.log.error('Error loading document: ' + pResponse.Error);
635
+ return;
636
+ }
637
+
638
+ let tmpDoc = pResponse.Documentation;
639
+ this._CurrentDocName = tmpDoc.Name || 'Untitled';
640
+ this._CurrentDocContent = tmpDoc.Content || '';
641
+
642
+ this._showDocument();
643
+ });
644
+ }
645
+
646
+ _showDocument()
647
+ {
648
+ let tmpContentWrap = document.getElementById('Facto-SourceDetail-ContentWrap');
649
+ let tmpEditorWrap = document.getElementById('Facto-SourceDetail-EditorWrap');
650
+
651
+ if (this._EditMode)
652
+ {
653
+ // Edit mode: show editor sub-view
654
+ if (tmpContentWrap) tmpContentWrap.style.display = 'none';
655
+ if (tmpEditorWrap) tmpEditorWrap.style.display = 'block';
656
+
657
+ let tmpEditorView = this.pict.views['Facto-Full-SourceEditor'];
658
+ if (tmpEditorView)
659
+ {
660
+ tmpEditorView.openEditor(this._CurrentIDSource, this._CurrentIDDoc, this._CurrentDocName, this._CurrentDocContent);
661
+ }
662
+ }
663
+ else
664
+ {
665
+ // Read mode: show rendered content
666
+ if (tmpEditorWrap) tmpEditorWrap.style.display = 'none';
667
+ if (tmpContentWrap) tmpContentWrap.style.display = 'block';
668
+
669
+ this._renderReadOnlyContent();
670
+ }
671
+ }
672
+
673
+ _renderReadOnlyContent()
674
+ {
675
+ let tmpDisplayEl = document.getElementById('Facto-SourceDetail-ContentDisplay');
676
+ if (!tmpDisplayEl) return;
677
+
678
+ if (!this._CurrentDocContent)
679
+ {
680
+ tmpDisplayEl.innerHTML = '<p style="color:var(--facto-text-tertiary);">Empty document.</p>';
681
+ return;
682
+ }
683
+
684
+ let tmpHTML = this.pict.providers.PictContent.parseMarkdown(this._CurrentDocContent);
685
+ tmpDisplayEl.innerHTML = tmpHTML;
686
+ }
687
+
688
+ toggleEditMode()
689
+ {
690
+ let tmpToggleBtn = document.getElementById('Facto-SourceDetail-EditToggle');
691
+
692
+ if (this._EditMode)
693
+ {
694
+ // Leaving edit mode: marshal content from editor sub-view
695
+ let tmpEditorView = this.pict.views['Facto-Full-SourceEditor'];
696
+ if (tmpEditorView)
697
+ {
698
+ let tmpResult = tmpEditorView.closeEditor();
699
+ this._CurrentDocContent = tmpResult.Content;
700
+ this._CurrentDocName = tmpResult.Name;
701
+ }
702
+
703
+ this._EditMode = false;
704
+ if (tmpToggleBtn)
705
+ {
706
+ tmpToggleBtn.innerHTML = '&#9998; Edit';
707
+ tmpToggleBtn.classList.remove('active');
708
+ }
709
+ }
710
+ else
711
+ {
712
+ // Entering edit mode
713
+ this._EditMode = true;
714
+ if (tmpToggleBtn)
715
+ {
716
+ tmpToggleBtn.innerHTML = '&#10003; Done';
717
+ tmpToggleBtn.classList.add('active');
718
+ }
719
+ }
720
+
721
+ // Re-display current document in the new mode (if one is selected)
722
+ if (this._CurrentIDDoc)
723
+ {
724
+ this._showDocument();
725
+ }
726
+ }
727
+
728
+ createDocument()
729
+ {
730
+ let tmpNameInput = document.getElementById('Facto-SourceDetail-NewDocName');
731
+ let tmpName = tmpNameInput ? tmpNameInput.value.trim() : '';
732
+ if (!tmpName)
733
+ {
734
+ // Auto-number untitled docs based on existing ones
735
+ let tmpUntitledCount = 0;
736
+ if (this._Documentation)
737
+ {
738
+ for (let i = 0; i < this._Documentation.length; i++)
739
+ {
740
+ let tmpDocName = this._Documentation[i].Name || '';
741
+ if (tmpDocName === 'Untitled' || tmpDocName.match(/^Untitled \d+$/))
742
+ {
743
+ tmpUntitledCount++;
744
+ }
745
+ }
746
+ }
747
+ tmpName = 'Untitled ' + (tmpUntitledCount + 1);
748
+ }
749
+
750
+ let tmpProvider = this.pict.providers.Facto;
751
+ tmpProvider.createSourceDocument(this._CurrentIDSource,
752
+ {
753
+ Name: tmpName,
754
+ DocumentType: 'markdown',
755
+ Content: '# ' + tmpName + '\n\n'
756
+ }).then(
757
+ (pResponse) =>
758
+ {
759
+ if (pResponse && pResponse.Success && pResponse.Documentation)
760
+ {
761
+ // Clear the input
762
+ if (tmpNameInput) tmpNameInput.value = '';
763
+
764
+ // Reload doc list and auto-select the new doc
765
+ let tmpNewID = pResponse.Documentation.IDSourceDocumentation;
766
+ return tmpProvider.loadSourceDocumentation(this._CurrentIDSource).then(
767
+ (pDocsResponse) =>
768
+ {
769
+ this._Documentation = (pDocsResponse && pDocsResponse.Documentation) ? pDocsResponse.Documentation : [];
770
+ this._renderDocList();
771
+ this.selectDocument(tmpNewID);
772
+ });
773
+ }
774
+ });
775
+ }
776
+
777
+ closeDocument()
778
+ {
779
+ let tmpEditorWrap = document.getElementById('Facto-SourceDetail-EditorWrap');
780
+ let tmpContentWrap = document.getElementById('Facto-SourceDetail-ContentWrap');
781
+ if (tmpEditorWrap) tmpEditorWrap.style.display = 'none';
782
+ if (tmpContentWrap) tmpContentWrap.style.display = 'none';
783
+
784
+ this._CurrentIDDoc = null;
785
+ this._CurrentDocContent = '';
786
+
787
+ // Update URL to remove doc reference
788
+ if (window.history && window.history.replaceState)
789
+ {
790
+ window.history.replaceState(null, '', '#/Source/' + this._CurrentIDSource);
791
+ }
792
+
793
+ // Re-render doc list to clear active highlight
794
+ this._renderDocList();
795
+ }
796
+
797
+ /**
798
+ * Called by the editor sub-view after a document name change on save.
799
+ */
800
+ onDocumentNameChanged(pIDDoc, pNewName)
801
+ {
802
+ for (let i = 0; i < this._Documentation.length; i++)
803
+ {
804
+ if (this._Documentation[i].IDSourceDocumentation === parseInt(pIDDoc, 10))
805
+ {
806
+ this._Documentation[i].Name = pNewName;
807
+ break;
808
+ }
809
+ }
810
+ this._CurrentDocName = pNewName;
811
+ this._renderDocList();
812
+ }
813
+
814
+ goBack()
815
+ {
816
+ this.pict.PictApplication.navigateTo('/SourceResearch');
817
+ }
818
+ }
819
+
820
+ module.exports = FactoFullSourceDetailView;
821
+
822
+ module.exports.default_configuration = _ViewConfiguration;