arcane-os 0.42.2 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/browser-runtime/ai/twin-cloud.mjs +234 -0
- package/docs/reference/ai/twin-cloud.md +124 -0
- package/docs/reference/runtime-components.md +63 -10
- package/docs/reference/runtime-modules.md +64 -0
- package/package.json +2 -2
- package/runtime/arcane/components/data-view.html +136 -51
- package/runtime/arcane/components/file-manager.html +214 -32
- package/runtime/arcane/components/markdown-editor.html +70 -0
- package/runtime/arcane/components/modal.html +8 -4
- package/runtime/arcane/modules/PrintView.js +625 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
87
87
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
88
88
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
89
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
89
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
90
90
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
91
91
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
92
92
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -31,12 +31,16 @@
|
|
|
31
31
|
let content=null;
|
|
32
32
|
let fileManager=null;
|
|
33
33
|
let operationGeneration=0;
|
|
34
|
+
let openController = null;
|
|
35
|
+
let closingTask = null;
|
|
34
36
|
let destroyed=false;
|
|
35
37
|
|
|
36
38
|
host.beforeOpen=is.function(host.beforeOpen)
|
|
37
39
|
?host.beforeOpen
|
|
38
40
|
:async function beforeOpen(){};
|
|
39
41
|
host.open=openData;
|
|
42
|
+
host.close = closeData;
|
|
43
|
+
host.printPreview=printPreview;
|
|
40
44
|
host.destroy=destroy;
|
|
41
45
|
|
|
42
46
|
openButton.innerText=host.dataset.button||'View Data';
|
|
@@ -52,80 +56,159 @@
|
|
|
52
56
|
{signal:lifecycleController.signal}
|
|
53
57
|
);
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
dataModal.addEventListener(
|
|
60
|
+
'modal-closed',
|
|
61
|
+
function closeDataFromModal(event) {
|
|
62
|
+
if (event.target === dataModal && !closingTask) {
|
|
63
|
+
closeData();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{signal: lifecycleController.signal}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
async function openData() {
|
|
70
|
+
if (destroyed) {
|
|
57
71
|
return false;
|
|
58
72
|
}
|
|
59
|
-
const
|
|
60
|
-
await
|
|
61
|
-
if(destroyed||generation!==operationGeneration){
|
|
73
|
+
const requestedGeneration=operationGeneration;
|
|
74
|
+
if (closingTask && !await closingTask) {
|
|
62
75
|
return false;
|
|
63
76
|
}
|
|
64
|
-
|
|
65
|
-
const reload=Boolean(content);
|
|
66
|
-
|
|
67
|
-
const modal=await waitForComponent(
|
|
68
|
-
dataModal,
|
|
69
|
-
{
|
|
70
|
-
methods:['populate','open'],
|
|
71
|
-
property:'ready',
|
|
72
|
-
event:'modal-ready',
|
|
73
|
-
signal:lifecycleController.signal
|
|
74
|
-
}
|
|
75
|
-
);
|
|
76
|
-
if(destroyed||generation!==operationGeneration){
|
|
77
|
+
if (destroyed||requestedGeneration!==operationGeneration) {
|
|
77
78
|
return false;
|
|
78
79
|
}
|
|
80
|
+
const generation = ++operationGeneration;
|
|
81
|
+
openController?.abort('data-view-open-replaced');
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
openController = controller;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await host.beforeOpen();
|
|
87
|
+
if (destroyed || generation !== operationGeneration) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
const reload = Boolean(content);
|
|
92
|
+
const modal = await waitForComponent(
|
|
93
|
+
dataModal,
|
|
94
|
+
{
|
|
95
|
+
methods: ['populate', 'open'],
|
|
96
|
+
property: 'ready',
|
|
97
|
+
event: 'modal-ready',
|
|
98
|
+
signal: controller.signal
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
if (destroyed || generation !== operationGeneration) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
if (!content) {
|
|
106
|
+
content = document.createElement('section');
|
|
107
|
+
fileManager = document.createElement('html-import');
|
|
108
|
+
|
|
109
|
+
const heading = document.createElement('h1');
|
|
110
|
+
|
|
111
|
+
heading.innerText = host.dataset.title || 'Data';
|
|
112
|
+
fileManager.className = 'file-manager';
|
|
113
|
+
forwardPreviewOptions();
|
|
114
|
+
fileManager.dataset.dirs = host.dataset.dirs || '';
|
|
115
|
+
fileManager.dataset.layout = host.dataset.layout || 'files';
|
|
116
|
+
fileManager.setAttribute(
|
|
117
|
+
'href',
|
|
118
|
+
new URL('./file-manager.html', componentHref).href
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
content.append(heading, fileManager);
|
|
122
|
+
await modal.populate(content, false);
|
|
123
|
+
if (destroyed || generation !== operationGeneration) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
if (reload) {
|
|
129
|
+
forwardPreviewOptions();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const manager = await waitForComponent(
|
|
133
|
+
fileManager,
|
|
134
|
+
{
|
|
135
|
+
methods: ['loadAll'],
|
|
136
|
+
property: 'ready',
|
|
137
|
+
event: 'file-manager-ready',
|
|
138
|
+
signal: controller.signal
|
|
139
|
+
}
|
|
94
140
|
);
|
|
141
|
+
if (destroyed || generation !== operationGeneration) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
95
144
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
145
|
+
if (reload) {
|
|
146
|
+
await manager.loadAll();
|
|
147
|
+
if (destroyed || generation !== operationGeneration) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return await modal.open();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (controller.signal.aborted) {
|
|
99
155
|
return false;
|
|
100
156
|
}
|
|
157
|
+
throw error;
|
|
158
|
+
} finally {
|
|
159
|
+
if (openController === controller) {
|
|
160
|
+
openController = null;
|
|
161
|
+
}
|
|
101
162
|
}
|
|
163
|
+
}
|
|
102
164
|
|
|
103
|
-
|
|
104
|
-
|
|
165
|
+
async function closeData() {
|
|
166
|
+
if (destroyed) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
operationGeneration += 1;
|
|
170
|
+
openController?.abort('data-view-closed');
|
|
171
|
+
openController = null;
|
|
172
|
+
if (closingTask) {
|
|
173
|
+
return closingTask;
|
|
105
174
|
}
|
|
106
175
|
|
|
107
|
-
const
|
|
108
|
-
fileManager
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
176
|
+
const childClosing = is.function(fileManager?.close)
|
|
177
|
+
? fileManager.close()
|
|
178
|
+
: Promise.resolve(true);
|
|
179
|
+
closingTask = Promise.resolve(childClosing)
|
|
180
|
+
.then(closeDataAfterChildren)
|
|
181
|
+
.catch(
|
|
182
|
+
function reportDataCloseFailure(error) {
|
|
183
|
+
arcaneLogging.error('Unable to close data view:', error);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
try {
|
|
188
|
+
return await closingTask;
|
|
189
|
+
} finally {
|
|
190
|
+
closingTask = null;
|
|
118
191
|
}
|
|
192
|
+
}
|
|
119
193
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
194
|
+
async function closeDataAfterChildren(childrenClosed) {
|
|
195
|
+
if (childrenClosed === false || destroyed) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (is.function(dataModal.close)) {
|
|
199
|
+
const closed = await dataModal.close();
|
|
200
|
+
if (closed === false) {
|
|
123
201
|
return false;
|
|
124
202
|
}
|
|
125
203
|
}
|
|
204
|
+
return !destroyed && dataModal.opened !== true;
|
|
205
|
+
}
|
|
126
206
|
|
|
127
|
-
|
|
128
|
-
|
|
207
|
+
async function printPreview(){
|
|
208
|
+
if(destroyed||dataModal.opened!==true||!is.function(fileManager?.printPreview)){
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return fileManager.printPreview();
|
|
129
212
|
}
|
|
130
213
|
|
|
131
214
|
function forwardPreviewOptions(){
|
|
@@ -146,6 +229,8 @@
|
|
|
146
229
|
}
|
|
147
230
|
destroyed=true;
|
|
148
231
|
operationGeneration+=1;
|
|
232
|
+
openController?.abort('data-view-destroyed');
|
|
233
|
+
openController = null;
|
|
149
234
|
lifecycleController.abort('data-view-destroyed');
|
|
150
235
|
fileManager?.destroy?.();
|
|
151
236
|
dataModal.destroy?.();
|
|
@@ -469,8 +469,15 @@
|
|
|
469
469
|
directory=>directory.trim()
|
|
470
470
|
).filter(Boolean);
|
|
471
471
|
const fileEntityPromise=import('../entities/File.js');
|
|
472
|
+
const printModulePromise=import('../modules/PrintView.js');
|
|
473
|
+
void printModulePromise.catch(function reportPrintModuleFailure(error){
|
|
474
|
+
arcaneLogging.error('Unable to load rendered printing:',error);
|
|
475
|
+
});
|
|
472
476
|
|
|
473
477
|
let activePreview=null;
|
|
478
|
+
let presentationController = new AbortController();
|
|
479
|
+
let uploadPresentationController = null;
|
|
480
|
+
let closingTask = null;
|
|
474
481
|
let markdownModulePromise=null;
|
|
475
482
|
let uploadDirectory='';
|
|
476
483
|
let initialized=false;
|
|
@@ -497,6 +504,8 @@
|
|
|
497
504
|
host.setDirectoryFilter=setDirectoryFilter;
|
|
498
505
|
host.select=select;
|
|
499
506
|
host.clearSelection=clearSelection;
|
|
507
|
+
host.close = closeFileManager;
|
|
508
|
+
host.printPreview=printPreview;
|
|
500
509
|
host.destroy=destroy;
|
|
501
510
|
|
|
502
511
|
Object.defineProperty(
|
|
@@ -1457,22 +1466,20 @@
|
|
|
1457
1466
|
}
|
|
1458
1467
|
}
|
|
1459
1468
|
|
|
1460
|
-
async function openDirectory(directory=''){
|
|
1461
|
-
if(
|
|
1469
|
+
async function openDirectory(directory = '', signal = presentationController.signal) {
|
|
1470
|
+
if (closingTask && !await closingTask) {
|
|
1471
|
+
return false;
|
|
1472
|
+
}
|
|
1473
|
+
if(destroyed||signal.aborted||!isVisibleDirectory(directory)){
|
|
1462
1474
|
return false;
|
|
1463
1475
|
}
|
|
1464
1476
|
const generation=++directoryOpenGeneration;
|
|
1465
|
-
const modal=await
|
|
1477
|
+
const modal = await waitForModal(
|
|
1466
1478
|
directoryModal,
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
property:'ready',
|
|
1470
|
-
event:'modal-ready',
|
|
1471
|
-
signal:lifecycleController.signal
|
|
1472
|
-
},
|
|
1473
|
-
{signal:lifecycleController.signal}
|
|
1479
|
+
['populate', 'open'],
|
|
1480
|
+
signal
|
|
1474
1481
|
);
|
|
1475
|
-
if(destroyed||generation!==directoryOpenGeneration||!isVisibleDirectory(directory)){
|
|
1482
|
+
if(!modal||destroyed||generation!==directoryOpenGeneration||!isVisibleDirectory(directory)){
|
|
1476
1483
|
return false;
|
|
1477
1484
|
}
|
|
1478
1485
|
const keys=await dbopfs.getAllKeys(directory);
|
|
@@ -1534,7 +1541,11 @@
|
|
|
1534
1541
|
}
|
|
1535
1542
|
|
|
1536
1543
|
async function openFile(directory='',fileName=''){
|
|
1537
|
-
|
|
1544
|
+
const signal = presentationController.signal;
|
|
1545
|
+
if (closingTask && !await closingTask) {
|
|
1546
|
+
return false;
|
|
1547
|
+
}
|
|
1548
|
+
if(destroyed||signal.aborted){
|
|
1538
1549
|
return false;
|
|
1539
1550
|
}
|
|
1540
1551
|
releasePreview(activePreview,'file-preview-replaced');
|
|
@@ -1543,7 +1554,12 @@
|
|
|
1543
1554
|
controller:new AbortController(),
|
|
1544
1555
|
generation,
|
|
1545
1556
|
urls:new Set(),
|
|
1546
|
-
media:new Set()
|
|
1557
|
+
media:new Set(),
|
|
1558
|
+
title:`${directory}/${fileName}`,
|
|
1559
|
+
rendered:null,
|
|
1560
|
+
printPreparation:null,
|
|
1561
|
+
printRetains:0,
|
|
1562
|
+
released:false
|
|
1547
1563
|
};
|
|
1548
1564
|
activePreview=preview;
|
|
1549
1565
|
let modal;
|
|
@@ -1577,8 +1593,20 @@
|
|
|
1577
1593
|
const deleteButton=document.createElement('button');
|
|
1578
1594
|
const deleteIcon=document.createElement('img');
|
|
1579
1595
|
const status=document.createElement('p');
|
|
1596
|
+
const printButton=document.createElement('button');
|
|
1580
1597
|
|
|
1581
1598
|
heading.innerText=`${directory}/${fileName}`;
|
|
1599
|
+
printButton.type='button';
|
|
1600
|
+
printButton.textContent='Print';
|
|
1601
|
+
printButton.disabled=true;
|
|
1602
|
+
preview.printButton=printButton;
|
|
1603
|
+
preview.printStatus=status;
|
|
1604
|
+
preview.container=content;
|
|
1605
|
+
printButton.addEventListener('click',function printOpenPreview(){
|
|
1606
|
+
void printPreview().catch(function reportPrintRejection(error){
|
|
1607
|
+
reportPreviewPrintFailure(error,preview);
|
|
1608
|
+
});
|
|
1609
|
+
},{signal:preview.controller.signal});
|
|
1582
1610
|
actions.className='file-view-actions';
|
|
1583
1611
|
deleteButton.type='button';
|
|
1584
1612
|
deleteButton.className='file-view-delete';
|
|
@@ -1598,7 +1626,7 @@
|
|
|
1598
1626
|
};
|
|
1599
1627
|
status.innerText='Opening file...';
|
|
1600
1628
|
status.setAttribute('role','status');
|
|
1601
|
-
actions.append(deleteButton);
|
|
1629
|
+
actions.append(printButton,deleteButton);
|
|
1602
1630
|
content.append(
|
|
1603
1631
|
styles,
|
|
1604
1632
|
heading,
|
|
@@ -1631,6 +1659,10 @@
|
|
|
1631
1659
|
}
|
|
1632
1660
|
status.remove();
|
|
1633
1661
|
content.append(rendered);
|
|
1662
|
+
preview.rendered=rendered;
|
|
1663
|
+
void preparePreviewPrint(preview).catch(function reportPrintPreparation(error){
|
|
1664
|
+
reportPreviewPrintFailure(error,preview);
|
|
1665
|
+
});
|
|
1634
1666
|
return true;
|
|
1635
1667
|
}catch(error){
|
|
1636
1668
|
if(isCurrentPreview(preview)){
|
|
@@ -1643,6 +1675,69 @@
|
|
|
1643
1675
|
}
|
|
1644
1676
|
}
|
|
1645
1677
|
|
|
1678
|
+
function preparePreviewPrint(preview){
|
|
1679
|
+
if(!preview.printPreparation){
|
|
1680
|
+
preview.printPreparation=printModulePromise.then(function registerPreviewPrint({createPrintView}){
|
|
1681
|
+
if(!isCurrentPreview(preview)||!preview.rendered){
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
const view=createPrintView({
|
|
1685
|
+
host,
|
|
1686
|
+
content:()=>preview.rendered,
|
|
1687
|
+
title:()=>preview.title,
|
|
1688
|
+
active:()=>isCurrentPreview(preview)&&fileModal.opened===true,
|
|
1689
|
+
priority:1,
|
|
1690
|
+
signal:preview.controller.signal,
|
|
1691
|
+
retain:()=>retainPreviewPrintResources(preview),
|
|
1692
|
+
onError:error=>reportPreviewPrintFailure(error,preview)
|
|
1693
|
+
});
|
|
1694
|
+
preview.printButton.disabled=false;
|
|
1695
|
+
return view;
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
return preview.printPreparation;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
async function printPreview(){
|
|
1702
|
+
const preview=activePreview;
|
|
1703
|
+
if(!preview?.rendered||!isCurrentPreview(preview)||fileModal.opened!==true){
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
const view=await preparePreviewPrint(preview);
|
|
1707
|
+
return view&&isCurrentPreview(preview)?view.print():false;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function reportPreviewPrintFailure(error,preview){
|
|
1711
|
+
arcaneLogging.error('Unable to print file preview:',error);
|
|
1712
|
+
if(isCurrentPreview(preview)){
|
|
1713
|
+
preview.printStatus.textContent='Unable to open print preview. Please try again.';
|
|
1714
|
+
preview.printStatus.setAttribute('role','alert');
|
|
1715
|
+
preview.container.append(preview.printStatus);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function retainPreviewPrintResources(preview){
|
|
1720
|
+
preview.printRetains+=1;
|
|
1721
|
+
let retained=true;
|
|
1722
|
+
return function releasePrintedPreviewResources(){
|
|
1723
|
+
if(!retained){
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
retained=false;
|
|
1727
|
+
preview.printRetains-=1;
|
|
1728
|
+
if(preview.released&&preview.printRetains===0){
|
|
1729
|
+
releasePreviewURLs(preview);
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function releasePreviewURLs(preview){
|
|
1735
|
+
for(const url of preview.urls){
|
|
1736
|
+
URL.revokeObjectURL(url);
|
|
1737
|
+
}
|
|
1738
|
+
preview.urls.clear();
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1646
1741
|
function configureFileModal(modal){
|
|
1647
1742
|
const appearance=getComputedStyle(host);
|
|
1648
1743
|
for(const property of ['--modal-width','--modal-max-height','--modal-padding']){
|
|
@@ -1660,9 +1755,10 @@
|
|
|
1660
1755
|
}
|
|
1661
1756
|
|
|
1662
1757
|
function releasePreview(preview,reason){
|
|
1663
|
-
if(!preview){
|
|
1758
|
+
if(!preview||preview.released){
|
|
1664
1759
|
return;
|
|
1665
1760
|
}
|
|
1761
|
+
preview.released=true;
|
|
1666
1762
|
preview.controller.abort(reason);
|
|
1667
1763
|
for(const element of preview.media){
|
|
1668
1764
|
if(element.localName==='audio'){
|
|
@@ -1676,11 +1772,10 @@
|
|
|
1676
1772
|
element.load();
|
|
1677
1773
|
}
|
|
1678
1774
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1775
|
+
if(preview.printRetains===0){
|
|
1776
|
+
releasePreviewURLs(preview);
|
|
1681
1777
|
}
|
|
1682
1778
|
preview.media.clear();
|
|
1683
|
-
preview.urls.clear();
|
|
1684
1779
|
if(activePreview===preview){
|
|
1685
1780
|
activePreview=null;
|
|
1686
1781
|
}
|
|
@@ -2091,8 +2186,12 @@
|
|
|
2091
2186
|
|
|
2092
2187
|
openButton.addEventListener(
|
|
2093
2188
|
'click',
|
|
2094
|
-
async()
|
|
2189
|
+
async function openSelectedFile() {
|
|
2190
|
+
const signal = presentationController.signal;
|
|
2095
2191
|
await select(entry.path,{entry:entry});
|
|
2192
|
+
if (destroyed || signal.aborted) {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2096
2195
|
const detail={path:entry.path,entry:entry,trigger:'click'};
|
|
2097
2196
|
const operationId=nextOperationId('open');
|
|
2098
2197
|
const openAllowed=emit('file-manager-open',detail,true,operationId);
|
|
@@ -2130,21 +2229,21 @@
|
|
|
2130
2229
|
}
|
|
2131
2230
|
|
|
2132
2231
|
async function confirmDelete(directory='',fileName='',closeFile=false){
|
|
2133
|
-
|
|
2232
|
+
const signal = presentationController.signal;
|
|
2233
|
+
if (closingTask && !await closingTask) {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
if(destroyed||signal.aborted||!directory||!fileName){
|
|
2134
2237
|
return false;
|
|
2135
2238
|
}
|
|
2136
2239
|
const generation=++deleteGeneration;
|
|
2137
2240
|
|
|
2138
|
-
const modal=await
|
|
2241
|
+
const modal = await waitForModal(
|
|
2139
2242
|
deleteModal,
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
property:'ready',
|
|
2143
|
-
event:'modal-ready',
|
|
2144
|
-
signal:lifecycleController.signal
|
|
2145
|
-
}
|
|
2243
|
+
['populate', 'open', 'close'],
|
|
2244
|
+
signal
|
|
2146
2245
|
);
|
|
2147
|
-
if(destroyed||generation!==deleteGeneration){
|
|
2246
|
+
if(!modal||destroyed||generation!==deleteGeneration){
|
|
2148
2247
|
return false;
|
|
2149
2248
|
}
|
|
2150
2249
|
const content=document.createElement('section');
|
|
@@ -2168,12 +2267,19 @@
|
|
|
2168
2267
|
deleteButton.addEventListener(
|
|
2169
2268
|
'click',
|
|
2170
2269
|
async function deleteSelectedFile(){
|
|
2270
|
+
if (destroyed || signal.aborted || generation !== deleteGeneration) {
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2171
2273
|
cancelButton.disabled=true;
|
|
2172
2274
|
deleteButton.disabled=true;
|
|
2173
2275
|
|
|
2174
2276
|
try{
|
|
2175
2277
|
await dbopfs.delete(directory,fileName);
|
|
2176
|
-
if(destroyed
|
|
2278
|
+
if(destroyed){
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
if (generation !== deleteGeneration) {
|
|
2282
|
+
await loadAll();
|
|
2177
2283
|
return;
|
|
2178
2284
|
}
|
|
2179
2285
|
await modal.close(undefined,true);
|
|
@@ -2201,7 +2307,9 @@
|
|
|
2201
2307
|
message.innerText=`Unable to delete ${fileName}.`;
|
|
2202
2308
|
cancelButton.disabled=false;
|
|
2203
2309
|
deleteButton.disabled=false;
|
|
2204
|
-
|
|
2310
|
+
}
|
|
2311
|
+
if (!destroyed) {
|
|
2312
|
+
arcaneLogging.error('Unable to delete file:', error);
|
|
2205
2313
|
}
|
|
2206
2314
|
}
|
|
2207
2315
|
},
|
|
@@ -2230,6 +2338,7 @@
|
|
|
2230
2338
|
}
|
|
2231
2339
|
|
|
2232
2340
|
uploadDirectory=directory;
|
|
2341
|
+
uploadPresentationController = presentationController;
|
|
2233
2342
|
fileUpload.click();
|
|
2234
2343
|
}
|
|
2235
2344
|
|
|
@@ -2239,6 +2348,8 @@
|
|
|
2239
2348
|
}
|
|
2240
2349
|
const files=Array.from(fileUpload.files||[]);
|
|
2241
2350
|
const directory=uploadDirectory;
|
|
2351
|
+
const presentation = uploadPresentationController || presentationController;
|
|
2352
|
+
uploadPresentationController = null;
|
|
2242
2353
|
fileUpload.value='';
|
|
2243
2354
|
|
|
2244
2355
|
if(!files.length||!directory){
|
|
@@ -2269,8 +2380,8 @@
|
|
|
2269
2380
|
return false;
|
|
2270
2381
|
}
|
|
2271
2382
|
|
|
2272
|
-
if(layout==='grid'){
|
|
2273
|
-
await openDirectory(directory);
|
|
2383
|
+
if(layout==='grid' && !presentation.signal.aborted){
|
|
2384
|
+
await openDirectory(directory, presentation.signal);
|
|
2274
2385
|
}
|
|
2275
2386
|
|
|
2276
2387
|
const failed=results.find(
|
|
@@ -2309,6 +2420,76 @@
|
|
|
2309
2420
|
}
|
|
2310
2421
|
}
|
|
2311
2422
|
|
|
2423
|
+
async function waitForModal(modal, methods, signal) {
|
|
2424
|
+
try {
|
|
2425
|
+
return await waitForComponent(
|
|
2426
|
+
modal,
|
|
2427
|
+
{
|
|
2428
|
+
methods,
|
|
2429
|
+
property: 'ready',
|
|
2430
|
+
event: 'modal-ready',
|
|
2431
|
+
signal
|
|
2432
|
+
}
|
|
2433
|
+
);
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
if (signal.aborted) {
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
2438
|
+
throw error;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
async function closeFileManager() {
|
|
2443
|
+
if (destroyed) {
|
|
2444
|
+
return false;
|
|
2445
|
+
}
|
|
2446
|
+
presentationController.abort('file-manager-closed');
|
|
2447
|
+
presentationController = new AbortController();
|
|
2448
|
+
treeOpenGeneration += 1;
|
|
2449
|
+
directoryOpenGeneration += 1;
|
|
2450
|
+
deleteGeneration += 1;
|
|
2451
|
+
selectionGeneration += 1;
|
|
2452
|
+
// Cancel unfinished opens now; a settled preview remains usable until
|
|
2453
|
+
// its modal actually closes, including when a child refuses to close.
|
|
2454
|
+
if (!activePreview?.rendered) {
|
|
2455
|
+
fileOpenGeneration += 1;
|
|
2456
|
+
releasePreview(activePreview, 'file-manager-closed');
|
|
2457
|
+
}
|
|
2458
|
+
if (closingTask) {
|
|
2459
|
+
return closingTask;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
closingTask = Promise.resolve().then(closeOwnedDialogs);
|
|
2463
|
+
try {
|
|
2464
|
+
return await closingTask;
|
|
2465
|
+
} finally {
|
|
2466
|
+
closingTask = null;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
async function closeOwnedDialogs() {
|
|
2471
|
+
try {
|
|
2472
|
+
for (const modal of [deleteModal, fileModal, directoryModal]) {
|
|
2473
|
+
if (destroyed) {
|
|
2474
|
+
return false;
|
|
2475
|
+
}
|
|
2476
|
+
if (is.function(modal.close)) {
|
|
2477
|
+
const closed = await modal.close();
|
|
2478
|
+
if (closed === false) {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
if (modal.opened === true) {
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return !destroyed;
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
arcaneLogging.error('Unable to close file manager:', error);
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2312
2493
|
function destroy(){
|
|
2313
2494
|
if(destroyed){
|
|
2314
2495
|
return false;
|
|
@@ -2317,6 +2498,7 @@
|
|
|
2317
2498
|
initialized=false;
|
|
2318
2499
|
host.ready=false;
|
|
2319
2500
|
lifecycleController.abort('file-manager-destroyed');
|
|
2501
|
+
presentationController.abort('file-manager-destroyed');
|
|
2320
2502
|
treeLoadSequence+=1;
|
|
2321
2503
|
gridLoadSequence+=1;
|
|
2322
2504
|
providerGeneration+=1;
|