retold-data-service 2.0.21 → 2.0.23

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 (37) hide show
  1. package/.quackage-comprehension-loader.json +19 -0
  2. package/bin/retold-data-service-clone.js +4 -1
  3. package/generate-bookstore-comprehension.js +645 -0
  4. package/package.json +7 -7
  5. package/source/Retold-Data-Service.js +30 -2
  6. package/source/services/comprehension-loader/ComprehensionLoader-Command-Load.js +345 -0
  7. package/source/services/comprehension-loader/ComprehensionLoader-Command-Schema.js +97 -0
  8. package/source/services/comprehension-loader/ComprehensionLoader-Command-Session.js +221 -0
  9. package/source/services/comprehension-loader/ComprehensionLoader-Command-WebUI.js +57 -0
  10. package/source/services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js +536 -0
  11. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader-Configuration.json +9 -0
  12. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +86 -0
  13. package/source/services/comprehension-loader/pict-app/Pict-ComprehensionLoader-Bundle.js +6 -0
  14. package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +760 -0
  15. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +360 -0
  16. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +472 -0
  17. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +119 -0
  18. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +269 -0
  19. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +330 -0
  20. package/source/services/comprehension-loader/web/comprehension-loader.js +6794 -0
  21. package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -0
  22. package/source/services/comprehension-loader/web/comprehension-loader.min.js +2 -0
  23. package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -0
  24. package/source/services/comprehension-loader/web/index.html +17 -0
  25. package/source/services/data-cloner/DataCloner-Command-Schema.js +407 -15
  26. package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +59 -1
  27. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +1 -0
  28. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +125 -5
  29. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +18 -8
  30. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +104 -1
  31. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +1 -1
  32. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +12 -0
  33. package/source/services/data-cloner/web/data-cloner.js +201 -139
  34. package/source/services/data-cloner/web/data-cloner.js.map +1 -1
  35. package/source/services/data-cloner/web/data-cloner.min.js +1 -1
  36. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
  37. package/test/RetoldDataService_tests.js +225 -0
