pptb-standard-sample-tool 1.0.12 → 1.1.2
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/README.md +1 -1
- package/dist/app.js +630 -199
- package/dist/icon/sample-icon.svg +2 -0
- package/dist/index.html +44 -13
- package/npm-shrinkwrap.json +6 -6
- package/package.json +7 -6
- /package/{icon → dist/icon}/sample-icon.png +0 -0
package/dist/app.js
CHANGED
|
@@ -22,7 +22,7 @@ let createdId = null;
|
|
|
22
22
|
* Initialize the application
|
|
23
23
|
*/
|
|
24
24
|
async function initialize() {
|
|
25
|
-
log(
|
|
25
|
+
log("Initializing HTML Sample Tool...", "info");
|
|
26
26
|
try {
|
|
27
27
|
// Check connection
|
|
28
28
|
await refreshConnection();
|
|
@@ -34,10 +34,10 @@ async function initialize() {
|
|
|
34
34
|
await applyTheme();
|
|
35
35
|
// Load any previously saved setting into UI
|
|
36
36
|
await loadToolSetting();
|
|
37
|
-
log(
|
|
37
|
+
log("Tool initialized successfully", "success");
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
|
-
log(`Initialization error: ${error.message}`,
|
|
40
|
+
log(`Initialization error: ${error.message}`, "error");
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
@@ -47,12 +47,12 @@ async function refreshConnection() {
|
|
|
47
47
|
try {
|
|
48
48
|
currentConnection = await toolbox.connections.getActiveConnection();
|
|
49
49
|
secondaryConnection = await toolbox.connections.getSecondaryConnection();
|
|
50
|
-
const connectionInfo = document.getElementById(
|
|
50
|
+
const connectionInfo = document.getElementById("connection-info");
|
|
51
51
|
if (!connectionInfo)
|
|
52
52
|
return;
|
|
53
53
|
if (currentConnection) {
|
|
54
54
|
const envClass = currentConnection.environment.toLowerCase();
|
|
55
|
-
connectionInfo.className =
|
|
55
|
+
connectionInfo.className = "info-box success";
|
|
56
56
|
connectionInfo.innerHTML = `
|
|
57
57
|
<div class="connection-details">
|
|
58
58
|
<div class="connection-item">
|
|
@@ -73,19 +73,20 @@ async function refreshConnection() {
|
|
|
73
73
|
</div>
|
|
74
74
|
</div>
|
|
75
75
|
`;
|
|
76
|
-
log(`Connected to: ${currentConnection.name}`,
|
|
76
|
+
log(`Connected to: ${currentConnection.name}`, "success");
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
|
-
connectionInfo.className =
|
|
80
|
-
connectionInfo.innerHTML =
|
|
81
|
-
|
|
79
|
+
connectionInfo.className = "info-box warning";
|
|
80
|
+
connectionInfo.innerHTML =
|
|
81
|
+
"<p><strong>⚠️ No active connection</strong><br>Please connect to a Dataverse environment to use this tool.</p>";
|
|
82
|
+
log("No active connection found", "warning");
|
|
82
83
|
}
|
|
83
84
|
if (secondaryConnection) {
|
|
84
|
-
const secondaryInfo = document.getElementById(
|
|
85
|
+
const secondaryInfo = document.getElementById("secondary-connection-info");
|
|
85
86
|
if (!secondaryInfo)
|
|
86
87
|
return;
|
|
87
88
|
const envClass = secondaryConnection.environment.toLowerCase();
|
|
88
|
-
secondaryInfo.className =
|
|
89
|
+
secondaryInfo.className = "info-box success";
|
|
89
90
|
secondaryInfo.innerHTML = `
|
|
90
91
|
<div class="connection-details">
|
|
91
92
|
<div class="connection-item">
|
|
@@ -106,19 +107,20 @@ async function refreshConnection() {
|
|
|
106
107
|
</div>
|
|
107
108
|
</div>
|
|
108
109
|
`;
|
|
109
|
-
log(`Secondary connection: ${secondaryConnection.name}`,
|
|
110
|
+
log(`Secondary connection: ${secondaryConnection.name}`, "success");
|
|
110
111
|
}
|
|
111
112
|
else {
|
|
112
|
-
const secondaryInfo = document.getElementById(
|
|
113
|
+
const secondaryInfo = document.getElementById("secondary-connection-info");
|
|
113
114
|
if (!secondaryInfo)
|
|
114
115
|
return;
|
|
115
|
-
secondaryInfo.className =
|
|
116
|
-
secondaryInfo.innerHTML =
|
|
117
|
-
|
|
116
|
+
secondaryInfo.className = "info-box warning";
|
|
117
|
+
secondaryInfo.innerHTML =
|
|
118
|
+
"<p><strong>⚠️ No secondary connection</strong><br>Please connect to a secondary Dataverse environment to use this tool.</p>";
|
|
119
|
+
log("No secondary connection found", "warning");
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
catch (error) {
|
|
121
|
-
log(`Error refreshing connection: ${error.message}`,
|
|
123
|
+
log(`Error refreshing connection: ${error.message}`, "error");
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
/**
|
|
@@ -126,24 +128,24 @@ async function refreshConnection() {
|
|
|
126
128
|
*/
|
|
127
129
|
function subscribeToEvents() {
|
|
128
130
|
toolbox.events.on((event, payload) => {
|
|
129
|
-
log(`Event: ${payload.event}`,
|
|
131
|
+
log(`Event: ${payload.event}`, "info");
|
|
130
132
|
switch (payload.event) {
|
|
131
|
-
case
|
|
132
|
-
case
|
|
133
|
+
case "connection:updated":
|
|
134
|
+
case "connection:created":
|
|
133
135
|
refreshConnection();
|
|
134
136
|
break;
|
|
135
|
-
case
|
|
137
|
+
case "connection:deleted":
|
|
136
138
|
currentConnection = null;
|
|
137
139
|
refreshConnection();
|
|
138
140
|
break;
|
|
139
|
-
case
|
|
141
|
+
case "terminal:output":
|
|
140
142
|
handleTerminalOutput(payload.data);
|
|
141
143
|
break;
|
|
142
|
-
case
|
|
144
|
+
case "terminal:command:completed":
|
|
143
145
|
handleCommandCompleted(payload.data);
|
|
144
146
|
break;
|
|
145
|
-
case
|
|
146
|
-
log(`Terminal error: ${payload.data.error}`,
|
|
147
|
+
case "terminal:error":
|
|
148
|
+
log(`Terminal error: ${payload.data.error}`, "error");
|
|
147
149
|
break;
|
|
148
150
|
}
|
|
149
151
|
});
|
|
@@ -153,35 +155,56 @@ function subscribeToEvents() {
|
|
|
153
155
|
*/
|
|
154
156
|
function setupEventHandlers() {
|
|
155
157
|
// Notification buttons
|
|
156
|
-
document
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
document
|
|
158
|
+
document
|
|
159
|
+
.getElementById("show-success-btn")
|
|
160
|
+
?.addEventListener("click", () => showNotification("Success!", "Operation completed successfully", "success"));
|
|
161
|
+
document
|
|
162
|
+
.getElementById("show-info-btn")
|
|
163
|
+
?.addEventListener("click", () => showNotification("Information", "This is an informational message", "info"));
|
|
164
|
+
document
|
|
165
|
+
.getElementById("show-warning-btn")
|
|
166
|
+
?.addEventListener("click", () => showNotification("Warning", "Please review this warning", "warning"));
|
|
167
|
+
document
|
|
168
|
+
.getElementById("show-error-btn")
|
|
169
|
+
?.addEventListener("click", () => showNotification("Error", "An error has occurred", "error"));
|
|
170
|
+
document.getElementById("show-loading-btn")?.addEventListener("click", showLoading);
|
|
160
171
|
// Utility buttons
|
|
161
|
-
document.getElementById(
|
|
162
|
-
document.getElementById(
|
|
163
|
-
document.getElementById(
|
|
172
|
+
document.getElementById("copy-clipboard-btn")?.addEventListener("click", copyToClipboard);
|
|
173
|
+
document.getElementById("get-theme-btn")?.addEventListener("click", showCurrentTheme);
|
|
174
|
+
document.getElementById("save-file-btn")?.addEventListener("click", saveDataToFile);
|
|
175
|
+
// File System API buttons
|
|
176
|
+
document.getElementById("read-text-btn")?.addEventListener("click", readText);
|
|
177
|
+
document.getElementById("read-directory-btn")?.addEventListener("click", readDirectory);
|
|
178
|
+
document.getElementById("create-directory-btn")?.addEventListener("click", createDirectory);
|
|
179
|
+
document.getElementById("read-system-file-btn")?.addEventListener("click", readSystemFile);
|
|
180
|
+
document.getElementById("read-hardcoded-file-btn")?.addEventListener("click", readHardcodedFile);
|
|
181
|
+
document.getElementById("read-direct-file-btn")?.addEventListener("click", readDirectFile);
|
|
164
182
|
// Terminal buttons
|
|
165
|
-
document.getElementById(
|
|
166
|
-
document.getElementById(
|
|
167
|
-
document.getElementById(
|
|
183
|
+
document.getElementById("create-terminal-btn")?.addEventListener("click", createTerminal);
|
|
184
|
+
document.getElementById("execute-command-btn")?.addEventListener("click", executeTerminalCommand);
|
|
185
|
+
document.getElementById("close-terminal-btn")?.addEventListener("click", closeTerminal);
|
|
168
186
|
// Dataverse query button
|
|
169
|
-
document.getElementById(
|
|
170
|
-
document.getElementById(
|
|
187
|
+
document.getElementById("query-accounts-btn")?.addEventListener("click", queryAccounts);
|
|
188
|
+
document.getElementById("query-accounts-secondary-btn")?.addEventListener("click", queryAccountsSecondary);
|
|
189
|
+
document.getElementById("query-contacts-querydata-btn")?.addEventListener("click", queryContactQueryData);
|
|
171
190
|
// CRUD buttons
|
|
172
|
-
document.getElementById(
|
|
173
|
-
document.getElementById(
|
|
174
|
-
document.getElementById(
|
|
191
|
+
document.getElementById("create-contact-btn")?.addEventListener("click", createContact);
|
|
192
|
+
document.getElementById("update-contact-btn")?.addEventListener("click", updateContact);
|
|
193
|
+
document.getElementById("delete-contact-btn")?.addEventListener("click", deleteContact);
|
|
175
194
|
// Metadata button
|
|
176
|
-
document.getElementById(
|
|
195
|
+
document.getElementById("get-metadata-btn")?.addEventListener("click", getContactMetadata);
|
|
196
|
+
document.getElementById("get-metadata-attributes")?.addEventListener("click", getAccountAttributesMetadata);
|
|
197
|
+
document.getElementById("get-metadata-allentities")?.addEventListener("click", getAllEntities);
|
|
198
|
+
//Execute buttons
|
|
199
|
+
document.getElementById("whoami-btn")?.addEventListener("click", executeWhoAmI);
|
|
177
200
|
// Clear log button
|
|
178
|
-
document.getElementById(
|
|
201
|
+
document.getElementById("clear-log-btn")?.addEventListener("click", clearLog);
|
|
179
202
|
// Advanced utilities demos
|
|
180
|
-
document.getElementById(
|
|
181
|
-
document.getElementById(
|
|
203
|
+
document.getElementById("parallel-demo-btn")?.addEventListener("click", demoExecuteParallel);
|
|
204
|
+
document.getElementById("loading-demo-btn")?.addEventListener("click", demoLoading);
|
|
182
205
|
// Settings buttons
|
|
183
|
-
document.getElementById(
|
|
184
|
-
document.getElementById(
|
|
206
|
+
document.getElementById("load-setting-btn")?.addEventListener("click", loadToolSetting);
|
|
207
|
+
document.getElementById("save-setting-btn")?.addEventListener("click", saveToolSetting);
|
|
185
208
|
}
|
|
186
209
|
/**
|
|
187
210
|
* Show notification
|
|
@@ -192,12 +215,23 @@ async function showNotification(title, body, type) {
|
|
|
192
215
|
title,
|
|
193
216
|
body,
|
|
194
217
|
type,
|
|
195
|
-
duration: 3000
|
|
218
|
+
duration: 3000,
|
|
196
219
|
});
|
|
197
220
|
log(`Notification shown: ${title} - ${body}`, type);
|
|
198
221
|
}
|
|
199
222
|
catch (error) {
|
|
200
|
-
log(`Error showing notification: ${error.message}`,
|
|
223
|
+
log(`Error showing notification: ${error.message}`, "error");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function showLoading() {
|
|
227
|
+
try {
|
|
228
|
+
await toolbox.utils.showLoading("Loading... for 3 seconds");
|
|
229
|
+
log("Loading shown for 3 seconds", "info");
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
231
|
+
await toolbox.utils.hideLoading();
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
log(`Error showing loading: ${error.message}`, "error");
|
|
201
235
|
}
|
|
202
236
|
}
|
|
203
237
|
/**
|
|
@@ -207,14 +241,14 @@ async function copyToClipboard() {
|
|
|
207
241
|
try {
|
|
208
242
|
const data = {
|
|
209
243
|
timestamp: new Date().toISOString(),
|
|
210
|
-
connection: currentConnection?.name ||
|
|
211
|
-
message:
|
|
244
|
+
connection: currentConnection?.name || "No connection",
|
|
245
|
+
message: "This data was copied from the HTML Sample Tool",
|
|
212
246
|
};
|
|
213
247
|
await toolbox.utils.copyToClipboard(JSON.stringify(data, null, 2));
|
|
214
|
-
await showNotification(
|
|
248
|
+
await showNotification("Copied!", "Data copied to clipboard", "success");
|
|
215
249
|
}
|
|
216
250
|
catch (error) {
|
|
217
|
-
log(`Error copying to clipboard: ${error.message}`,
|
|
251
|
+
log(`Error copying to clipboard: ${error.message}`, "error");
|
|
218
252
|
}
|
|
219
253
|
}
|
|
220
254
|
/**
|
|
@@ -223,11 +257,11 @@ async function copyToClipboard() {
|
|
|
223
257
|
async function showCurrentTheme() {
|
|
224
258
|
try {
|
|
225
259
|
const theme = await toolbox.utils.getCurrentTheme();
|
|
226
|
-
await showNotification(
|
|
227
|
-
log(`Current theme: ${theme}`,
|
|
260
|
+
await showNotification("Current Theme", `The current theme is: ${theme}`, "info");
|
|
261
|
+
log(`Current theme: ${theme}`, "info");
|
|
228
262
|
}
|
|
229
263
|
catch (error) {
|
|
230
|
-
log(`Error getting theme: ${error.message}`,
|
|
264
|
+
log(`Error getting theme: ${error.message}`, "error");
|
|
231
265
|
}
|
|
232
266
|
}
|
|
233
267
|
/**
|
|
@@ -237,24 +271,305 @@ async function saveDataToFile() {
|
|
|
237
271
|
try {
|
|
238
272
|
const data = {
|
|
239
273
|
timestamp: new Date().toISOString(),
|
|
240
|
-
connection: currentConnection
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
274
|
+
connection: currentConnection
|
|
275
|
+
? {
|
|
276
|
+
name: currentConnection.name,
|
|
277
|
+
url: currentConnection.url,
|
|
278
|
+
environment: currentConnection.environment,
|
|
279
|
+
}
|
|
280
|
+
: null,
|
|
281
|
+
message: "Export from HTML Sample Tool",
|
|
246
282
|
};
|
|
247
|
-
const filePath = await toolbox.
|
|
283
|
+
const filePath = await toolbox.fileSystem.saveFile("sample-export.json", JSON.stringify(data, null, 2));
|
|
248
284
|
if (filePath) {
|
|
249
|
-
await showNotification(
|
|
250
|
-
log(`File saved to: ${filePath}`,
|
|
285
|
+
await showNotification("File Saved", `File saved to: ${filePath}`, "success");
|
|
286
|
+
log(`File saved to: ${filePath}`, "success");
|
|
251
287
|
}
|
|
252
288
|
else {
|
|
253
|
-
log(
|
|
289
|
+
log("File save cancelled", "info");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
log(`Error saving file: ${error.message}`, "error");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Read text file
|
|
298
|
+
*/
|
|
299
|
+
async function readText() {
|
|
300
|
+
try {
|
|
301
|
+
const output = document.getElementById("filesystem-output");
|
|
302
|
+
if (output)
|
|
303
|
+
output.textContent = "Selecting text file...\n";
|
|
304
|
+
const filePath = await toolbox.fileSystem.selectPath({
|
|
305
|
+
type: "file",
|
|
306
|
+
title: "Select a Text File",
|
|
307
|
+
filters: [
|
|
308
|
+
{ name: "Text Files", extensions: ["txt", "json", "xml", "csv", "md"] },
|
|
309
|
+
{ name: "All Files", extensions: ["*"] },
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
if (!filePath) {
|
|
313
|
+
if (output)
|
|
314
|
+
output.textContent = "File selection cancelled.";
|
|
315
|
+
log("File selection cancelled", "info");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (output)
|
|
319
|
+
output.textContent = `Reading file: ${filePath}\n\n`;
|
|
320
|
+
const content = await toolbox.fileSystem.readText(filePath);
|
|
321
|
+
if (output) {
|
|
322
|
+
output.textContent += `File Size: ${content.length} characters\n\n`;
|
|
323
|
+
output.textContent += "Content:\n";
|
|
324
|
+
output.textContent += "─".repeat(50) + "\n";
|
|
325
|
+
output.textContent += content;
|
|
326
|
+
}
|
|
327
|
+
await showNotification("Success", `File read successfully (${content.length} characters)`, "success");
|
|
328
|
+
log(`Read text file: ${filePath} (${content.length} chars)`, "success");
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
const output = document.getElementById("filesystem-output");
|
|
332
|
+
if (output)
|
|
333
|
+
output.textContent = `Error: ${error.message}`;
|
|
334
|
+
log(`Error reading text file: ${error.message}`, "error");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Read directory contents
|
|
339
|
+
*/
|
|
340
|
+
async function readDirectory() {
|
|
341
|
+
try {
|
|
342
|
+
const output = document.getElementById("filesystem-output");
|
|
343
|
+
if (output)
|
|
344
|
+
output.textContent = "Selecting directory...\n";
|
|
345
|
+
const dirPath = await toolbox.fileSystem.selectPath({
|
|
346
|
+
type: "folder",
|
|
347
|
+
title: "Select a Directory",
|
|
348
|
+
});
|
|
349
|
+
if (!dirPath) {
|
|
350
|
+
if (output)
|
|
351
|
+
output.textContent = "Directory selection cancelled.";
|
|
352
|
+
log("Directory selection cancelled", "info");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (output)
|
|
356
|
+
output.textContent = `Reading directory: ${dirPath}\n\n`;
|
|
357
|
+
const entries = await toolbox.fileSystem.readDirectory(dirPath);
|
|
358
|
+
// Separate files and directories
|
|
359
|
+
const directories = entries.filter((e) => e.type === "directory");
|
|
360
|
+
const files = entries.filter((e) => e.type === "file");
|
|
361
|
+
if (output) {
|
|
362
|
+
output.textContent += `Found ${entries.length} entries:\n`;
|
|
363
|
+
output.textContent += "─".repeat(50) + "\n";
|
|
364
|
+
if (directories.length > 0) {
|
|
365
|
+
output.textContent += `\nDirectories (${directories.length}):\n`;
|
|
366
|
+
directories.forEach((entry) => {
|
|
367
|
+
output.textContent += ` 📁 ${entry.name}\n`;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (files.length > 0) {
|
|
371
|
+
output.textContent += `\nFiles (${files.length}):\n`;
|
|
372
|
+
files.forEach((entry) => {
|
|
373
|
+
output.textContent += ` 📄 ${entry.name}\n`;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
254
376
|
}
|
|
377
|
+
await showNotification("Success", `Directory read successfully (${entries.length} entries)`, "success");
|
|
378
|
+
log(`Read directory: ${dirPath} (${entries.length} entries: ${directories.length} dirs, ${files.length} files)`, "success");
|
|
255
379
|
}
|
|
256
380
|
catch (error) {
|
|
257
|
-
|
|
381
|
+
const output = document.getElementById("filesystem-output");
|
|
382
|
+
if (output)
|
|
383
|
+
output.textContent = `Error: ${error.message}`;
|
|
384
|
+
log(`Error reading directory: ${error.message}`, "error");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Create directory
|
|
389
|
+
*/
|
|
390
|
+
async function createDirectory() {
|
|
391
|
+
try {
|
|
392
|
+
const output = document.getElementById("filesystem-output");
|
|
393
|
+
if (output)
|
|
394
|
+
output.textContent = "Selecting parent directory...\n";
|
|
395
|
+
const parentPath = await toolbox.fileSystem.selectPath({
|
|
396
|
+
type: "folder",
|
|
397
|
+
title: "Select Parent Directory",
|
|
398
|
+
});
|
|
399
|
+
if (!parentPath) {
|
|
400
|
+
if (output)
|
|
401
|
+
output.textContent = "Directory selection cancelled.";
|
|
402
|
+
log("Directory selection cancelled", "info");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// Use built-in prompt for directory name (workaround since browser prompt may not be available)
|
|
406
|
+
// For better UX, you could add an input field to the UI
|
|
407
|
+
const dirName = prompt("Enter new directory name:", "new-folder");
|
|
408
|
+
if (!dirName || dirName.trim() === "") {
|
|
409
|
+
if (output)
|
|
410
|
+
output.textContent = "Directory creation cancelled.";
|
|
411
|
+
log("Directory creation cancelled", "info");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const newDirPath = `${parentPath}/${dirName.trim()}`;
|
|
415
|
+
if (output)
|
|
416
|
+
output.textContent = `Creating directory: ${newDirPath}\n`;
|
|
417
|
+
await toolbox.fileSystem.createDirectory(newDirPath);
|
|
418
|
+
if (output) {
|
|
419
|
+
output.textContent += `✓ Directory created successfully!\n`;
|
|
420
|
+
output.textContent += `Path: ${newDirPath}`;
|
|
421
|
+
}
|
|
422
|
+
await showNotification("Success", `Directory created: ${newDirPath}`, "success");
|
|
423
|
+
log(`Created directory: ${newDirPath}`, "success");
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
const output = document.getElementById("filesystem-output");
|
|
427
|
+
if (output)
|
|
428
|
+
output.textContent = `Error: ${error.message}`;
|
|
429
|
+
log(`Error creating directory: ${error.message}`, "error");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Read system file (hardcoded macOS path for testing)
|
|
434
|
+
* This tests error handling when accessing restricted files
|
|
435
|
+
*/
|
|
436
|
+
async function readSystemFile() {
|
|
437
|
+
try {
|
|
438
|
+
const output = document.getElementById("filesystem-output");
|
|
439
|
+
const systemFilePath = "/var/log/system.log";
|
|
440
|
+
if (output)
|
|
441
|
+
output.textContent = `Attempting to read system file: ${systemFilePath}\n\n`;
|
|
442
|
+
log(`Attempting to read system file: ${systemFilePath}`, "info");
|
|
443
|
+
const content = await toolbox.fileSystem.readText(systemFilePath);
|
|
444
|
+
if (output) {
|
|
445
|
+
output.textContent += `File Size: ${content.length} characters\n\n`;
|
|
446
|
+
output.textContent += "Content (first 1000 chars):\n";
|
|
447
|
+
output.textContent += "─".repeat(50) + "\n";
|
|
448
|
+
output.textContent += content.substring(0, 1000);
|
|
449
|
+
if (content.length > 1000) {
|
|
450
|
+
output.textContent += "\n\n... (truncated)";
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
await showNotification("Success", `System file read (${content.length} characters)`, "success");
|
|
454
|
+
log(`Successfully read system file: ${systemFilePath}`, "success");
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
const output = document.getElementById("filesystem-output");
|
|
458
|
+
const errorMsg = error.message;
|
|
459
|
+
if (output) {
|
|
460
|
+
output.textContent = `❌ Error Reading System File\n`;
|
|
461
|
+
output.textContent += `Path: /var/log/system.log\n\n`;
|
|
462
|
+
output.textContent += `Error: ${errorMsg}\n\n`;
|
|
463
|
+
output.textContent += "This is expected - system files are typically restricted due to:\n";
|
|
464
|
+
output.textContent += "- File permissions (insufficient access)\n";
|
|
465
|
+
output.textContent += "- Security restrictions\n";
|
|
466
|
+
output.textContent += "- Sandbox limitations";
|
|
467
|
+
}
|
|
468
|
+
log(`Error reading system file: ${errorMsg}`, "error");
|
|
469
|
+
await showNotification("Error Reading System File", errorMsg, "error");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Read file with selection dialog, but then hardcode a macOS path instead
|
|
474
|
+
* This tests ignoring user selection and attempting to read a restricted path
|
|
475
|
+
*/
|
|
476
|
+
async function readHardcodedFile() {
|
|
477
|
+
try {
|
|
478
|
+
const output = document.getElementById("filesystem-output");
|
|
479
|
+
if (output)
|
|
480
|
+
output.textContent = "Opening file selection dialog...\n";
|
|
481
|
+
log("Opening file selection dialog", "info");
|
|
482
|
+
// Open file picker dialog
|
|
483
|
+
const selectedPath = await toolbox.fileSystem.selectPath({
|
|
484
|
+
type: "file",
|
|
485
|
+
title: "Select a File (will be ignored)",
|
|
486
|
+
filters: [{ name: "All Files", extensions: ["*"] }],
|
|
487
|
+
});
|
|
488
|
+
if (!selectedPath) {
|
|
489
|
+
if (output)
|
|
490
|
+
output.textContent = "File selection cancelled.";
|
|
491
|
+
log("File selection cancelled", "info");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
// Show what was selected
|
|
495
|
+
if (output) {
|
|
496
|
+
output.textContent = `User selected: ${selectedPath}\n\n`;
|
|
497
|
+
output.textContent += "But we're ignoring that and attempting to read a hardcoded system path instead...\n\n";
|
|
498
|
+
output.textContent += "─".repeat(50) + "\n\n";
|
|
499
|
+
}
|
|
500
|
+
log(`User selected: ${selectedPath} (will be ignored)`, "info");
|
|
501
|
+
// Hardcode a macOS system path and ignore the selection
|
|
502
|
+
const hardcodedPath = "/etc/passwd";
|
|
503
|
+
if (output)
|
|
504
|
+
output.textContent += `Attempting to read hardcoded path: ${hardcodedPath}\n\n`;
|
|
505
|
+
log(`Attempting to read hardcoded path: ${hardcodedPath}`, "info");
|
|
506
|
+
const content = await toolbox.fileSystem.readText(hardcodedPath);
|
|
507
|
+
if (output) {
|
|
508
|
+
output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
|
|
509
|
+
output.textContent += "Content:\n";
|
|
510
|
+
output.textContent += "─".repeat(50) + "\n";
|
|
511
|
+
output.textContent += content.substring(0, 1500);
|
|
512
|
+
if (content.length > 1500) {
|
|
513
|
+
output.textContent += "\n\n... (truncated)";
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
await showNotification("File Read Successfully", `Hardcoded file read: ${hardcodedPath} (${content.length} characters)`, "success");
|
|
517
|
+
log(`Successfully read hardcoded file: ${hardcodedPath}`, "success");
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
const output = document.getElementById("filesystem-output");
|
|
521
|
+
const errorMsg = error.message;
|
|
522
|
+
if (output) {
|
|
523
|
+
output.textContent += `❌ Error Reading Hardcoded File\n`;
|
|
524
|
+
output.textContent += `Path: /etc/passwd\n\n`;
|
|
525
|
+
output.textContent += `Error: ${errorMsg}\n\n`;
|
|
526
|
+
output.textContent += "This demonstrates that hardcoded system paths are typically restricted:\n";
|
|
527
|
+
output.textContent += "- Permission denied (macOS sandbox restrictions)\n";
|
|
528
|
+
output.textContent += "- The file picker selected a different path, but we tried to read this one instead\n";
|
|
529
|
+
output.textContent += "- Real-world use case: don't hardcode paths, always use user selection";
|
|
530
|
+
}
|
|
531
|
+
log(`Error reading hardcoded file: ${errorMsg}`, "error");
|
|
532
|
+
await showNotification("Error Reading Hardcoded File", errorMsg, "error");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Read a hardcoded macOS file path directly without using selectPath dialog
|
|
537
|
+
* This tests reading a restricted file with no user interaction
|
|
538
|
+
*/
|
|
539
|
+
async function readDirectFile() {
|
|
540
|
+
try {
|
|
541
|
+
const output = document.getElementById("filesystem-output");
|
|
542
|
+
const hardcodedPath = "/etc/hosts";
|
|
543
|
+
if (output)
|
|
544
|
+
output.textContent = `Reading hardcoded path directly (no dialog)...\n`;
|
|
545
|
+
if (output)
|
|
546
|
+
output.textContent += `Path: ${hardcodedPath}\n\n`;
|
|
547
|
+
log(`Attempting direct read of hardcoded path: ${hardcodedPath}`, "info");
|
|
548
|
+
const content = await toolbox.fileSystem.readText(hardcodedPath);
|
|
549
|
+
if (output) {
|
|
550
|
+
output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
|
|
551
|
+
output.textContent += "Content:\n";
|
|
552
|
+
output.textContent += "─".repeat(50) + "\n";
|
|
553
|
+
output.textContent += content;
|
|
554
|
+
}
|
|
555
|
+
await showNotification("File Read Successfully", `Direct file read: ${hardcodedPath} (${content.length} characters)`, "success");
|
|
556
|
+
log(`Successfully read direct file: ${hardcodedPath}`, "success");
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
const output = document.getElementById("filesystem-output");
|
|
560
|
+
const errorMsg = error.message;
|
|
561
|
+
if (output) {
|
|
562
|
+
output.textContent = `❌ Error Reading Hardcoded Path\n`;
|
|
563
|
+
output.textContent += `Path: /etc/hosts\n`;
|
|
564
|
+
output.textContent += `Method: Direct read (no selectPath dialog)\n\n`;
|
|
565
|
+
output.textContent += `Error: ${errorMsg}\n\n`;
|
|
566
|
+
output.textContent += "This demonstrates:\n";
|
|
567
|
+
output.textContent += "- System files are protected even with hardcoded paths\n";
|
|
568
|
+
output.textContent += "- No file picker dialog was used\n";
|
|
569
|
+
output.textContent += "- Security restrictions apply to all file access attempts";
|
|
570
|
+
}
|
|
571
|
+
log(`Error reading direct file: ${errorMsg}`, "error");
|
|
572
|
+
await showNotification("Error Reading Direct File", errorMsg, "error");
|
|
258
573
|
}
|
|
259
574
|
}
|
|
260
575
|
/**
|
|
@@ -263,20 +578,20 @@ async function saveDataToFile() {
|
|
|
263
578
|
async function createTerminal() {
|
|
264
579
|
try {
|
|
265
580
|
currentTerminal = await toolbox.terminal.create({
|
|
266
|
-
name:
|
|
581
|
+
name: "HTML Sample Terminal",
|
|
267
582
|
});
|
|
268
|
-
log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`,
|
|
583
|
+
log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`, "success");
|
|
269
584
|
// Enable command buttons
|
|
270
|
-
const executeBtn = document.getElementById(
|
|
271
|
-
const closeBtn = document.getElementById(
|
|
585
|
+
const executeBtn = document.getElementById("execute-command-btn");
|
|
586
|
+
const closeBtn = document.getElementById("close-terminal-btn");
|
|
272
587
|
if (executeBtn)
|
|
273
588
|
executeBtn.disabled = false;
|
|
274
589
|
if (closeBtn)
|
|
275
590
|
closeBtn.disabled = false;
|
|
276
|
-
await showNotification(
|
|
591
|
+
await showNotification("Terminal Created", `Terminal ${currentTerminal.name} is ready`, "success");
|
|
277
592
|
}
|
|
278
593
|
catch (error) {
|
|
279
|
-
log(`Error creating terminal: ${error.message}`,
|
|
594
|
+
log(`Error creating terminal: ${error.message}`, "error");
|
|
280
595
|
}
|
|
281
596
|
}
|
|
282
597
|
/**
|
|
@@ -284,21 +599,21 @@ async function createTerminal() {
|
|
|
284
599
|
*/
|
|
285
600
|
async function executeTerminalCommand() {
|
|
286
601
|
if (!currentTerminal) {
|
|
287
|
-
await showNotification(
|
|
602
|
+
await showNotification("No Terminal", "Please create a terminal first", "warning");
|
|
288
603
|
return;
|
|
289
604
|
}
|
|
290
605
|
try {
|
|
291
|
-
const isWindows = navigator.platform.toLowerCase().includes(
|
|
292
|
-
const command = isWindows ?
|
|
293
|
-
const output = document.getElementById(
|
|
606
|
+
const isWindows = navigator.platform.toLowerCase().includes("win");
|
|
607
|
+
const command = isWindows ? "dir" : "ls -la";
|
|
608
|
+
const output = document.getElementById("terminal-output");
|
|
294
609
|
if (output) {
|
|
295
610
|
output.textContent = `> ${command}\n`;
|
|
296
611
|
}
|
|
297
|
-
log(`Executing command: ${command}`,
|
|
612
|
+
log(`Executing command: ${command}`, "info");
|
|
298
613
|
await toolbox.terminal.execute(currentTerminal.id, command);
|
|
299
614
|
}
|
|
300
615
|
catch (error) {
|
|
301
|
-
log(`Error executing command: ${error.message}`,
|
|
616
|
+
log(`Error executing command: ${error.message}`, "error");
|
|
302
617
|
}
|
|
303
618
|
}
|
|
304
619
|
/**
|
|
@@ -309,21 +624,21 @@ async function closeTerminal() {
|
|
|
309
624
|
return;
|
|
310
625
|
try {
|
|
311
626
|
await toolbox.terminal.close(currentTerminal.id);
|
|
312
|
-
log(
|
|
627
|
+
log("Terminal closed", "info");
|
|
313
628
|
currentTerminal = null;
|
|
314
629
|
// Disable command buttons
|
|
315
|
-
const executeBtn = document.getElementById(
|
|
316
|
-
const closeBtn = document.getElementById(
|
|
630
|
+
const executeBtn = document.getElementById("execute-command-btn");
|
|
631
|
+
const closeBtn = document.getElementById("close-terminal-btn");
|
|
317
632
|
if (executeBtn)
|
|
318
633
|
executeBtn.disabled = true;
|
|
319
634
|
if (closeBtn)
|
|
320
635
|
closeBtn.disabled = true;
|
|
321
|
-
const output = document.getElementById(
|
|
636
|
+
const output = document.getElementById("terminal-output");
|
|
322
637
|
if (output)
|
|
323
|
-
output.textContent =
|
|
638
|
+
output.textContent = "";
|
|
324
639
|
}
|
|
325
640
|
catch (error) {
|
|
326
|
-
log(`Error closing terminal: ${error.message}`,
|
|
641
|
+
log(`Error closing terminal: ${error.message}`, "error");
|
|
327
642
|
}
|
|
328
643
|
}
|
|
329
644
|
/**
|
|
@@ -332,7 +647,7 @@ async function closeTerminal() {
|
|
|
332
647
|
function handleTerminalOutput(data) {
|
|
333
648
|
if (!currentTerminal || data.terminalId !== currentTerminal.id)
|
|
334
649
|
return;
|
|
335
|
-
const output = document.getElementById(
|
|
650
|
+
const output = document.getElementById("terminal-output");
|
|
336
651
|
if (output) {
|
|
337
652
|
output.textContent += data.data;
|
|
338
653
|
output.scrollTop = output.scrollHeight;
|
|
@@ -344,7 +659,7 @@ function handleTerminalOutput(data) {
|
|
|
344
659
|
function handleCommandCompleted(data) {
|
|
345
660
|
if (!currentTerminal || data.terminalId !== currentTerminal.id)
|
|
346
661
|
return;
|
|
347
|
-
const output = document.getElementById(
|
|
662
|
+
const output = document.getElementById("terminal-output");
|
|
348
663
|
if (output) {
|
|
349
664
|
output.textContent += `\n[Command completed with exit code: ${data.exitCode}]\n`;
|
|
350
665
|
output.scrollTop = output.scrollHeight;
|
|
@@ -355,16 +670,18 @@ function handleCommandCompleted(data) {
|
|
|
355
670
|
*/
|
|
356
671
|
async function queryAccounts() {
|
|
357
672
|
if (!currentConnection) {
|
|
358
|
-
await showNotification(
|
|
673
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
359
674
|
return;
|
|
360
675
|
}
|
|
361
676
|
try {
|
|
362
|
-
const output = document.getElementById(
|
|
677
|
+
const output = document.getElementById("query-output");
|
|
363
678
|
if (output)
|
|
364
|
-
output.textContent =
|
|
679
|
+
output.textContent = "Querying accounts...\n";
|
|
365
680
|
// If user saved a FetchXML in settings, prefer that; otherwise use default
|
|
366
|
-
const saved = await getSetting(
|
|
367
|
-
const fetchXml =
|
|
681
|
+
const saved = await getSetting("demo.fetchxml");
|
|
682
|
+
const fetchXml = saved && saved.trim().length > 0
|
|
683
|
+
? saved
|
|
684
|
+
: `
|
|
368
685
|
<fetch top="10">
|
|
369
686
|
<entity name="account">
|
|
370
687
|
<attribute name="name" />
|
|
@@ -385,16 +702,51 @@ async function queryAccounts() {
|
|
|
385
702
|
output.textContent += ` Email: ${account.emailaddress1}\n`;
|
|
386
703
|
if (account.telephone1)
|
|
387
704
|
output.textContent += ` Phone: ${account.telephone1}\n`;
|
|
388
|
-
output.textContent +=
|
|
705
|
+
output.textContent += "\n";
|
|
389
706
|
});
|
|
390
707
|
}
|
|
391
|
-
log(`Queried ${result.value.length} accounts`,
|
|
708
|
+
log(`Queried ${result.value.length} accounts`, "success");
|
|
392
709
|
}
|
|
393
710
|
catch (error) {
|
|
394
|
-
const output = document.getElementById(
|
|
711
|
+
const output = document.getElementById("query-output");
|
|
395
712
|
if (output)
|
|
396
713
|
output.textContent = `Error: ${error.message}`;
|
|
397
|
-
log(`Error querying accounts: ${error.message}`,
|
|
714
|
+
log(`Error querying accounts: ${error.message}`, "error");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function queryContactQueryData() {
|
|
718
|
+
if (!currentConnection) {
|
|
719
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const output = document.getElementById("query-output");
|
|
723
|
+
try {
|
|
724
|
+
if (output)
|
|
725
|
+
output.textContent = "Querying contacts... with QueryData\n";
|
|
726
|
+
const queryString = "contacts?$top=10&$select=fullname,contactid,emailaddress1,telephone1&$orderby=fullname asc";
|
|
727
|
+
if (output)
|
|
728
|
+
output.textContent += "Using QueryData:\n" + queryString + "\n\n";
|
|
729
|
+
const result = await window.dataverseAPI.queryData(queryString);
|
|
730
|
+
if (output)
|
|
731
|
+
output.textContent += `Found ${result.value.length} contact(s):\n\n`;
|
|
732
|
+
result.value.forEach((contact, index) => {
|
|
733
|
+
if (output) {
|
|
734
|
+
output.textContent += `${index + 1}. ${contact.fullname}\n`;
|
|
735
|
+
output.textContent += ` ID: ${contact.contactid}\n`;
|
|
736
|
+
if (contact.emailaddress1)
|
|
737
|
+
output.textContent += ` Email: ${contact.emailaddress1}\n`;
|
|
738
|
+
if (contact.telephone1)
|
|
739
|
+
output.textContent += ` Phone: ${contact.telephone1}\n`;
|
|
740
|
+
output.textContent += "\n";
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
log(`Queried ${result.value.length} contacts`, "success");
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
const errorMsg = `Error: ${error.message}`;
|
|
747
|
+
if (output)
|
|
748
|
+
output.textContent = errorMsg;
|
|
749
|
+
log(`Error querying contacts: ${error.message}`, "error");
|
|
398
750
|
}
|
|
399
751
|
}
|
|
400
752
|
/**
|
|
@@ -402,16 +754,18 @@ async function queryAccounts() {
|
|
|
402
754
|
*/
|
|
403
755
|
async function queryAccountsSecondary() {
|
|
404
756
|
if (!secondaryConnection) {
|
|
405
|
-
await showNotification(
|
|
757
|
+
await showNotification("No Secondary Connection", "Please configure a secondary Dataverse connection", "warning");
|
|
406
758
|
return;
|
|
407
759
|
}
|
|
408
760
|
try {
|
|
409
|
-
const output = document.getElementById(
|
|
761
|
+
const output = document.getElementById("query-output-secondary");
|
|
410
762
|
if (output)
|
|
411
|
-
output.textContent =
|
|
763
|
+
output.textContent = "Querying accounts using secondary connection...\n";
|
|
412
764
|
// Use saved FetchXML if present, otherwise default
|
|
413
|
-
const saved = await getSetting(
|
|
414
|
-
const fetchXml =
|
|
765
|
+
const saved = await getSetting("demo.fetchxml");
|
|
766
|
+
const fetchXml = saved && saved.trim().length > 0
|
|
767
|
+
? saved
|
|
768
|
+
: `
|
|
415
769
|
<fetch top="10">
|
|
416
770
|
<entity name="account">
|
|
417
771
|
<attribute name="name" />
|
|
@@ -423,7 +777,7 @@ async function queryAccountsSecondary() {
|
|
|
423
777
|
</fetch>
|
|
424
778
|
`.trim();
|
|
425
779
|
// Pass 'secondary' to target the secondary connection
|
|
426
|
-
const result = await dataverse.fetchXmlQuery(fetchXml,
|
|
780
|
+
const result = await dataverse.fetchXmlQuery(fetchXml, "secondary");
|
|
427
781
|
if (output) {
|
|
428
782
|
output.textContent = `Found ${result.value.length} account(s) on secondary:\n\n`;
|
|
429
783
|
result.value.forEach((account, index) => {
|
|
@@ -433,16 +787,16 @@ async function queryAccountsSecondary() {
|
|
|
433
787
|
output.textContent += ` Email: ${account.emailaddress1}\n`;
|
|
434
788
|
if (account.telephone1)
|
|
435
789
|
output.textContent += ` Phone: ${account.telephone1}\n`;
|
|
436
|
-
output.textContent +=
|
|
790
|
+
output.textContent += "\n";
|
|
437
791
|
});
|
|
438
792
|
}
|
|
439
|
-
log(`Queried ${result.value.length} accounts on secondary`,
|
|
793
|
+
log(`Queried ${result.value.length} accounts on secondary`, "success");
|
|
440
794
|
}
|
|
441
795
|
catch (error) {
|
|
442
|
-
const output = document.getElementById(
|
|
796
|
+
const output = document.getElementById("query-output-secondary");
|
|
443
797
|
if (output)
|
|
444
798
|
output.textContent = `Error (secondary): ${error.message}`;
|
|
445
|
-
log(`Error querying accounts (secondary): ${error.message}`,
|
|
799
|
+
log(`Error querying accounts (secondary): ${error.message}`, "error");
|
|
446
800
|
}
|
|
447
801
|
}
|
|
448
802
|
/**
|
|
@@ -450,20 +804,20 @@ async function queryAccountsSecondary() {
|
|
|
450
804
|
*/
|
|
451
805
|
async function createContact() {
|
|
452
806
|
if (!currentConnection) {
|
|
453
|
-
await showNotification(
|
|
807
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
454
808
|
return;
|
|
455
809
|
}
|
|
456
810
|
try {
|
|
457
|
-
const firstnameInput = document.getElementById(
|
|
458
|
-
const lastnameInput = document.getElementById(
|
|
459
|
-
const output = document.getElementById(
|
|
811
|
+
const firstnameInput = document.getElementById("contact-firstname");
|
|
812
|
+
const lastnameInput = document.getElementById("contact-lastname");
|
|
813
|
+
const output = document.getElementById("crud-output");
|
|
460
814
|
if (output)
|
|
461
|
-
output.textContent =
|
|
462
|
-
const result = await dataverse.create(
|
|
815
|
+
output.textContent = "Creating contact...\n";
|
|
816
|
+
const result = await dataverse.create("contact", {
|
|
463
817
|
firstname: firstnameInput.value,
|
|
464
818
|
lastname: lastnameInput.value,
|
|
465
|
-
telephone1:
|
|
466
|
-
description:
|
|
819
|
+
telephone1: "555-0100",
|
|
820
|
+
description: "Created by HTML Sample Tool",
|
|
467
821
|
});
|
|
468
822
|
createdId = result.id;
|
|
469
823
|
if (output) {
|
|
@@ -472,20 +826,20 @@ async function createContact() {
|
|
|
472
826
|
output.textContent += `Name: ${firstnameInput.value} ${lastnameInput.value}\n`;
|
|
473
827
|
}
|
|
474
828
|
// Enable update and delete buttons
|
|
475
|
-
const updateBtn = document.getElementById(
|
|
476
|
-
const deleteBtn = document.getElementById(
|
|
829
|
+
const updateBtn = document.getElementById("update-contact-btn");
|
|
830
|
+
const deleteBtn = document.getElementById("delete-contact-btn");
|
|
477
831
|
if (updateBtn)
|
|
478
832
|
updateBtn.disabled = false;
|
|
479
833
|
if (deleteBtn)
|
|
480
834
|
deleteBtn.disabled = false;
|
|
481
|
-
await showNotification(
|
|
482
|
-
log(`Contact created: ${result.id}`,
|
|
835
|
+
await showNotification("Contact Created", `Contact "${firstnameInput.value} ${lastnameInput.value}" created successfully`, "success");
|
|
836
|
+
log(`Contact created: ${result.id}`, "success");
|
|
483
837
|
}
|
|
484
838
|
catch (error) {
|
|
485
|
-
const output = document.getElementById(
|
|
839
|
+
const output = document.getElementById("crud-output");
|
|
486
840
|
if (output)
|
|
487
841
|
output.textContent = `Error: ${error.message}`;
|
|
488
|
-
log(`Error creating contact: ${error.message}`,
|
|
842
|
+
log(`Error creating contact: ${error.message}`, "error");
|
|
489
843
|
}
|
|
490
844
|
}
|
|
491
845
|
/**
|
|
@@ -493,30 +847,30 @@ async function createContact() {
|
|
|
493
847
|
*/
|
|
494
848
|
async function updateContact() {
|
|
495
849
|
if (!createdId) {
|
|
496
|
-
await showNotification(
|
|
850
|
+
await showNotification("No Contact", "Please create a contact first", "warning");
|
|
497
851
|
return;
|
|
498
852
|
}
|
|
499
853
|
try {
|
|
500
|
-
const output = document.getElementById(
|
|
854
|
+
const output = document.getElementById("crud-output");
|
|
501
855
|
if (output)
|
|
502
|
-
output.textContent =
|
|
503
|
-
await dataverse.update(
|
|
504
|
-
description:
|
|
505
|
-
telephone1:
|
|
856
|
+
output.textContent = "Updating contact...\n";
|
|
857
|
+
await dataverse.update("contact", createdId, {
|
|
858
|
+
description: "Updated by HTML Sample Tool at " + new Date().toISOString(),
|
|
859
|
+
telephone1: "555-0200",
|
|
506
860
|
});
|
|
507
861
|
if (output) {
|
|
508
862
|
output.textContent = `Contact updated successfully!\n\n`;
|
|
509
863
|
output.textContent += `ID: ${createdId}\n`;
|
|
510
864
|
output.textContent += `Updated fields: description, telephone1\n`;
|
|
511
865
|
}
|
|
512
|
-
await showNotification(
|
|
513
|
-
log(`Contact updated: ${createdId}`,
|
|
866
|
+
await showNotification("Contact Updated", "Contact updated successfully", "success");
|
|
867
|
+
log(`Contact updated: ${createdId}`, "success");
|
|
514
868
|
}
|
|
515
869
|
catch (error) {
|
|
516
|
-
const output = document.getElementById(
|
|
870
|
+
const output = document.getElementById("crud-output");
|
|
517
871
|
if (output)
|
|
518
872
|
output.textContent = `Error: ${error.message}`;
|
|
519
|
-
log(`Error updating contact: ${error.message}`,
|
|
873
|
+
log(`Error updating contact: ${error.message}`, "error");
|
|
520
874
|
}
|
|
521
875
|
}
|
|
522
876
|
/**
|
|
@@ -524,34 +878,34 @@ async function updateContact() {
|
|
|
524
878
|
*/
|
|
525
879
|
async function deleteContact() {
|
|
526
880
|
if (!createdId) {
|
|
527
|
-
await showNotification(
|
|
881
|
+
await showNotification("No Contact", "Please create a contact first", "warning");
|
|
528
882
|
return;
|
|
529
883
|
}
|
|
530
884
|
try {
|
|
531
|
-
const output = document.getElementById(
|
|
885
|
+
const output = document.getElementById("crud-output");
|
|
532
886
|
if (output)
|
|
533
|
-
output.textContent =
|
|
534
|
-
await dataverse.delete(
|
|
887
|
+
output.textContent = "Deleting contact...\n";
|
|
888
|
+
await dataverse.delete("contact", createdId);
|
|
535
889
|
if (output) {
|
|
536
890
|
output.textContent = `Contact deleted successfully!\n\n`;
|
|
537
891
|
output.textContent += `ID: ${createdId}\n`;
|
|
538
892
|
}
|
|
539
893
|
// Disable update and delete buttons
|
|
540
|
-
const updateBtn = document.getElementById(
|
|
541
|
-
const deleteBtn = document.getElementById(
|
|
894
|
+
const updateBtn = document.getElementById("update-contact-btn");
|
|
895
|
+
const deleteBtn = document.getElementById("delete-contact-btn");
|
|
542
896
|
if (updateBtn)
|
|
543
897
|
updateBtn.disabled = true;
|
|
544
898
|
if (deleteBtn)
|
|
545
899
|
deleteBtn.disabled = true;
|
|
546
|
-
await showNotification(
|
|
547
|
-
log(`Contact deleted: ${createdId}`,
|
|
900
|
+
await showNotification("Contact Deleted", "Contact deleted successfully", "success");
|
|
901
|
+
log(`Contact deleted: ${createdId}`, "success");
|
|
548
902
|
createdId = null;
|
|
549
903
|
}
|
|
550
904
|
catch (error) {
|
|
551
|
-
const output = document.getElementById(
|
|
905
|
+
const output = document.getElementById("crud-output");
|
|
552
906
|
if (output)
|
|
553
907
|
output.textContent = `Error: ${error.message}`;
|
|
554
|
-
log(`Error deleting contact: ${error.message}`,
|
|
908
|
+
log(`Error deleting contact: ${error.message}`, "error");
|
|
555
909
|
}
|
|
556
910
|
}
|
|
557
911
|
/**
|
|
@@ -559,28 +913,106 @@ async function deleteContact() {
|
|
|
559
913
|
*/
|
|
560
914
|
async function getContactMetadata() {
|
|
561
915
|
if (!currentConnection) {
|
|
562
|
-
await showNotification(
|
|
916
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
563
917
|
return;
|
|
564
918
|
}
|
|
565
919
|
try {
|
|
566
|
-
const output = document.getElementById(
|
|
920
|
+
const output = document.getElementById("metadata-output");
|
|
567
921
|
if (output)
|
|
568
|
-
output.textContent =
|
|
922
|
+
output.textContent = "Retrieving metadata...\n";
|
|
569
923
|
// Adjusted to match current API signature (logical name, includeAttributes?)
|
|
570
|
-
const metadata = await dataverse.getEntityMetadata(
|
|
924
|
+
const metadata = await dataverse.getEntityMetadata("contact", true);
|
|
571
925
|
if (output) {
|
|
572
|
-
output.textContent =
|
|
926
|
+
output.textContent = "Contact Entity Metadata:\n\n";
|
|
573
927
|
output.textContent += `Logical Name: ${metadata.LogicalName}\n`;
|
|
574
928
|
output.textContent += `Metadata ID: ${metadata.MetadataId}\n`;
|
|
575
|
-
output.textContent += `Display Name: ${metadata.DisplayName?.LocalizedLabels?.[0]?.Label ||
|
|
929
|
+
output.textContent += `Display Name: ${metadata.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
|
|
930
|
+
}
|
|
931
|
+
log("Contact metadata retrieved", "success");
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
const output = document.getElementById("metadata-output");
|
|
935
|
+
if (output)
|
|
936
|
+
output.textContent = `Error: ${error.message}`;
|
|
937
|
+
log(`Error getting metadata: ${error.message}`, "error");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
async function getAccountAttributesMetadata() {
|
|
941
|
+
if (!currentConnection) {
|
|
942
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const output = document.getElementById("metadata-output");
|
|
946
|
+
try {
|
|
947
|
+
if (output) {
|
|
948
|
+
output.textContent = "Retrieving account attributes metadata...\n";
|
|
949
|
+
const metadata = await dataverse.getEntityRelatedMetadata("account", "Attributes", [
|
|
950
|
+
"LogicalName",
|
|
951
|
+
"DisplayName",
|
|
952
|
+
"AttributeType",
|
|
953
|
+
]);
|
|
954
|
+
if (metadata) {
|
|
955
|
+
const metadataArray = metadata.value;
|
|
956
|
+
output.textContent += `Found ${metadataArray.length} attributes:\n\n`;
|
|
957
|
+
metadataArray.forEach((attr, index) => {
|
|
958
|
+
output.textContent += `${index + 1}. ${attr.LogicalName} (${attr.AttributeType}) - ${attr.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
const output = document.getElementById("metadata-output");
|
|
965
|
+
if (output)
|
|
966
|
+
output.textContent = `Error: ${error.message}`;
|
|
967
|
+
log(`Error getting account attributes metadata: ${error.message}`, "error");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
async function getAllEntities() {
|
|
971
|
+
if (!currentConnection) {
|
|
972
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const output = document.getElementById("metadata-output");
|
|
976
|
+
if (output) {
|
|
977
|
+
try {
|
|
978
|
+
output.textContent = "Retrieving all entities metadata...\n";
|
|
979
|
+
const metadata = await dataverse.getAllEntitiesMetadata();
|
|
980
|
+
if (metadata) {
|
|
981
|
+
const metadataArray = metadata.value;
|
|
982
|
+
output.textContent += `Found ${metadataArray.length} entities:\n\n`;
|
|
983
|
+
metadataArray.forEach((entity, index) => {
|
|
984
|
+
output.textContent += `${index + 1}. ${entity.LogicalName} - ${entity.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
if (output)
|
|
990
|
+
output.textContent = `Error: ${error.message}`;
|
|
991
|
+
log(`Error getting all entities metadata: ${error.message}`, "error");
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
async function executeWhoAmI() {
|
|
996
|
+
if (!currentConnection) {
|
|
997
|
+
await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
try {
|
|
1001
|
+
const output = document.getElementById("execute-output");
|
|
1002
|
+
if (output)
|
|
1003
|
+
output.textContent = "Executing WhoAmI action...\n";
|
|
1004
|
+
const result = await dataverse.execute({ operationName: "WhoAmI", operationType: "function" });
|
|
1005
|
+
if (output) {
|
|
1006
|
+
output.textContent = "WhoAmI Result:\n\n";
|
|
1007
|
+
output.textContent += JSON.stringify(result, null, 2);
|
|
576
1008
|
}
|
|
577
|
-
log(
|
|
1009
|
+
log("WhoAmI executed successfully", "success");
|
|
578
1010
|
}
|
|
579
1011
|
catch (error) {
|
|
580
|
-
const output = document.getElementById(
|
|
1012
|
+
const output = document.getElementById("execute-output");
|
|
581
1013
|
if (output)
|
|
582
1014
|
output.textContent = `Error: ${error.message}`;
|
|
583
|
-
log(`Error
|
|
1015
|
+
log(`Error executing WhoAmI: ${error.message}`, "error");
|
|
584
1016
|
}
|
|
585
1017
|
}
|
|
586
1018
|
/**
|
|
@@ -589,21 +1021,21 @@ async function getContactMetadata() {
|
|
|
589
1021
|
async function applyTheme() {
|
|
590
1022
|
try {
|
|
591
1023
|
const theme = await toolbox.utils.getCurrentTheme();
|
|
592
|
-
document.body.setAttribute(
|
|
1024
|
+
document.body.setAttribute("data-theme", theme);
|
|
593
1025
|
}
|
|
594
1026
|
catch (error) {
|
|
595
|
-
log(`Error applying theme: ${error.message}`,
|
|
1027
|
+
log(`Error applying theme: ${error.message}`, "error");
|
|
596
1028
|
}
|
|
597
1029
|
}
|
|
598
1030
|
/**
|
|
599
1031
|
* Log message to event log
|
|
600
1032
|
*/
|
|
601
|
-
function log(message, type =
|
|
602
|
-
const logDiv = document.getElementById(
|
|
1033
|
+
function log(message, type = "info") {
|
|
1034
|
+
const logDiv = document.getElementById("event-log");
|
|
603
1035
|
if (!logDiv)
|
|
604
1036
|
return;
|
|
605
1037
|
const timestamp = new Date().toLocaleTimeString();
|
|
606
|
-
const logEntry = document.createElement(
|
|
1038
|
+
const logEntry = document.createElement("div");
|
|
607
1039
|
logEntry.className = `log-entry ${type}`;
|
|
608
1040
|
logEntry.innerHTML = `
|
|
609
1041
|
<span class="log-timestamp">[${timestamp}]</span>
|
|
@@ -620,15 +1052,15 @@ function log(message, type = 'info') {
|
|
|
620
1052
|
* Clear event log
|
|
621
1053
|
*/
|
|
622
1054
|
function clearLog() {
|
|
623
|
-
const logDiv = document.getElementById(
|
|
1055
|
+
const logDiv = document.getElementById("event-log");
|
|
624
1056
|
if (logDiv) {
|
|
625
|
-
logDiv.innerHTML =
|
|
1057
|
+
logDiv.innerHTML = "";
|
|
626
1058
|
}
|
|
627
1059
|
}
|
|
628
1060
|
// -----------------------------
|
|
629
1061
|
// Tool Settings helpers & demos
|
|
630
1062
|
// -----------------------------
|
|
631
|
-
const SETTINGS_KEY =
|
|
1063
|
+
const SETTINGS_KEY = "demo.fetchxml";
|
|
632
1064
|
async function getSetting(key) {
|
|
633
1065
|
try {
|
|
634
1066
|
// Tool settings are scoped to the tool; implementation provided by the host app
|
|
@@ -637,7 +1069,7 @@ async function getSetting(key) {
|
|
|
637
1069
|
return value;
|
|
638
1070
|
}
|
|
639
1071
|
catch (error) {
|
|
640
|
-
log(`Error reading setting: ${error.message}`,
|
|
1072
|
+
log(`Error reading setting: ${error.message}`, "error");
|
|
641
1073
|
return undefined;
|
|
642
1074
|
}
|
|
643
1075
|
}
|
|
@@ -646,21 +1078,20 @@ async function setSetting(key, value) {
|
|
|
646
1078
|
await toolbox.settings?.set?.(key, value);
|
|
647
1079
|
}
|
|
648
1080
|
catch (error) {
|
|
649
|
-
log(`Error saving setting: ${error.message}`,
|
|
1081
|
+
log(`Error saving setting: ${error.message}`, "error");
|
|
650
1082
|
throw error;
|
|
651
1083
|
}
|
|
652
1084
|
}
|
|
653
1085
|
async function loadToolSetting() {
|
|
654
|
-
const textarea = document.getElementById(
|
|
655
|
-
const output = document.getElementById(
|
|
1086
|
+
const textarea = document.getElementById("settings-fetchxml");
|
|
1087
|
+
const output = document.getElementById("settings-output");
|
|
656
1088
|
try {
|
|
657
1089
|
const val = await getSetting(SETTINGS_KEY);
|
|
658
|
-
debugger;
|
|
659
1090
|
if (textarea)
|
|
660
|
-
textarea.value = val ||
|
|
1091
|
+
textarea.value = val || "";
|
|
661
1092
|
if (output)
|
|
662
|
-
output.textContent = val ?
|
|
663
|
-
log(
|
|
1093
|
+
output.textContent = val ? "Loaded saved FetchXML from settings." : "No saved FetchXML found.";
|
|
1094
|
+
log("Loaded tool setting", "info");
|
|
664
1095
|
}
|
|
665
1096
|
catch (error) {
|
|
666
1097
|
if (output)
|
|
@@ -668,24 +1099,24 @@ async function loadToolSetting() {
|
|
|
668
1099
|
}
|
|
669
1100
|
}
|
|
670
1101
|
async function saveToolSetting() {
|
|
671
|
-
const textarea = document.getElementById(
|
|
672
|
-
const output = document.getElementById(
|
|
673
|
-
const value = (textarea?.value ||
|
|
1102
|
+
const textarea = document.getElementById("settings-fetchxml");
|
|
1103
|
+
const output = document.getElementById("settings-output");
|
|
1104
|
+
const value = (textarea?.value || "").trim();
|
|
674
1105
|
if (!value) {
|
|
675
|
-
await showNotification(
|
|
1106
|
+
await showNotification("Nothing to save", "Enter FetchXML before saving.", "warning");
|
|
676
1107
|
return;
|
|
677
1108
|
}
|
|
678
1109
|
try {
|
|
679
1110
|
await setSetting(SETTINGS_KEY, value);
|
|
680
1111
|
if (output)
|
|
681
|
-
output.textContent =
|
|
682
|
-
await showNotification(
|
|
683
|
-
log(
|
|
1112
|
+
output.textContent = "Saved FetchXML to tool settings.";
|
|
1113
|
+
await showNotification("Setting Saved", "Your FetchXML has been saved.", "success");
|
|
1114
|
+
log("Saved tool setting", "success");
|
|
684
1115
|
}
|
|
685
1116
|
catch (error) {
|
|
686
1117
|
if (output)
|
|
687
1118
|
output.textContent = `Error saving setting: ${error.message}`;
|
|
688
|
-
await showNotification(
|
|
1119
|
+
await showNotification("Save Failed", error.message, "error");
|
|
689
1120
|
}
|
|
690
1121
|
}
|
|
691
1122
|
/**
|
|
@@ -693,13 +1124,13 @@ async function saveToolSetting() {
|
|
|
693
1124
|
* Falls back gracefully if no connection is available.
|
|
694
1125
|
*/
|
|
695
1126
|
async function demoExecuteParallel() {
|
|
696
|
-
const output = document.getElementById(
|
|
1127
|
+
const output = document.getElementById("parallel-output");
|
|
697
1128
|
if (output)
|
|
698
|
-
output.textContent =
|
|
1129
|
+
output.textContent = "Running parallel operations...\n";
|
|
699
1130
|
if (!currentConnection) {
|
|
700
1131
|
if (output)
|
|
701
|
-
output.textContent +=
|
|
702
|
-
await showNotification(
|
|
1132
|
+
output.textContent += "No active Dataverse connection.\n";
|
|
1133
|
+
await showNotification("No Connection", "Connect to a Dataverse environment to run the parallel demo", "warning");
|
|
703
1134
|
return;
|
|
704
1135
|
}
|
|
705
1136
|
try {
|
|
@@ -707,70 +1138,70 @@ async function demoExecuteParallel() {
|
|
|
707
1138
|
const accountFetchXml = `<fetch top="1"><entity name="account"><attribute name="name" /><attribute name="accountid" /></entity></fetch>`;
|
|
708
1139
|
const contactFetchXml = `<fetch top="1"><entity name="contact"><attribute name="fullname" /><attribute name="contactid" /></entity></fetch>`;
|
|
709
1140
|
const userFetchXml = `<fetch top="3"><entity name="systemuser"><attribute name="fullname" /><attribute name="systemuserid" /></entity></fetch>`;
|
|
710
|
-
log(
|
|
1141
|
+
log("Starting parallel Dataverse queries", "info");
|
|
711
1142
|
const [accounts, contacts, users] = await toolbox.utils.executeParallel(dataverse.fetchXmlQuery(accountFetchXml), dataverse.fetchXmlQuery(contactFetchXml), dataverse.fetchXmlQuery(userFetchXml));
|
|
712
1143
|
if (output) {
|
|
713
|
-
output.textContent +=
|
|
1144
|
+
output.textContent += "All operations completed!\n\n";
|
|
714
1145
|
output.textContent += `Accounts Returned: ${accounts.value.length}\n`;
|
|
715
|
-
accounts.value.forEach((a) => output.textContent += ` • ${a.name} (${a.accountid})\n`);
|
|
1146
|
+
accounts.value.forEach((a) => (output.textContent += ` • ${a.name} (${a.accountid})\n`));
|
|
716
1147
|
output.textContent += `\nContacts Returned: ${contacts.value.length}\n`;
|
|
717
|
-
contacts.value.forEach((c) => output.textContent += ` • ${c.fullname} (${c.contactid})\n`);
|
|
1148
|
+
contacts.value.forEach((c) => (output.textContent += ` • ${c.fullname} (${c.contactid})\n`));
|
|
718
1149
|
output.textContent += `\nUsers Returned: ${users.value.length}\n`;
|
|
719
|
-
users.value.forEach((u) => output.textContent += ` • ${u.fullname} (${u.systemuserid})\n`);
|
|
1150
|
+
users.value.forEach((u) => (output.textContent += ` • ${u.fullname} (${u.systemuserid})\n`));
|
|
720
1151
|
}
|
|
721
|
-
await showNotification(
|
|
722
|
-
log(
|
|
1152
|
+
await showNotification("Parallel Complete", "Fetched accounts, contacts & users", "success");
|
|
1153
|
+
log("Parallel queries finished successfully", "success");
|
|
723
1154
|
}
|
|
724
1155
|
catch (error) {
|
|
725
1156
|
if (output)
|
|
726
1157
|
output.textContent += `Error: ${error.message}\n`;
|
|
727
|
-
log(`Parallel query error: ${error.message}`,
|
|
728
|
-
await showNotification(
|
|
1158
|
+
log(`Parallel query error: ${error.message}`, "error");
|
|
1159
|
+
await showNotification("Parallel Error", error.message, "error");
|
|
729
1160
|
}
|
|
730
1161
|
}
|
|
731
1162
|
/**
|
|
732
1163
|
* Demonstrate showLoading/hideLoading utilities wrapping async work.
|
|
733
1164
|
*/
|
|
734
1165
|
async function demoLoading() {
|
|
735
|
-
const output = document.getElementById(
|
|
1166
|
+
const output = document.getElementById("parallel-output");
|
|
736
1167
|
if (output)
|
|
737
|
-
output.textContent =
|
|
1168
|
+
output.textContent = "Showing loading screen...\n";
|
|
738
1169
|
try {
|
|
739
|
-
await toolbox.utils.showLoading(
|
|
740
|
-
log(
|
|
1170
|
+
await toolbox.utils.showLoading("Processing data...");
|
|
1171
|
+
log("Loading screen displayed", "info");
|
|
741
1172
|
// Simulate async work or perform a lightweight query
|
|
742
1173
|
if (currentConnection) {
|
|
743
1174
|
const fetchXml = `<fetch top="2"><entity name="account"><attribute name="name" /></entity></fetch>`;
|
|
744
1175
|
const result = await dataverse.fetchXmlQuery(fetchXml);
|
|
745
1176
|
if (output) {
|
|
746
1177
|
output.textContent += `Fetched ${result.value.length} account(s) during loading:\n`;
|
|
747
|
-
result.value.forEach((a) => output.textContent += ` • ${a.name}\n`);
|
|
1178
|
+
result.value.forEach((a) => (output.textContent += ` • ${a.name}\n`));
|
|
748
1179
|
}
|
|
749
1180
|
}
|
|
750
1181
|
else {
|
|
751
1182
|
// Fallback simulated delay
|
|
752
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
1183
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
753
1184
|
if (output)
|
|
754
|
-
output.textContent +=
|
|
1185
|
+
output.textContent += "Simulated work (no connection).\n";
|
|
755
1186
|
}
|
|
756
1187
|
}
|
|
757
1188
|
catch (error) {
|
|
758
1189
|
if (output)
|
|
759
1190
|
output.textContent += `Error during loading demo: ${error.message}\n`;
|
|
760
|
-
log(`Loading demo error: ${error.message}`,
|
|
761
|
-
await showNotification(
|
|
1191
|
+
log(`Loading demo error: ${error.message}`, "error");
|
|
1192
|
+
await showNotification("Loading Demo Error", error.message, "error");
|
|
762
1193
|
}
|
|
763
1194
|
finally {
|
|
764
1195
|
await toolbox.utils.hideLoading();
|
|
765
1196
|
if (output)
|
|
766
|
-
output.textContent +=
|
|
767
|
-
log(
|
|
768
|
-
await showNotification(
|
|
1197
|
+
output.textContent += "\nLoading screen hidden.";
|
|
1198
|
+
log("Loading screen hidden", "info");
|
|
1199
|
+
await showNotification("Loading Complete", "Demo finished", "success");
|
|
769
1200
|
}
|
|
770
1201
|
}
|
|
771
1202
|
// Initialize when DOM is ready
|
|
772
|
-
if (document.readyState ===
|
|
773
|
-
document.addEventListener(
|
|
1203
|
+
if (document.readyState === "loading") {
|
|
1204
|
+
document.addEventListener("DOMContentLoaded", initialize);
|
|
774
1205
|
}
|
|
775
1206
|
else {
|
|
776
1207
|
initialize();
|