@@ -0,0 +1,269 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class ComprehensionLoaderSessionView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ configureSession()
11
+ {
12
+ let tmpServerURL = document.getElementById('serverURL').value.trim();
13
+ if (!tmpServerURL)
14
+ {
15
+ this.pict.providers.ComprehensionLoader.setStatus('sessionConfigStatus', 'Server URL is required.', 'error');
16
+ return;
17
+ }
18
+
19
+ let tmpBody = { ServerURL: tmpServerURL.replace(/\/+$/, '') + '/1.0/' };
20
+
21
+ let tmpAuthMethod = document.getElementById('authMethod').value.trim();
22
+ if (tmpAuthMethod)
23
+ {
24
+ tmpBody.AuthenticationMethod = tmpAuthMethod;
25
+ }
26
+
27
+ let tmpAuthURI = document.getElementById('authURI').value.trim();
28
+ if (tmpAuthURI)
29
+ {
30
+ tmpBody.AuthenticationURITemplate = tmpAuthURI;
31
+ }
32
+
33
+ let tmpCheckURI = document.getElementById('checkURI').value.trim();
34
+ if (tmpCheckURI)
35
+ {
36
+ tmpBody.CheckSessionURITemplate = tmpCheckURI;
37
+ }
38
+
39
+ let tmpCookieName = document.getElementById('cookieName').value.trim();
40
+ if (tmpCookieName)
41
+ {
42
+ tmpBody.CookieName = tmpCookieName;
43
+ }
44
+
45
+ let tmpCookieValueAddr = document.getElementById('cookieValueAddr').value.trim();
46
+ if (tmpCookieValueAddr)
47
+ {
48
+ tmpBody.CookieValueAddress = tmpCookieValueAddr;
49
+ }
50
+
51
+ let tmpCookieValueTemplate = document.getElementById('cookieValueTemplate').value.trim();
52
+ if (tmpCookieValueTemplate)
53
+ {
54
+ tmpBody.CookieValueTemplate = tmpCookieValueTemplate;
55
+ }
56
+
57
+ let tmpLoginMarker = document.getElementById('loginMarker').value.trim();
58
+ if (tmpLoginMarker)
59
+ {
60
+ tmpBody.CheckSessionLoginMarker = tmpLoginMarker;
61
+ }
62
+
63
+ this.pict.providers.ComprehensionLoader.setStatus('sessionConfigStatus', 'Configuring session...', 'info');
64
+
65
+ this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/session/configure', tmpBody)
66
+ .then(
67
+ (pData) =>
68
+ {
69
+ if (pData.Success)
70
+ {
71
+ this.pict.providers.ComprehensionLoader.setStatus('sessionConfigStatus', 'Session configured for ' + pData.ServerURL + ' (domain: ' + pData.DomainMatch + ')', 'ok');
72
+ }
73
+ else
74
+ {
75
+ this.pict.providers.ComprehensionLoader.setStatus('sessionConfigStatus', 'Configuration failed: ' + (pData.Error || 'Unknown error'), 'error');
76
+ }
77
+ })
78
+ .catch(
79
+ (pError) =>
80
+ {
81
+ this.pict.providers.ComprehensionLoader.setStatus('sessionConfigStatus', 'Request failed: ' + pError.message, 'error');
82
+ });
83
+ }
84
+
85
+ authenticate()
86
+ {
87
+ let tmpUserName = document.getElementById('userName').value.trim();
88
+ let tmpPassword = document.getElementById('password').value.trim();
89
+
90
+ if (!tmpUserName || !tmpPassword)
91
+ {
92
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Username and password are required.', 'error');
93
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'error');
94
+ return;
95
+ }
96
+
97
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'busy');
98
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Authenticating...', 'info');
99
+
100
+ this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/session/authenticate', { UserName: tmpUserName, Password: tmpPassword })
101
+ .then(
102
+ (pData) =>
103
+ {
104
+ if (pData.Success && pData.Authenticated)
105
+ {
106
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Authenticated successfully.', 'ok');
107
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'ok');
108
+ }
109
+ else
110
+ {
111
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Authentication failed: ' + (pData.Error || 'Not authenticated'), 'error');
112
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'error');
113
+ }
114
+ })
115
+ .catch(
116
+ (pError) =>
117
+ {
118
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
119
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'error');
120
+ });
121
+ }
122
+
123
+ checkSession()
124
+ {
125
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Checking session...', 'info');
126
+
127
+ this.pict.providers.ComprehensionLoader.api('GET', '/comprehension_load/session/check')
128
+ .then(
129
+ (pData) =>
130
+ {
131
+ if (pData.Authenticated)
132
+ {
133
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Session is active. Server: ' + (pData.ServerURL || 'N/A'), 'ok');
134
+ }
135
+ else if (pData.Configured)
136
+ {
137
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Session configured but not authenticated.', 'warn');
138
+ }
139
+ else
140
+ {
141
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'No session configured.', 'warn');
142
+ }
143
+ })
144
+ .catch(
145
+ (pError) =>
146
+ {
147
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
148
+ });
149
+ }
150
+
151
+ deauthenticate()
152
+ {
153
+ this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/session/deauthenticate')
154
+ .then(
155
+ (pData) =>
156
+ {
157
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Session deauthenticated.', 'info');
158
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, '');
159
+ })
160
+ .catch(
161
+ (pError) =>
162
+ {
163
+ this.pict.providers.ComprehensionLoader.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
164
+ });
165
+ }
166
+
167
+ goAction()
168
+ {
169
+ // Two-step: configure session, then authenticate after delay
170
+ this.pict.providers.ComprehensionLoader.setSectionPhase(1, 'busy');
171
+ this.configureSession();
172
+ setTimeout(
173
+ () =>
174
+ {
175
+ this.authenticate();
176
+ }, 1500);
177
+ }
178
+ }
179
+
180
+ module.exports = ComprehensionLoaderSessionView;
181
+
182
+ module.exports.default_configuration =
183
+ {
184
+ ViewIdentifier: 'ComprehensionLoader-Session',
185
+ DefaultRenderable: 'ComprehensionLoader-Session',
186
+ DefaultDestinationAddress: '#ComprehensionLoader-Section-Session',
187
+ Templates:
188
+ [
189
+ {
190
+ Hash: 'ComprehensionLoader-Session',
191
+ Template: /*html*/`
192
+ <div class="accordion-row">
193
+ <div class="accordion-number">1</div>
194
+ <div class="accordion-card" id="section1" data-section="1">
195
+ <div class="accordion-header" onclick="pict.views['ComprehensionLoader-Layout'].toggleSection('section1')">
196
+ <div class="accordion-title">Remote Session</div>
197
+ <span class="accordion-phase" id="phase1"></span>
198
+ <div class="accordion-preview" id="preview1">Configure remote server URL and credentials</div>
199
+ <div class="accordion-actions">
200
+ <span class="accordion-go" onclick="event.stopPropagation(); pict.views['ComprehensionLoader-Session'].goAction()">go</span>
201
+ <label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto1"> <span class="auto-label">auto</span></label>
202
+ </div>
203
+ <div class="accordion-toggle">&#9660;</div>
204
+ </div>
205
+ <div class="accordion-body">
206
+ <div class="inline-group">
207
+ <div style="flex:2">
208
+ <label for="serverURL">Remote Server URL</label>
209
+ <input type="text" id="serverURL" placeholder="http://remote-server:8086" value="">
210
+ </div>
211
+ <div style="flex:1">
212
+ <label for="authMethod">Auth Method</label>
213
+ <input type="text" id="authMethod" placeholder="get" value="get">
214
+ </div>
215
+ </div>
216
+
217
+ <details style="margin-bottom:10px">
218
+ <summary style="cursor:pointer; font-size:0.9em; color:#666">Advanced Session Options</summary>
219
+ <div style="padding:10px 0">
220
+ <label for="authURI">Authentication URI Template (leave blank for default)</label>
221
+ <input type="text" id="authURI" placeholder="Authenticate/{~D:Record.UserName~}/{~D:Record.Password~}">
222
+ <label for="checkURI">Check Session URI Template</label>
223
+ <input type="text" id="checkURI" placeholder="CheckSession">
224
+ <label for="cookieName">Cookie Name</label>
225
+ <input type="text" id="cookieName" placeholder="SessionID" value="SessionID">
226
+ <label for="cookieValueAddr">Cookie Value Address</label>
227
+ <input type="text" id="cookieValueAddr" placeholder="SessionID" value="SessionID">
228
+ <label for="cookieValueTemplate">Cookie Value Template (overrides Address if set)</label>
229
+ <input type="text" id="cookieValueTemplate" placeholder="{~D:Record.SessionID~}">
230
+ <label for="loginMarker">Login Marker</label>
231
+ <input type="text" id="loginMarker" placeholder="LoggedIn" value="LoggedIn">
232
+ </div>
233
+ </details>
234
+
235
+ <button class="primary" onclick="pict.views['ComprehensionLoader-Session'].configureSession()">Configure Session</button>
236
+ <div id="sessionConfigStatus"></div>
237
+
238
+ <hr style="margin:16px 0; border:none; border-top:1px solid #eee">
239
+
240
+ <div class="inline-group">
241
+ <div>
242
+ <label for="userName">Username</label>
243
+ <input type="text" id="userName" placeholder="username">
244
+ </div>
245
+ <div>
246
+ <label for="password">Password</label>
247
+ <input type="password" id="password" placeholder="password">
248
+ </div>
249
+ </div>
250
+
251
+ <button class="success" onclick="pict.views['ComprehensionLoader-Session'].authenticate()">Authenticate</button>
252
+ <button class="secondary" onclick="pict.views['ComprehensionLoader-Session'].checkSession()">Check Session</button>
253
+ <button class="danger" onclick="pict.views['ComprehensionLoader-Session'].deauthenticate()">Deauthenticate</button>
254
+ <div id="sessionAuthStatus"></div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ `
259
+ }
260
+ ],
261
+ Renderables:
262
+ [
263
+ {
264
+ RenderableHash: 'ComprehensionLoader-Session',
265
+ TemplateHash: 'ComprehensionLoader-Session',
266
+ DestinationAddress: '#ComprehensionLoader-Section-Session'
267
+ }
268
+ ]
269
+ };
@@ -0,0 +1,330 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class ComprehensionLoaderSourceView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ onAfterRender()
11
+ {
12
+ // Restore source mode and toggle UI
13
+ let tmpSourceMode = localStorage.getItem('comprehensionLoader_comprehensionSourceMode');
14
+ if (tmpSourceMode === 'file')
15
+ {
16
+ let tmpFileRadio = document.getElementById('sourceMode_file');
17
+ if (tmpFileRadio) tmpFileRadio.checked = true;
18
+ }
19
+ this.onSourceModeChange();
20
+ }
21
+
22
+ onSourceModeChange()
23
+ {
24
+ let tmpMode = document.querySelector('input[name="comprehensionSourceMode"]:checked');
25
+ let tmpModeName = tmpMode ? tmpMode.value : 'url';
26
+
27
+ let tmpURLSection = document.getElementById('sourceURLSection');
28
+ let tmpFileSection = document.getElementById('sourceFileSection');
29
+
30
+ if (tmpURLSection) tmpURLSection.style.display = tmpModeName === 'url' ? '' : 'none';
31
+ if (tmpFileSection) tmpFileSection.style.display = tmpModeName === 'file' ? '' : 'none';
32
+
33
+ this.pict.providers.ComprehensionLoader.updateAllPreviews();
34
+ }
35
+
36
+ fetchFromURL()
37
+ {
38
+ let tmpURL = document.getElementById('comprehensionURL').value.trim();
39
+ if (!tmpURL)
40
+ {
41
+ this.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Comprehension URL is required.', 'error');
42
+ return;
43
+ }
44
+
45
+ this.pict.providers.ComprehensionLoader.setSectionPhase(3, 'busy');
46
+ this.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Fetching comprehension...', 'info');
47
+
48
+ let tmpSelf = this;
49
+
50
+ // Try browser fetch first, fall back to server proxy on CORS failure
51
+ fetch(tmpURL)
52
+ .then(function(pResponse)
53
+ {
54
+ if (!pResponse.ok) throw new Error('HTTP ' + pResponse.status);
55
+ return pResponse.json();
56
+ })
57
+ .then(function(pData)
58
+ {
59
+ tmpSelf.sendComprehensionToServer(pData);
60
+ })
61
+ .catch(function(pError)
62
+ {
63
+ // CORS or network error — try server-side proxy
64
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Direct fetch failed (' + pError.message + '), trying server proxy...', 'info');
65
+ tmpSelf.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/comprehension/proxy-fetch', { URL: tmpURL })
66
+ .then(function(pProxyData)
67
+ {
68
+ if (pProxyData.Success)
69
+ {
70
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus',
71
+ 'Loaded via proxy: ' + pProxyData.EntityCount + ' entities, ' + tmpSelf.pict.providers.ComprehensionLoader.formatNumber(pProxyData.TotalRecords) + ' records.', 'ok');
72
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'ok');
73
+ tmpSelf.renderComprehensionSummary(pProxyData);
74
+ }
75
+ else
76
+ {
77
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Proxy fetch failed: ' + (pProxyData.Error || 'Unknown error'), 'error');
78
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
79
+ }
80
+ })
81
+ .catch(function(pProxyError)
82
+ {
83
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Proxy request failed: ' + pProxyError.message, 'error');
84
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
85
+ });
86
+ });
87
+ }
88
+
89
+ loadFromFiles()
90
+ {
91
+ let tmpFileInput = document.getElementById('comprehensionFiles');
92
+ if (!tmpFileInput || tmpFileInput.files.length === 0)
93
+ {
94
+ this.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Please select one or more JSON files.', 'error');
95
+ return;
96
+ }
97
+
98
+ this.pict.providers.ComprehensionLoader.setSectionPhase(3, 'busy');
99
+ this.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Reading files...', 'info');
100
+
101
+ let tmpSelf = this;
102
+ let tmpFiles = tmpFileInput.files;
103
+ let tmpMergedData = {};
104
+ let tmpFilesRead = 0;
105
+
106
+ for (let i = 0; i < tmpFiles.length; i++)
107
+ {
108
+ (function(pFile)
109
+ {
110
+ let tmpReader = new FileReader();
111
+ tmpReader.onload = function(pEvent)
112
+ {
113
+ try
114
+ {
115
+ let tmpParsed = JSON.parse(pEvent.target.result);
116
+
117
+ // Merge entity keys from this file into the merged data
118
+ let tmpKeys = Object.keys(tmpParsed);
119
+ for (let k = 0; k < tmpKeys.length; k++)
120
+ {
121
+ let tmpKey = tmpKeys[k];
122
+ if (Array.isArray(tmpParsed[tmpKey]))
123
+ {
124
+ if (!tmpMergedData[tmpKey])
125
+ {
126
+ tmpMergedData[tmpKey] = [];
127
+ }
128
+ tmpMergedData[tmpKey] = tmpMergedData[tmpKey].concat(tmpParsed[tmpKey]);
129
+ }
130
+ else
131
+ {
132
+ tmpMergedData[tmpKey] = tmpParsed[tmpKey];
133
+ }
134
+ }
135
+ }
136
+ catch (pParseError)
137
+ {
138
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Error parsing ' + pFile.name + ': ' + pParseError.message, 'error');
139
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
140
+ return;
141
+ }
142
+
143
+ tmpFilesRead++;
144
+ if (tmpFilesRead === tmpFiles.length)
145
+ {
146
+ // All files read — send merged comprehension to server
147
+ tmpSelf.sendComprehensionToServer(tmpMergedData);
148
+ }
149
+ };
150
+ tmpReader.onerror = function()
151
+ {
152
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Error reading ' + pFile.name, 'error');
153
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
154
+ };
155
+ tmpReader.readAsText(pFile);
156
+ })(tmpFiles[i]);
157
+ }
158
+ }
159
+
160
+ sendComprehensionToServer(pData)
161
+ {
162
+ let tmpSelf = this;
163
+ this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/comprehension/receive', { Comprehension: pData })
164
+ .then(function(pResult)
165
+ {
166
+ if (pResult.Success)
167
+ {
168
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus',
169
+ 'Loaded: ' + pResult.EntityCount + ' entities, ' + tmpSelf.pict.providers.ComprehensionLoader.formatNumber(pResult.TotalRecords) + ' records.', 'ok');
170
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'ok');
171
+ tmpSelf.renderComprehensionSummary(pResult);
172
+ }
173
+ else
174
+ {
175
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Failed: ' + (pResult.Error || 'Unknown error'), 'error');
176
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
177
+ }
178
+ })
179
+ .catch(function(pError)
180
+ {
181
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Request failed: ' + pError.message, 'error');
182
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, 'error');
183
+ });
184
+ }
185
+
186
+ renderComprehensionSummary(pResult)
187
+ {
188
+ let tmpContainer = document.getElementById('comprehensionSummary');
189
+ if (!tmpContainer) return;
190
+
191
+ let tmpHtml = '<table style="width:100%; border-collapse:collapse; margin-top:8px; font-size:0.9em">';
192
+ tmpHtml += '<thead><tr><th style="text-align:left; padding:6px 12px; border-bottom:2px solid #ddd">Entity</th>';
193
+ tmpHtml += '<th style="text-align:right; padding:6px 12px; border-bottom:2px solid #ddd">Records</th></tr></thead>';
194
+ tmpHtml += '<tbody>';
195
+
196
+ let tmpEntityList = pResult.EntityList || [];
197
+ let tmpRecordCounts = pResult.RecordCounts || {};
198
+ for (let i = 0; i < tmpEntityList.length; i++)
199
+ {
200
+ let tmpName = tmpEntityList[i];
201
+ let tmpCount = tmpRecordCounts[tmpName] || 0;
202
+ tmpHtml += '<tr>';
203
+ tmpHtml += '<td style="padding:4px 12px; border-bottom:1px solid #f0f0f0">' + this.pict.providers.ComprehensionLoader.escapeHtml(tmpName) + '</td>';
204
+ tmpHtml += '<td style="padding:4px 12px; border-bottom:1px solid #f0f0f0; text-align:right; font-variant-numeric:tabular-nums">' + this.pict.providers.ComprehensionLoader.formatNumber(tmpCount) + '</td>';
205
+ tmpHtml += '</tr>';
206
+ }
207
+
208
+ tmpHtml += '</tbody>';
209
+ tmpHtml += '<tfoot><tr>';
210
+ tmpHtml += '<td style="padding:6px 12px; font-weight:600">Total</td>';
211
+ tmpHtml += '<td style="padding:6px 12px; text-align:right; font-weight:600; font-variant-numeric:tabular-nums">' + this.pict.providers.ComprehensionLoader.formatNumber(pResult.TotalRecords) + '</td>';
212
+ tmpHtml += '</tr></tfoot>';
213
+ tmpHtml += '</table>';
214
+
215
+ tmpContainer.innerHTML = tmpHtml;
216
+ tmpContainer.style.display = '';
217
+ }
218
+
219
+ clearComprehension()
220
+ {
221
+ let tmpSelf = this;
222
+ this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/comprehension/clear')
223
+ .then(function(pData)
224
+ {
225
+ if (pData.Success)
226
+ {
227
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Comprehension data cleared.', 'info');
228
+ tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(3, '');
229
+ let tmpContainer = document.getElementById('comprehensionSummary');
230
+ if (tmpContainer)
231
+ {
232
+ tmpContainer.innerHTML = '';
233
+ tmpContainer.style.display = 'none';
234
+ }
235
+ }
236
+ })
237
+ .catch(function(pError)
238
+ {
239
+ tmpSelf.pict.providers.ComprehensionLoader.setStatus('sourceStatus', 'Request failed: ' + pError.message, 'error');
240
+ });
241
+ }
242
+
243
+ goAction()
244
+ {
245
+ let tmpMode = document.querySelector('input[name="comprehensionSourceMode"]:checked');
246
+ let tmpModeName = tmpMode ? tmpMode.value : 'url';
247
+
248
+ if (tmpModeName === 'url')
249
+ {
250
+ this.fetchFromURL();
251
+ }
252
+ else
253
+ {
254
+ this.loadFromFiles();
255
+ }
256
+ }
257
+ }
258
+
259
+ module.exports = ComprehensionLoaderSourceView;
260
+
261
+ module.exports.default_configuration =
262
+ {
263
+ ViewIdentifier: 'ComprehensionLoader-Source',
264
+ DefaultRenderable: 'ComprehensionLoader-Source',
265
+ DefaultDestinationAddress: '#ComprehensionLoader-Section-Source',
266
+ Templates:
267
+ [
268
+ {
269
+ Hash: 'ComprehensionLoader-Source',
270
+ Template: /*html*/`
271
+ <div class="accordion-row">
272
+ <div class="accordion-number">3</div>
273
+ <div class="accordion-card" id="section3" data-section="3">
274
+ <div class="accordion-header" onclick="pict.views['ComprehensionLoader-Layout'].toggleSection('section3')">
275
+ <div class="accordion-title">Comprehension Source</div>
276
+ <span class="accordion-phase" id="phase3"></span>
277
+ <div class="accordion-preview" id="preview3">Provide a comprehension JSON URL or upload files</div>
278
+ <div class="accordion-actions">
279
+ <span class="accordion-go" onclick="event.stopPropagation(); pict.views['ComprehensionLoader-Source'].goAction()">go</span>
280
+ <label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto3"> <span class="auto-label">auto</span></label>
281
+ </div>
282
+ <div class="accordion-toggle">&#9660;</div>
283
+ </div>
284
+ <div class="accordion-body">
285
+ <div style="margin-bottom:12px">
286
+ <label style="margin-bottom:6px">Source Mode</label>
287
+ <div style="display:flex; gap:16px; align-items:center">
288
+ <label style="font-weight:normal; margin:0; cursor:pointer">
289
+ <input type="radio" name="comprehensionSourceMode" id="sourceMode_url" value="url" checked onchange="pict.views['ComprehensionLoader-Source'].onSourceModeChange()"> URL
290
+ <span style="color:#888; font-size:0.85em">(fetch from a URL)</span>
291
+ </label>
292
+ <label style="font-weight:normal; margin:0; cursor:pointer">
293
+ <input type="radio" name="comprehensionSourceMode" id="sourceMode_file" value="file" onchange="pict.views['ComprehensionLoader-Source'].onSourceModeChange()"> File Upload
294
+ <span style="color:#888; font-size:0.85em">(load JSON from local files)</span>
295
+ </label>
296
+ </div>
297
+ </div>
298
+
299
+ <div id="sourceURLSection">
300
+ <label for="comprehensionURL">Comprehension JSON URL</label>
301
+ <input type="text" id="comprehensionURL" placeholder="http://example.com/comprehension.json">
302
+ <button class="primary" onclick="pict.views['ComprehensionLoader-Source'].fetchFromURL()">Fetch Comprehension</button>
303
+ </div>
304
+
305
+ <div id="sourceFileSection" style="display:none">
306
+ <label for="comprehensionFiles">Comprehension JSON File(s)</label>
307
+ <input type="file" id="comprehensionFiles" multiple accept=".json" style="margin-bottom:10px">
308
+ <div style="font-size:0.8em; color:#888; margin-bottom:10px">Multiple files will be merged (entity keys combined).</div>
309
+ <button class="primary" onclick="pict.views['ComprehensionLoader-Source'].loadFromFiles()">Load Files</button>
310
+ </div>
311
+
312
+ <button class="secondary" onclick="pict.views['ComprehensionLoader-Source'].clearComprehension()" style="margin-left:0">Clear Comprehension</button>
313
+ <div id="sourceStatus"></div>
314
+
315
+ <div id="comprehensionSummary" style="display:none"></div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ `
320
+ }
321
+ ],
322
+ Renderables:
323
+ [
324
+ {
325
+ RenderableHash: 'ComprehensionLoader-Source',
326
+ TemplateHash: 'ComprehensionLoader-Source',
327
+ DestinationAddress: '#ComprehensionLoader-Section-Source'
328
+ }
329
+ ]
330
+ };