@heyputer/puter.js 2.1.1 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/puter.cjs +2 -2
- package/index.d.ts +102 -4
- package/package.json +1 -1
- package/src/modules/AI.js +340 -28
- package/src/modules/Auth.js +38 -0
- package/src/modules/UI.js +417 -389
package/src/modules/UI.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import putility from '@heyputer/putility';
|
|
2
|
+
import EventListener from '../lib/EventListener.js';
|
|
1
3
|
import FSItem from './FSItem.js';
|
|
2
4
|
import PuterDialog from './PuterDialog.js';
|
|
3
|
-
import EventListener from '../lib/EventListener.js';
|
|
4
|
-
import putility from '@heyputer/putility';
|
|
5
5
|
|
|
6
6
|
const FILE_SAVE_CANCELLED = Symbol('FILE_SAVE_CANCELLED');
|
|
7
7
|
const FILE_OPEN_CANCELLED = Symbol('FILE_OPEN_CANCELLED');
|
|
@@ -21,8 +21,8 @@ class AppConnection extends EventListener {
|
|
|
21
21
|
// Whether the target app uses the Puter SDK, and so accepts messages
|
|
22
22
|
// (Closing and close events will still function.)
|
|
23
23
|
#usesSDK;
|
|
24
|
-
|
|
25
|
-
static from
|
|
24
|
+
|
|
25
|
+
static from(values, context) {
|
|
26
26
|
const connection = new AppConnection(context, {
|
|
27
27
|
target: values.appInstanceID,
|
|
28
28
|
usesSDK: values.usesSDK,
|
|
@@ -58,13 +58,13 @@ class AppConnection extends EventListener {
|
|
|
58
58
|
// TODO: Set this.#puterOrigin to the puter origin
|
|
59
59
|
|
|
60
60
|
(globalThis.document) && window.addEventListener('message', event => {
|
|
61
|
-
if (event.data.msg === 'messageToApp') {
|
|
62
|
-
if (event.data.appInstanceID !== this.targetAppInstanceID) {
|
|
61
|
+
if ( event.data.msg === 'messageToApp' ) {
|
|
62
|
+
if ( event.data.appInstanceID !== this.targetAppInstanceID ) {
|
|
63
63
|
// Message is from a different AppConnection; ignore it.
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
// TODO: does this check really make sense?
|
|
67
|
-
if (event.data.targetAppInstanceID !== this.appInstanceID) {
|
|
67
|
+
if ( event.data.targetAppInstanceID !== this.appInstanceID ) {
|
|
68
68
|
console.error(`AppConnection received message intended for wrong app! appInstanceID=${this.appInstanceID}, target=${event.data.targetAppInstanceID}`);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
@@ -72,8 +72,8 @@ class AppConnection extends EventListener {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
if (event.data.msg === 'appClosed') {
|
|
76
|
-
if (event.data.appInstanceID !== this.targetAppInstanceID) {
|
|
75
|
+
if ( event.data.msg === 'appClosed' ) {
|
|
76
|
+
if ( event.data.appInstanceID !== this.targetAppInstanceID ) {
|
|
77
77
|
// Message is from a different AppConnection; ignore it.
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
@@ -88,16 +88,18 @@ class AppConnection extends EventListener {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Does the target app use the Puter SDK? If not, certain features will be unavailable.
|
|
91
|
-
get usesSDK() {
|
|
91
|
+
get usesSDK() {
|
|
92
|
+
return this.#usesSDK;
|
|
93
|
+
}
|
|
92
94
|
|
|
93
95
|
// Send a message to the target app. Requires the target to use the Puter SDK.
|
|
94
96
|
postMessage(message) {
|
|
95
|
-
if (!this.#isOpen) {
|
|
97
|
+
if ( !this.#isOpen ) {
|
|
96
98
|
console.warn('Trying to post message on a closed AppConnection');
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
if (!this.#usesSDK) {
|
|
102
|
+
if ( !this.#usesSDK ) {
|
|
101
103
|
console.warn('Trying to post message to a non-SDK app');
|
|
102
104
|
return;
|
|
103
105
|
}
|
|
@@ -116,7 +118,7 @@ class AppConnection extends EventListener {
|
|
|
116
118
|
|
|
117
119
|
// Attempt to close the target application
|
|
118
120
|
close() {
|
|
119
|
-
if (!this.#isOpen) {
|
|
121
|
+
if ( !this.#isOpen ) {
|
|
120
122
|
console.warn('Trying to close an app on a closed AppConnection');
|
|
121
123
|
return;
|
|
122
124
|
}
|
|
@@ -134,7 +136,7 @@ class UI extends EventListener {
|
|
|
134
136
|
// we start from 1 because 0 is falsy and we want to avoid that for the message id
|
|
135
137
|
#messageID = 1;
|
|
136
138
|
|
|
137
|
-
// Holds the callback functions for the various events
|
|
139
|
+
// Holds the callback functions for the various events
|
|
138
140
|
// that are triggered when a watched item has changed.
|
|
139
141
|
itemWatchCallbackFunctions = [];
|
|
140
142
|
|
|
@@ -147,15 +149,15 @@ class UI extends EventListener {
|
|
|
147
149
|
// If we have a parent app, holds an AppConnection to it
|
|
148
150
|
#parentAppConnection = null;
|
|
149
151
|
|
|
150
|
-
// Holds the callback functions for the various events
|
|
152
|
+
// Holds the callback functions for the various events
|
|
151
153
|
// that can be triggered by the host environment's messages.
|
|
152
154
|
#callbackFunctions = [];
|
|
153
155
|
|
|
154
|
-
// onWindowClose() is executed right before the window is closed. Users can override this function
|
|
156
|
+
// onWindowClose() is executed right before the window is closed. Users can override this function
|
|
155
157
|
// to perform a variety of tasks right before window is closed. Users can override this function.
|
|
156
158
|
#onWindowClose;
|
|
157
159
|
|
|
158
|
-
// When an item is opened by this app in any way onItemsOpened() is executed. Users can override this function.
|
|
160
|
+
// When an item is opened by this app in any way onItemsOpened() is executed. Users can override this function.
|
|
159
161
|
#onItemsOpened;
|
|
160
162
|
|
|
161
163
|
#onLaunchedWithItems;
|
|
@@ -170,7 +172,7 @@ class UI extends EventListener {
|
|
|
170
172
|
#overlayTimer = null;
|
|
171
173
|
|
|
172
174
|
// Replaces boilerplate for most methods: posts a message to the GUI with a unique ID, and sets a callback for it.
|
|
173
|
-
#postMessageWithCallback
|
|
175
|
+
#postMessageWithCallback(name, resolve, args = {}) {
|
|
174
176
|
const msg_id = this.#messageID++;
|
|
175
177
|
this.messageTarget?.postMessage({
|
|
176
178
|
msg: name,
|
|
@@ -183,9 +185,9 @@ class UI extends EventListener {
|
|
|
183
185
|
this.#callbackFunctions[msg_id] = resolve;
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
#postMessageWithObject
|
|
188
|
+
#postMessageWithObject(name, value) {
|
|
187
189
|
const dehydrator = this.util.rpc.getDehydrator({
|
|
188
|
-
target: this.messageTarget
|
|
190
|
+
target: this.messageTarget,
|
|
189
191
|
});
|
|
190
192
|
this.messageTarget?.postMessage({
|
|
191
193
|
msg: name,
|
|
@@ -193,9 +195,9 @@ class UI extends EventListener {
|
|
|
193
195
|
appInstanceID: this.appInstanceID,
|
|
194
196
|
value: dehydrator.dehydrate(value),
|
|
195
197
|
}, '*');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
#ipc_stub
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
async #ipc_stub({
|
|
199
201
|
callback,
|
|
200
202
|
method,
|
|
201
203
|
parameters,
|
|
@@ -207,10 +209,10 @@ class UI extends EventListener {
|
|
|
207
209
|
done_setting_resolve();
|
|
208
210
|
});
|
|
209
211
|
});
|
|
210
|
-
if ( ! resolve ) debugger;
|
|
211
212
|
const callback_id = this.util.rpc.registerCallback(resolve);
|
|
212
213
|
this.messageTarget?.postMessage({
|
|
213
|
-
$: 'puter-ipc',
|
|
214
|
+
$: 'puter-ipc',
|
|
215
|
+
v: 2,
|
|
214
216
|
appInstanceID: this.appInstanceID,
|
|
215
217
|
env: this.env,
|
|
216
218
|
msg: method,
|
|
@@ -220,9 +222,9 @@ class UI extends EventListener {
|
|
|
220
222
|
const ret = await p;
|
|
221
223
|
if ( callback ) callback(ret);
|
|
222
224
|
return ret;
|
|
223
|
-
}
|
|
225
|
+
};
|
|
224
226
|
|
|
225
|
-
constructor
|
|
227
|
+
constructor(context, { appInstanceID, parentInstanceID }) {
|
|
226
228
|
const eventNames = [
|
|
227
229
|
'localeChanged',
|
|
228
230
|
'themeChanged',
|
|
@@ -237,10 +239,10 @@ class UI extends EventListener {
|
|
|
237
239
|
this.env = context.env;
|
|
238
240
|
this.util = context.util;
|
|
239
241
|
|
|
240
|
-
if(this.env === 'app'){
|
|
242
|
+
if ( this.env === 'app' ){
|
|
241
243
|
this.messageTarget = window.parent;
|
|
242
244
|
}
|
|
243
|
-
else if(this.env === 'gui'){
|
|
245
|
+
else if ( this.env === 'gui' ){
|
|
244
246
|
return;
|
|
245
247
|
}
|
|
246
248
|
|
|
@@ -250,24 +252,24 @@ class UI extends EventListener {
|
|
|
250
252
|
messageTarget: this.messageTarget,
|
|
251
253
|
});
|
|
252
254
|
|
|
253
|
-
if (this.parentInstanceID) {
|
|
255
|
+
if ( this.parentInstanceID ) {
|
|
254
256
|
this.#parentAppConnection = new AppConnection(this.context, {
|
|
255
257
|
target: this.parentInstanceID,
|
|
256
|
-
usesSDK: true
|
|
258
|
+
usesSDK: true,
|
|
257
259
|
});
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
// Tell the host environment that this app is using the Puter SDK and is ready to receive messages,
|
|
261
263
|
// this will allow the OS to send custom messages to the app
|
|
262
264
|
this.messageTarget?.postMessage({
|
|
263
|
-
msg:
|
|
265
|
+
msg: 'READY',
|
|
264
266
|
appInstanceID: this.appInstanceID,
|
|
265
267
|
}, '*');
|
|
266
268
|
|
|
267
269
|
// When this app's window is focused send a message to the host environment
|
|
268
270
|
(globalThis.document) && window.addEventListener('focus', (e) => {
|
|
269
271
|
this.messageTarget?.postMessage({
|
|
270
|
-
msg:
|
|
272
|
+
msg: 'windowFocused',
|
|
271
273
|
appInstanceID: this.appInstanceID,
|
|
272
274
|
}, '*');
|
|
273
275
|
});
|
|
@@ -275,44 +277,46 @@ class UI extends EventListener {
|
|
|
275
277
|
// Bind the message event listener to the window
|
|
276
278
|
let lastDraggedOverElement = null;
|
|
277
279
|
(globalThis.document) && window.addEventListener('message', async (e) => {
|
|
278
|
-
if (!e.data) return;
|
|
280
|
+
if ( !e.data ) return;
|
|
279
281
|
// `error`
|
|
280
|
-
if(e.data.error){
|
|
282
|
+
if ( e.data.error ){
|
|
281
283
|
throw e.data.error;
|
|
282
284
|
}
|
|
283
285
|
// `focus` event
|
|
284
|
-
else if(e.data.msg && e.data.msg === 'focus'){
|
|
286
|
+
else if ( e.data.msg && e.data.msg === 'focus' ){
|
|
285
287
|
window.focus();
|
|
286
288
|
}
|
|
287
289
|
// `click` event
|
|
288
|
-
else if(e.data.msg && e.data.msg === 'click'){
|
|
290
|
+
else if ( e.data.msg && e.data.msg === 'click' ){
|
|
289
291
|
// Get the element that was clicked on and click it
|
|
290
292
|
const clicked_el = document.elementFromPoint(e.data.x, e.data.y);
|
|
291
|
-
if(clicked_el !== null)
|
|
293
|
+
if ( clicked_el !== null )
|
|
294
|
+
{
|
|
292
295
|
clicked_el.click();
|
|
296
|
+
}
|
|
293
297
|
}
|
|
294
298
|
// `dragover` event based on the `drag` event from the host environment
|
|
295
|
-
else if(e.data.msg && e.data.msg === 'drag'){
|
|
299
|
+
else if ( e.data.msg && e.data.msg === 'drag' ){
|
|
296
300
|
// Get the element being dragged over
|
|
297
301
|
const draggedOverElement = document.elementFromPoint(e.data.x, e.data.y);
|
|
298
|
-
if(draggedOverElement !== lastDraggedOverElement){
|
|
302
|
+
if ( draggedOverElement !== lastDraggedOverElement ){
|
|
299
303
|
// If the last element exists and is different from the current, dispatch a dragleave on it
|
|
300
|
-
if(lastDraggedOverElement){
|
|
304
|
+
if ( lastDraggedOverElement ){
|
|
301
305
|
const dragLeaveEvent = new Event('dragleave', {
|
|
302
306
|
bubbles: true,
|
|
303
307
|
cancelable: true,
|
|
304
308
|
clientX: e.data.x,
|
|
305
|
-
clientY: e.data.y
|
|
309
|
+
clientY: e.data.y,
|
|
306
310
|
});
|
|
307
311
|
lastDraggedOverElement.dispatchEvent(dragLeaveEvent);
|
|
308
312
|
}
|
|
309
313
|
// If the current element exists and is different from the last, dispatch dragenter on it
|
|
310
|
-
if(draggedOverElement){
|
|
314
|
+
if ( draggedOverElement ){
|
|
311
315
|
const dragEnterEvent = new Event('dragenter', {
|
|
312
316
|
bubbles: true,
|
|
313
317
|
cancelable: true,
|
|
314
318
|
clientX: e.data.x,
|
|
315
|
-
clientY: e.data.y
|
|
319
|
+
clientY: e.data.y,
|
|
316
320
|
});
|
|
317
321
|
draggedOverElement.dispatchEvent(dragEnterEvent);
|
|
318
322
|
}
|
|
@@ -322,28 +326,28 @@ class UI extends EventListener {
|
|
|
322
326
|
}
|
|
323
327
|
}
|
|
324
328
|
// `drop` event
|
|
325
|
-
else if(e.data.msg && e.data.msg === 'drop'){
|
|
326
|
-
if(lastDraggedOverElement){
|
|
329
|
+
else if ( e.data.msg && e.data.msg === 'drop' ){
|
|
330
|
+
if ( lastDraggedOverElement ){
|
|
327
331
|
const dropEvent = new CustomEvent('drop', {
|
|
328
332
|
bubbles: true,
|
|
329
333
|
cancelable: true,
|
|
330
334
|
detail: {
|
|
331
335
|
clientX: e.data.x,
|
|
332
336
|
clientY: e.data.y,
|
|
333
|
-
items: e.data.items
|
|
334
|
-
}
|
|
337
|
+
items: e.data.items,
|
|
338
|
+
},
|
|
335
339
|
});
|
|
336
340
|
lastDraggedOverElement.dispatchEvent(dropEvent);
|
|
337
|
-
|
|
341
|
+
|
|
338
342
|
// Reset the lastDraggedOverElement
|
|
339
343
|
lastDraggedOverElement = null;
|
|
340
344
|
}
|
|
341
345
|
}
|
|
342
346
|
// windowWillClose
|
|
343
|
-
else if(e.data.msg === 'windowWillClose'){
|
|
347
|
+
else if ( e.data.msg === 'windowWillClose' ){
|
|
344
348
|
// If the user has not overridden onWindowClose() then send a message back to the host environment
|
|
345
349
|
// to let it know that it is ok to close the window.
|
|
346
|
-
if(this.#onWindowClose === undefined){
|
|
350
|
+
if ( this.#onWindowClose === undefined ){
|
|
347
351
|
this.messageTarget?.postMessage({
|
|
348
352
|
msg: true,
|
|
349
353
|
appInstanceID: this.appInstanceID,
|
|
@@ -351,9 +355,9 @@ class UI extends EventListener {
|
|
|
351
355
|
}, '*');
|
|
352
356
|
}
|
|
353
357
|
// If the user has overridden onWindowClose() then send a message back to the host environment
|
|
354
|
-
// to let it know that it is NOT ok to close the window. Then execute onWindowClose() and the user will
|
|
358
|
+
// to let it know that it is NOT ok to close the window. Then execute onWindowClose() and the user will
|
|
355
359
|
// have to manually close the window.
|
|
356
|
-
else{
|
|
360
|
+
else {
|
|
357
361
|
this.messageTarget?.postMessage({
|
|
358
362
|
msg: false,
|
|
359
363
|
appInstanceID: this.appInstanceID,
|
|
@@ -363,18 +367,18 @@ class UI extends EventListener {
|
|
|
363
367
|
}
|
|
364
368
|
}
|
|
365
369
|
// itemsOpened
|
|
366
|
-
else if(e.data.msg === 'itemsOpened'){
|
|
370
|
+
else if ( e.data.msg === 'itemsOpened' ){
|
|
367
371
|
// If the user has not overridden onItemsOpened() then only send a message back to the host environment
|
|
368
|
-
if(this.#onItemsOpened === undefined){
|
|
372
|
+
if ( this.#onItemsOpened === undefined ){
|
|
369
373
|
this.messageTarget?.postMessage({
|
|
370
374
|
msg: true,
|
|
371
375
|
appInstanceID: this.appInstanceID,
|
|
372
376
|
original_msg_id: e.data.msg_id,
|
|
373
|
-
}, '*');
|
|
377
|
+
}, '*');
|
|
374
378
|
}
|
|
375
379
|
// If the user has overridden onItemsOpened() then send a message back to the host environment
|
|
376
380
|
// and execute onItemsOpened()
|
|
377
|
-
else{
|
|
381
|
+
else {
|
|
378
382
|
this.messageTarget?.postMessage({
|
|
379
383
|
msg: false,
|
|
380
384
|
appInstanceID: this.appInstanceID,
|
|
@@ -382,59 +386,63 @@ class UI extends EventListener {
|
|
|
382
386
|
}, '*');
|
|
383
387
|
|
|
384
388
|
let items = [];
|
|
385
|
-
if(e.data.items.length > 0){
|
|
386
|
-
for (let index = 0; index < e.data.items.length; index++)
|
|
387
|
-
|
|
389
|
+
if ( e.data.items.length > 0 ){
|
|
390
|
+
for ( let index = 0; index < e.data.items.length; index++ )
|
|
391
|
+
{
|
|
392
|
+
items.push(new FSItem(e.data.items[index]));
|
|
393
|
+
}
|
|
388
394
|
}
|
|
389
395
|
this.#onItemsOpened(items);
|
|
390
396
|
}
|
|
391
397
|
}
|
|
392
398
|
// getAppDataSucceeded
|
|
393
|
-
else if(e.data.msg === 'getAppDataSucceeded'){
|
|
399
|
+
else if ( e.data.msg === 'getAppDataSucceeded' ){
|
|
394
400
|
let appDataItem = new FSItem(e.data.item);
|
|
395
|
-
if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
|
|
401
|
+
if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
|
|
396
402
|
this.#callbackFunctions[e.data.original_msg_id](appDataItem);
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
405
|
// instancesOpenSucceeded
|
|
400
|
-
else if(e.data.msg === 'instancesOpenSucceeded'){
|
|
401
|
-
if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
|
|
406
|
+
else if ( e.data.msg === 'instancesOpenSucceeded' ){
|
|
407
|
+
if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
|
|
402
408
|
this.#callbackFunctions[e.data.original_msg_id](e.data.instancesOpen);
|
|
403
409
|
}
|
|
404
410
|
}
|
|
405
411
|
// readAppDataFileSucceeded
|
|
406
|
-
else if(e.data.msg === 'readAppDataFileSucceeded'){
|
|
412
|
+
else if ( e.data.msg === 'readAppDataFileSucceeded' ){
|
|
407
413
|
let appDataItem = new FSItem(e.data.item);
|
|
408
|
-
if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
|
|
414
|
+
if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
|
|
409
415
|
this.#callbackFunctions[e.data.original_msg_id](appDataItem);
|
|
410
416
|
}
|
|
411
417
|
}
|
|
412
418
|
// readAppDataFileFailed
|
|
413
|
-
else if(e.data.msg === 'readAppDataFileFailed'){
|
|
414
|
-
if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
|
|
419
|
+
else if ( e.data.msg === 'readAppDataFileFailed' ){
|
|
420
|
+
if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
|
|
415
421
|
this.#callbackFunctions[e.data.original_msg_id](null);
|
|
416
422
|
}
|
|
417
423
|
}
|
|
418
424
|
// Determine if this is a response to a previous message and if so, is there
|
|
419
425
|
// a callback function for this message? if answer is yes to both then execute the callback
|
|
420
|
-
else if(e.data.original_msg_id !== undefined && this.#callbackFunctions[e.data.original_msg_id]){
|
|
421
|
-
if(e.data.msg === 'fileOpenPicked'){
|
|
426
|
+
else if ( e.data.original_msg_id !== undefined && this.#callbackFunctions[e.data.original_msg_id] ){
|
|
427
|
+
if ( e.data.msg === 'fileOpenPicked' ){
|
|
422
428
|
// 1 item returned
|
|
423
|
-
if(e.data.items.length === 1){
|
|
424
|
-
this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.items[0]));
|
|
429
|
+
if ( e.data.items.length === 1 ){
|
|
430
|
+
this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.items[0]));
|
|
425
431
|
}
|
|
426
432
|
// multiple items returned
|
|
427
|
-
else if(e.data.items.length > 1){
|
|
433
|
+
else if ( e.data.items.length > 1 ){
|
|
428
434
|
// multiple items returned
|
|
429
435
|
let items = [];
|
|
430
|
-
for (let index = 0; index < e.data.items.length; index++)
|
|
431
|
-
|
|
436
|
+
for ( let index = 0; index < e.data.items.length; index++ )
|
|
437
|
+
{
|
|
438
|
+
items.push(new FSItem(e.data.items[index]));
|
|
439
|
+
}
|
|
432
440
|
this.#callbackFunctions[e.data.original_msg_id](items);
|
|
433
441
|
}
|
|
434
442
|
}
|
|
435
|
-
else if(e.data.msg === 'directoryPicked'){
|
|
443
|
+
else if ( e.data.msg === 'directoryPicked' ){
|
|
436
444
|
// 1 item returned
|
|
437
|
-
if(e.data.items.length === 1){
|
|
445
|
+
if ( e.data.items.length === 1 ){
|
|
438
446
|
this.#callbackFunctions[e.data.original_msg_id](new FSItem({
|
|
439
447
|
uid: e.data.items[0].uid,
|
|
440
448
|
name: e.data.items[0].fsentry_name,
|
|
@@ -450,47 +458,49 @@ class UI extends EventListener {
|
|
|
450
458
|
}));
|
|
451
459
|
}
|
|
452
460
|
// multiple items returned
|
|
453
|
-
else if(e.data.items.length > 1){
|
|
461
|
+
else if ( e.data.items.length > 1 ){
|
|
454
462
|
// multiple items returned
|
|
455
463
|
let items = [];
|
|
456
|
-
for (let index = 0; index < e.data.items.length; index++)
|
|
457
|
-
|
|
464
|
+
for ( let index = 0; index < e.data.items.length; index++ )
|
|
465
|
+
{
|
|
466
|
+
items.push(new FSItem(e.data.items[index]));
|
|
467
|
+
}
|
|
458
468
|
this.#callbackFunctions[e.data.original_msg_id](items);
|
|
459
469
|
}
|
|
460
470
|
}
|
|
461
|
-
else if(e.data.msg === 'colorPicked'){
|
|
471
|
+
else if ( e.data.msg === 'colorPicked' ){
|
|
462
472
|
// execute callback
|
|
463
473
|
this.#callbackFunctions[e.data.original_msg_id](e.data.color);
|
|
464
474
|
}
|
|
465
|
-
else if(e.data.msg === 'fontPicked'){
|
|
475
|
+
else if ( e.data.msg === 'fontPicked' ){
|
|
466
476
|
// execute callback
|
|
467
|
-
this.#callbackFunctions[e.data.original_msg_id](e.data.font);
|
|
477
|
+
this.#callbackFunctions[e.data.original_msg_id](e.data.font);
|
|
468
478
|
}
|
|
469
|
-
else if(e.data.msg === 'alertResponded'){
|
|
479
|
+
else if ( e.data.msg === 'alertResponded' ){
|
|
470
480
|
// execute callback
|
|
471
|
-
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
|
|
481
|
+
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
|
|
472
482
|
}
|
|
473
|
-
else if(e.data.msg === 'promptResponded'){
|
|
483
|
+
else if ( e.data.msg === 'promptResponded' ){
|
|
474
484
|
// execute callback
|
|
475
|
-
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
|
|
485
|
+
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
|
|
476
486
|
}
|
|
477
|
-
else if(e.data.msg === 'languageReceived'){
|
|
487
|
+
else if ( e.data.msg === 'languageReceived' ){
|
|
478
488
|
// execute callback
|
|
479
|
-
this.#callbackFunctions[e.data.original_msg_id](e.data.language);
|
|
489
|
+
this.#callbackFunctions[e.data.original_msg_id](e.data.language);
|
|
480
490
|
}
|
|
481
|
-
else if(e.data.msg ===
|
|
491
|
+
else if ( e.data.msg === 'fileSaved' ){
|
|
482
492
|
// execute callback
|
|
483
|
-
this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.saved_file));
|
|
493
|
+
this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.saved_file));
|
|
484
494
|
}
|
|
485
|
-
else if(e.data.msg ===
|
|
495
|
+
else if ( e.data.msg === 'fileSaveCancelled' ){
|
|
486
496
|
// execute callback
|
|
487
497
|
this.#callbackFunctions[e.data.original_msg_id](FILE_SAVE_CANCELLED);
|
|
488
498
|
}
|
|
489
|
-
else if(e.data.msg ===
|
|
499
|
+
else if ( e.data.msg === 'fileOpenCancelled' ){
|
|
490
500
|
// execute callback
|
|
491
501
|
this.#callbackFunctions[e.data.original_msg_id](FILE_OPEN_CANCELLED);
|
|
492
502
|
}
|
|
493
|
-
else{
|
|
503
|
+
else {
|
|
494
504
|
// execute callback
|
|
495
505
|
this.#callbackFunctions[e.data.original_msg_id](e.data);
|
|
496
506
|
}
|
|
@@ -499,15 +509,17 @@ class UI extends EventListener {
|
|
|
499
509
|
delete this.#callbackFunctions[e.data.original_msg_id];
|
|
500
510
|
}
|
|
501
511
|
// Item Watch response
|
|
502
|
-
else if(e.data.msg ===
|
|
512
|
+
else if ( e.data.msg === 'itemChanged' && e.data.data && e.data.data.uid ){
|
|
503
513
|
//excute callback
|
|
504
|
-
if(this.itemWatchCallbackFunctions[e.data.data.uid] && typeof this.itemWatchCallbackFunctions[e.data.data.uid] === 'function')
|
|
514
|
+
if ( this.itemWatchCallbackFunctions[e.data.data.uid] && typeof this.itemWatchCallbackFunctions[e.data.data.uid] === 'function' )
|
|
515
|
+
{
|
|
505
516
|
this.itemWatchCallbackFunctions[e.data.data.uid](e.data.data);
|
|
517
|
+
}
|
|
506
518
|
}
|
|
507
519
|
// Broadcasts
|
|
508
|
-
else if (e.data.msg === 'broadcast') {
|
|
520
|
+
else if ( e.data.msg === 'broadcast' ) {
|
|
509
521
|
const { name, data } = e.data;
|
|
510
|
-
if (!this.#eventNames.includes(name)) {
|
|
522
|
+
if ( !this.#eventNames.includes(name) ) {
|
|
511
523
|
return;
|
|
512
524
|
}
|
|
513
525
|
this.emit(name, data);
|
|
@@ -541,16 +553,16 @@ class UI extends EventListener {
|
|
|
541
553
|
// We need to send the mouse position to the host environment
|
|
542
554
|
// This is important since a lot of UI elements depend on the mouse position (e.g. ContextMenus, Tooltips, etc.)
|
|
543
555
|
// and the host environment needs to know the mouse position to show these elements correctly.
|
|
544
|
-
// The host environment can't just get the mouse position since when the mouse is over an iframe it
|
|
556
|
+
// The host environment can't just get the mouse position since when the mouse is over an iframe it
|
|
545
557
|
// will not be able to get the mouse position. So we need to send the mouse position to the host environment.
|
|
546
|
-
globalThis.document?.addEventListener('mousemove', async (event)=>{
|
|
558
|
+
globalThis.document?.addEventListener('mousemove', async (event) => {
|
|
547
559
|
// Get the mouse position from the event object
|
|
548
560
|
this.mouseX = event.clientX;
|
|
549
561
|
this.mouseY = event.clientY;
|
|
550
562
|
|
|
551
563
|
// send the mouse position to the host environment
|
|
552
564
|
this.messageTarget?.postMessage({
|
|
553
|
-
msg:
|
|
565
|
+
msg: 'mouseMoved',
|
|
554
566
|
appInstanceID: this.appInstanceID,
|
|
555
567
|
x: this.mouseX,
|
|
556
568
|
y: this.mouseY,
|
|
@@ -558,26 +570,26 @@ class UI extends EventListener {
|
|
|
558
570
|
});
|
|
559
571
|
|
|
560
572
|
// click
|
|
561
|
-
globalThis.document?.addEventListener('click', async (event)=>{
|
|
573
|
+
globalThis.document?.addEventListener('click', async (event) => {
|
|
562
574
|
// Get the mouse position from the event object
|
|
563
575
|
this.mouseX = event.clientX;
|
|
564
576
|
this.mouseY = event.clientY;
|
|
565
577
|
|
|
566
578
|
// send the mouse position to the host environment
|
|
567
579
|
this.messageTarget?.postMessage({
|
|
568
|
-
msg:
|
|
580
|
+
msg: 'mouseClicked',
|
|
569
581
|
appInstanceID: this.appInstanceID,
|
|
570
582
|
x: this.mouseX,
|
|
571
583
|
y: this.mouseY,
|
|
572
584
|
}, '*');
|
|
573
|
-
})
|
|
585
|
+
});
|
|
574
586
|
}
|
|
575
587
|
|
|
576
|
-
onWindowClose
|
|
588
|
+
onWindowClose(callback) {
|
|
577
589
|
this.#onWindowClose = callback;
|
|
578
|
-
}
|
|
590
|
+
};
|
|
579
591
|
|
|
580
|
-
onItemsOpened
|
|
592
|
+
onItemsOpened(callback) {
|
|
581
593
|
// DEPRECATED - this is also called when items are dropped on the app, which in new versions should be handled
|
|
582
594
|
// with the 'drop' event.
|
|
583
595
|
// Check if a file was opened with this app, i.e. check URL parameters of window/iframe
|
|
@@ -585,13 +597,15 @@ class UI extends EventListener {
|
|
|
585
597
|
// before we can call it. This is why we need to check the URL parameters here.
|
|
586
598
|
// This should also be done only the very first time the callback is set (hence the if(!this.#onItemsOpened) check) since
|
|
587
599
|
// the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
|
|
588
|
-
if(!this.#onItemsOpened){
|
|
600
|
+
if ( !this.#onItemsOpened ){
|
|
589
601
|
let URLParams = new URLSearchParams(globalThis.location.search);
|
|
590
|
-
if(URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url')){
|
|
602
|
+
if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ){
|
|
591
603
|
let fpath = URLParams.get('puter.item.path');
|
|
592
604
|
|
|
593
|
-
if(!fpath.startsWith('~/') && !fpath.startsWith('/'))
|
|
594
|
-
|
|
605
|
+
if ( !fpath.startsWith('~/') && !fpath.startsWith('/') )
|
|
606
|
+
{
|
|
607
|
+
fpath = `~/${fpath}`;
|
|
608
|
+
}
|
|
595
609
|
|
|
596
610
|
callback([new FSItem({
|
|
597
611
|
name: URLParams.get('puter.item.name'),
|
|
@@ -609,30 +623,32 @@ class UI extends EventListener {
|
|
|
609
623
|
}
|
|
610
624
|
|
|
611
625
|
this.#onItemsOpened = callback;
|
|
612
|
-
}
|
|
626
|
+
};
|
|
613
627
|
|
|
614
628
|
// Check if the app was launched with items
|
|
615
629
|
// This is useful for apps that are launched with items (e.g. when a file is opened with the app)
|
|
616
|
-
wasLaunchedWithItems
|
|
630
|
+
wasLaunchedWithItems() {
|
|
617
631
|
const URLParams = new URLSearchParams(globalThis.location.search);
|
|
618
|
-
return URLParams.has('puter.item.name') &&
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
632
|
+
return URLParams.has('puter.item.name') &&
|
|
633
|
+
URLParams.has('puter.item.uid') &&
|
|
634
|
+
URLParams.has('puter.item.read_url');
|
|
635
|
+
};
|
|
622
636
|
|
|
623
|
-
onLaunchedWithItems
|
|
637
|
+
onLaunchedWithItems(callback) {
|
|
624
638
|
// Check if a file was opened with this app, i.e. check URL parameters of window/iframe
|
|
625
639
|
// Even though the file has been opened when the app is launched, we need to wait for the onLaunchedWithItems callback to be set
|
|
626
640
|
// before we can call it. This is why we need to check the URL parameters here.
|
|
627
641
|
// This should also be done only the very first time the callback is set (hence the if(!this.#onLaunchedWithItems) check) since
|
|
628
642
|
// the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
|
|
629
|
-
if(!this.#onLaunchedWithItems){
|
|
643
|
+
if ( !this.#onLaunchedWithItems ){
|
|
630
644
|
let URLParams = new URLSearchParams(globalThis.location.search);
|
|
631
|
-
if(URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url')){
|
|
645
|
+
if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ){
|
|
632
646
|
let fpath = URLParams.get('puter.item.path');
|
|
633
647
|
|
|
634
|
-
if(!fpath.startsWith('~/') && !fpath.startsWith('/'))
|
|
635
|
-
|
|
648
|
+
if ( !fpath.startsWith('~/') && !fpath.startsWith('/') )
|
|
649
|
+
{
|
|
650
|
+
fpath = `~/${fpath}`;
|
|
651
|
+
}
|
|
636
652
|
|
|
637
653
|
callback([new FSItem({
|
|
638
654
|
name: URLParams.get('puter.item.name'),
|
|
@@ -650,93 +666,99 @@ class UI extends EventListener {
|
|
|
650
666
|
}
|
|
651
667
|
|
|
652
668
|
this.#onLaunchedWithItems = callback;
|
|
653
|
-
}
|
|
669
|
+
};
|
|
654
670
|
|
|
655
|
-
requestEmailConfirmation
|
|
671
|
+
requestEmailConfirmation() {
|
|
656
672
|
return new Promise((resolve, reject) => {
|
|
657
673
|
this.#postMessageWithCallback('requestEmailConfirmation', resolve, { });
|
|
658
674
|
});
|
|
659
|
-
}
|
|
675
|
+
};
|
|
660
676
|
|
|
661
|
-
alert
|
|
677
|
+
alert(message, buttons, options, callback) {
|
|
662
678
|
return new Promise((resolve) => {
|
|
663
679
|
this.#postMessageWithCallback('ALERT', resolve, { message, buttons, options });
|
|
664
|
-
})
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
openDevPaymentsAccount() {
|
|
684
|
+
return new Promise((resolve) => {
|
|
685
|
+
this.#postMessageWithCallback('openDevPaymentsAccount', resolve, { });
|
|
686
|
+
});
|
|
665
687
|
}
|
|
666
688
|
|
|
667
|
-
instancesOpen
|
|
689
|
+
instancesOpen(callback) {
|
|
668
690
|
return new Promise((resolve) => {
|
|
669
691
|
this.#postMessageWithCallback('getInstancesOpen', resolve, { });
|
|
670
|
-
})
|
|
671
|
-
}
|
|
692
|
+
});
|
|
693
|
+
};
|
|
672
694
|
|
|
673
|
-
socialShare
|
|
695
|
+
socialShare(url, message, options, callback) {
|
|
674
696
|
return new Promise((resolve) => {
|
|
675
697
|
this.#postMessageWithCallback('socialShare', resolve, { url, message, options });
|
|
676
|
-
})
|
|
677
|
-
}
|
|
698
|
+
});
|
|
699
|
+
};
|
|
678
700
|
|
|
679
|
-
prompt
|
|
701
|
+
prompt(message, placeholder, options, callback) {
|
|
680
702
|
return new Promise((resolve) => {
|
|
681
703
|
this.#postMessageWithCallback('PROMPT', resolve, { message, placeholder, options });
|
|
682
|
-
})
|
|
683
|
-
}
|
|
704
|
+
});
|
|
705
|
+
};
|
|
684
706
|
|
|
685
|
-
showDirectoryPicker
|
|
707
|
+
showDirectoryPicker(options, callback){
|
|
686
708
|
return new Promise((resolve, reject) => {
|
|
687
|
-
if (!globalThis.open) {
|
|
688
|
-
return reject(
|
|
709
|
+
if ( !globalThis.open ) {
|
|
710
|
+
return reject('This API is not compatible in Web Workers.');
|
|
689
711
|
}
|
|
690
712
|
const msg_id = this.#messageID++;
|
|
691
|
-
if(this.env === 'app'){
|
|
713
|
+
if ( this.env === 'app' ){
|
|
692
714
|
this.messageTarget?.postMessage({
|
|
693
|
-
msg:
|
|
715
|
+
msg: 'showDirectoryPicker',
|
|
694
716
|
appInstanceID: this.appInstanceID,
|
|
695
717
|
uuid: msg_id,
|
|
696
718
|
options: options,
|
|
697
719
|
env: this.env,
|
|
698
720
|
}, '*');
|
|
699
|
-
}else{
|
|
721
|
+
} else {
|
|
700
722
|
let w = 700;
|
|
701
723
|
let h = 400;
|
|
702
724
|
let title = 'Puter: Open Directory';
|
|
703
|
-
var left = (screen.width/2)-(w/2);
|
|
704
|
-
var top = (screen.height/2)-(h/2);
|
|
705
|
-
window.open(`${puter.defaultGUIOrigin}/action/show-directory-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options)}`,
|
|
706
|
-
|
|
707
|
-
|
|
725
|
+
var left = (screen.width / 2) - (w / 2);
|
|
726
|
+
var top = (screen.height / 2) - (h / 2);
|
|
727
|
+
window.open(`${puter.defaultGUIOrigin}/action/show-directory-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options)}`,
|
|
728
|
+
title,
|
|
729
|
+
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${w}, height=${h}, top=${top}, left=${left}`);
|
|
708
730
|
}
|
|
709
731
|
|
|
710
732
|
//register callback
|
|
711
733
|
this.#callbackFunctions[msg_id] = resolve;
|
|
712
|
-
})
|
|
713
|
-
}
|
|
734
|
+
});
|
|
735
|
+
};
|
|
714
736
|
|
|
715
|
-
showOpenFilePicker
|
|
737
|
+
showOpenFilePicker(options, callback){
|
|
716
738
|
const undefinedOnCancel = new putility.libs.promise.TeePromise();
|
|
717
739
|
const resolveOnlyPromise = new Promise((resolve, reject) => {
|
|
718
|
-
if (!globalThis.open) {
|
|
719
|
-
return reject(
|
|
740
|
+
if ( !globalThis.open ) {
|
|
741
|
+
return reject('This API is not compatible in Web Workers.');
|
|
720
742
|
}
|
|
721
743
|
const msg_id = this.#messageID++;
|
|
722
744
|
|
|
723
|
-
if(this.env === 'app'){
|
|
745
|
+
if ( this.env === 'app' ){
|
|
724
746
|
this.messageTarget?.postMessage({
|
|
725
|
-
msg:
|
|
747
|
+
msg: 'showOpenFilePicker',
|
|
726
748
|
appInstanceID: this.appInstanceID,
|
|
727
749
|
uuid: msg_id,
|
|
728
750
|
options: options ?? {},
|
|
729
751
|
env: this.env,
|
|
730
752
|
}, '*');
|
|
731
|
-
}else{
|
|
753
|
+
} else {
|
|
732
754
|
let w = 700;
|
|
733
755
|
let h = 400;
|
|
734
756
|
let title = 'Puter: Open File';
|
|
735
|
-
var left = (screen.width/2)-(w/2);
|
|
736
|
-
var top = (screen.height/2)-(h/2);
|
|
737
|
-
window.open(`${puter.defaultGUIOrigin}/action/show-open-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options ?? {})}`,
|
|
738
|
-
|
|
739
|
-
|
|
757
|
+
var left = (screen.width / 2) - (w / 2);
|
|
758
|
+
var top = (screen.height / 2) - (h / 2);
|
|
759
|
+
window.open(`${puter.defaultGUIOrigin}/action/show-open-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options ?? {})}`,
|
|
760
|
+
title,
|
|
761
|
+
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${w}, height=${h}, top=${top}, left=${left}`);
|
|
740
762
|
}
|
|
741
763
|
//register callback
|
|
742
764
|
this.#callbackFunctions[msg_id] = (maybe_result) => {
|
|
@@ -748,45 +770,45 @@ class UI extends EventListener {
|
|
|
748
770
|
undefinedOnCancel.resolve(maybe_result);
|
|
749
771
|
resolve(maybe_result);
|
|
750
772
|
};
|
|
751
|
-
})
|
|
773
|
+
});
|
|
752
774
|
resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel;
|
|
753
775
|
return resolveOnlyPromise;
|
|
754
|
-
}
|
|
776
|
+
};
|
|
755
777
|
|
|
756
|
-
showFontPicker
|
|
778
|
+
showFontPicker(options){
|
|
757
779
|
return new Promise((resolve) => {
|
|
758
780
|
this.#postMessageWithCallback('showFontPicker', resolve, { options: options ?? {} });
|
|
759
|
-
})
|
|
760
|
-
}
|
|
781
|
+
});
|
|
782
|
+
};
|
|
761
783
|
|
|
762
|
-
showColorPicker
|
|
784
|
+
showColorPicker(options){
|
|
763
785
|
return new Promise((resolve) => {
|
|
764
786
|
this.#postMessageWithCallback('showColorPicker', resolve, { options: options ?? {} });
|
|
765
|
-
})
|
|
766
|
-
}
|
|
787
|
+
});
|
|
788
|
+
};
|
|
767
789
|
|
|
768
|
-
requestUpgrade
|
|
790
|
+
requestUpgrade() {
|
|
769
791
|
return new Promise((resolve) => {
|
|
770
792
|
this.#postMessageWithCallback('requestUpgrade', resolve, { });
|
|
771
|
-
})
|
|
772
|
-
}
|
|
793
|
+
});
|
|
794
|
+
};
|
|
773
795
|
|
|
774
|
-
showSaveFilePicker
|
|
796
|
+
showSaveFilePicker(content, suggestedName, type){
|
|
775
797
|
const undefinedOnCancel = new putility.libs.promise.TeePromise();
|
|
776
798
|
const resolveOnlyPromise = new Promise((resolve, reject) => {
|
|
777
|
-
if (!globalThis.open) {
|
|
778
|
-
return reject(
|
|
799
|
+
if ( !globalThis.open ) {
|
|
800
|
+
return reject('This API is not compatible in Web Workers.');
|
|
779
801
|
}
|
|
780
802
|
const msg_id = this.#messageID++;
|
|
781
803
|
if ( ! type && Object.prototype.toString.call(content) === '[object URL]' ) {
|
|
782
804
|
type = 'url';
|
|
783
805
|
}
|
|
784
806
|
const url = type === 'url' ? content.toString() : undefined;
|
|
785
|
-
const source_path = ['move','copy'].includes(type) ? content : undefined;
|
|
786
|
-
|
|
787
|
-
if(this.env === 'app'){
|
|
807
|
+
const source_path = ['move', 'copy'].includes(type) ? content : undefined;
|
|
808
|
+
|
|
809
|
+
if ( this.env === 'app' ){
|
|
788
810
|
this.messageTarget?.postMessage({
|
|
789
|
-
msg:
|
|
811
|
+
msg: 'showSaveFilePicker',
|
|
790
812
|
appInstanceID: this.appInstanceID,
|
|
791
813
|
content: url ? undefined : content,
|
|
792
814
|
save_type: type,
|
|
@@ -794,19 +816,19 @@ class UI extends EventListener {
|
|
|
794
816
|
source_path,
|
|
795
817
|
suggestedName: suggestedName ?? '',
|
|
796
818
|
env: this.env,
|
|
797
|
-
uuid: msg_id
|
|
819
|
+
uuid: msg_id,
|
|
798
820
|
}, '*');
|
|
799
|
-
}else{
|
|
821
|
+
} else {
|
|
800
822
|
window.addEventListener('message', async (e) => {
|
|
801
|
-
if(e.data?.msg ===
|
|
823
|
+
if ( e.data?.msg === 'sendMeFileData' ){
|
|
802
824
|
// Send the blob URL to the host environment
|
|
803
825
|
e.source.postMessage({
|
|
804
|
-
msg:
|
|
826
|
+
msg: 'showSaveFilePickerPopup',
|
|
805
827
|
content: url ? undefined : content,
|
|
806
828
|
url: url ? url.toString() : undefined,
|
|
807
829
|
suggestedName: suggestedName ?? '',
|
|
808
830
|
env: this.env,
|
|
809
|
-
uuid: msg_id
|
|
831
|
+
uuid: msg_id,
|
|
810
832
|
}, '*');
|
|
811
833
|
|
|
812
834
|
// remove the event listener
|
|
@@ -814,7 +836,7 @@ class UI extends EventListener {
|
|
|
814
836
|
}
|
|
815
837
|
});
|
|
816
838
|
// Create a Blob from your binary data
|
|
817
|
-
let blob = new Blob([content], {type: 'application/octet-stream'});
|
|
839
|
+
let blob = new Blob([content], { type: 'application/octet-stream' });
|
|
818
840
|
|
|
819
841
|
// Create an object URL for the Blob
|
|
820
842
|
let objectUrl = URL.createObjectURL(blob);
|
|
@@ -822,11 +844,11 @@ class UI extends EventListener {
|
|
|
822
844
|
let w = 700;
|
|
823
845
|
let h = 400;
|
|
824
846
|
let title = 'Puter: Save File';
|
|
825
|
-
var left = (screen.width/2)-(w/2);
|
|
826
|
-
var top = (screen.height/2)-(h/2);
|
|
827
|
-
window.open(`${puter.defaultGUIOrigin}/action/show-save-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&blobUrl=${encodeURIComponent(objectUrl)}`,
|
|
828
|
-
|
|
829
|
-
|
|
847
|
+
var left = (screen.width / 2) - (w / 2);
|
|
848
|
+
var top = (screen.height / 2) - (h / 2);
|
|
849
|
+
window.open(`${puter.defaultGUIOrigin}/action/show-save-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&blobUrl=${encodeURIComponent(objectUrl)}`,
|
|
850
|
+
title,
|
|
851
|
+
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${w}, height=${h}, top=${top}, left=${left}`);
|
|
830
852
|
}
|
|
831
853
|
//register callback
|
|
832
854
|
this.#callbackFunctions[msg_id] = (maybe_result) => {
|
|
@@ -839,159 +861,159 @@ class UI extends EventListener {
|
|
|
839
861
|
resolve(maybe_result);
|
|
840
862
|
};
|
|
841
863
|
});
|
|
842
|
-
|
|
864
|
+
|
|
843
865
|
resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel;
|
|
844
|
-
|
|
866
|
+
|
|
845
867
|
return resolveOnlyPromise;
|
|
846
|
-
}
|
|
868
|
+
};
|
|
847
869
|
|
|
848
|
-
setWindowTitle
|
|
849
|
-
if(typeof window_id === 'function'){
|
|
870
|
+
setWindowTitle(title, window_id, callback) {
|
|
871
|
+
if ( typeof window_id === 'function' ){
|
|
850
872
|
callback = window_id;
|
|
851
873
|
window_id = undefined;
|
|
852
|
-
}else if(typeof window_id ===
|
|
874
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
853
875
|
window_id = window_id.id;
|
|
854
876
|
}
|
|
855
877
|
|
|
856
878
|
return new Promise((resolve) => {
|
|
857
|
-
this.#postMessageWithCallback('setWindowTitle', resolve, { new_title: title, window_id: window_id});
|
|
858
|
-
})
|
|
859
|
-
}
|
|
879
|
+
this.#postMessageWithCallback('setWindowTitle', resolve, { new_title: title, window_id: window_id });
|
|
880
|
+
});
|
|
881
|
+
};
|
|
860
882
|
|
|
861
|
-
setWindowWidth
|
|
862
|
-
if(typeof window_id === 'function'){
|
|
883
|
+
setWindowWidth(width, window_id, callback) {
|
|
884
|
+
if ( typeof window_id === 'function' ){
|
|
863
885
|
callback = window_id;
|
|
864
886
|
window_id = undefined;
|
|
865
|
-
}else if(typeof window_id ===
|
|
887
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
866
888
|
window_id = window_id.id;
|
|
867
889
|
}
|
|
868
|
-
|
|
890
|
+
|
|
869
891
|
return new Promise((resolve) => {
|
|
870
892
|
this.#postMessageWithCallback('setWindowWidth', resolve, { width: width, window_id: window_id });
|
|
871
|
-
})
|
|
872
|
-
}
|
|
893
|
+
});
|
|
894
|
+
};
|
|
873
895
|
|
|
874
|
-
setWindowHeight
|
|
875
|
-
if(typeof window_id === 'function'){
|
|
896
|
+
setWindowHeight(height, window_id, callback) {
|
|
897
|
+
if ( typeof window_id === 'function' ){
|
|
876
898
|
callback = window_id;
|
|
877
899
|
window_id = undefined;
|
|
878
|
-
}else if(typeof window_id ===
|
|
900
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
879
901
|
window_id = window_id.id;
|
|
880
902
|
}
|
|
881
|
-
|
|
903
|
+
|
|
882
904
|
return new Promise((resolve) => {
|
|
883
905
|
this.#postMessageWithCallback('setWindowHeight', resolve, { height: height, window_id: window_id });
|
|
884
|
-
})
|
|
885
|
-
}
|
|
906
|
+
});
|
|
907
|
+
};
|
|
886
908
|
|
|
887
|
-
setWindowSize
|
|
888
|
-
if(typeof window_id === 'function'){
|
|
909
|
+
setWindowSize(width, height, window_id, callback) {
|
|
910
|
+
if ( typeof window_id === 'function' ){
|
|
889
911
|
callback = window_id;
|
|
890
912
|
window_id = undefined;
|
|
891
|
-
}else if(typeof window_id ===
|
|
913
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
892
914
|
window_id = window_id.id;
|
|
893
915
|
}
|
|
894
|
-
|
|
916
|
+
|
|
895
917
|
return new Promise((resolve) => {
|
|
896
918
|
this.#postMessageWithCallback('setWindowSize', resolve, { width: width, height: height, window_id: window_id });
|
|
897
|
-
})
|
|
898
|
-
}
|
|
919
|
+
});
|
|
920
|
+
};
|
|
899
921
|
|
|
900
|
-
setWindowPosition
|
|
901
|
-
if(typeof window_id === 'function'){
|
|
922
|
+
setWindowPosition(x, y, window_id, callback) {
|
|
923
|
+
if ( typeof window_id === 'function' ){
|
|
902
924
|
callback = window_id;
|
|
903
925
|
window_id = undefined;
|
|
904
|
-
}else if(typeof window_id ===
|
|
926
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
905
927
|
window_id = window_id.id;
|
|
906
928
|
}
|
|
907
|
-
|
|
929
|
+
|
|
908
930
|
return new Promise((resolve) => {
|
|
909
931
|
this.#postMessageWithCallback('setWindowPosition', resolve, { x, y, window_id });
|
|
910
|
-
})
|
|
911
|
-
}
|
|
932
|
+
});
|
|
933
|
+
};
|
|
912
934
|
|
|
913
|
-
setWindowY
|
|
914
|
-
if(typeof window_id === 'function'){
|
|
935
|
+
setWindowY(y, window_id, callback) {
|
|
936
|
+
if ( typeof window_id === 'function' ){
|
|
915
937
|
callback = window_id;
|
|
916
938
|
window_id = undefined;
|
|
917
|
-
}else if(typeof window_id ===
|
|
939
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
918
940
|
window_id = window_id.id;
|
|
919
941
|
}
|
|
920
942
|
|
|
921
943
|
return new Promise((resolve) => {
|
|
922
944
|
this.#postMessageWithCallback('setWindowY', resolve, { y, window_id });
|
|
923
|
-
})
|
|
924
|
-
}
|
|
945
|
+
});
|
|
946
|
+
};
|
|
925
947
|
|
|
926
|
-
setWindowX
|
|
927
|
-
if(typeof window_id === 'function'){
|
|
948
|
+
setWindowX(x, window_id, callback) {
|
|
949
|
+
if ( typeof window_id === 'function' ){
|
|
928
950
|
callback = window_id;
|
|
929
951
|
window_id = undefined;
|
|
930
|
-
}else if(typeof window_id ===
|
|
952
|
+
} else if ( typeof window_id === 'object' && window_id !== null ){
|
|
931
953
|
window_id = window_id.id;
|
|
932
954
|
}
|
|
933
955
|
|
|
934
956
|
return new Promise((resolve) => {
|
|
935
957
|
this.#postMessageWithCallback('setWindowX', resolve, { x, window_id });
|
|
936
|
-
})
|
|
937
|
-
}
|
|
958
|
+
});
|
|
959
|
+
};
|
|
938
960
|
|
|
939
|
-
showWindow
|
|
961
|
+
showWindow() {
|
|
940
962
|
this.#postMessageWithObject('showWindow');
|
|
941
|
-
}
|
|
963
|
+
};
|
|
942
964
|
|
|
943
|
-
hideWindow
|
|
965
|
+
hideWindow() {
|
|
944
966
|
this.#postMessageWithObject('hideWindow');
|
|
945
|
-
}
|
|
967
|
+
};
|
|
946
968
|
|
|
947
|
-
toggleWindow
|
|
969
|
+
toggleWindow() {
|
|
948
970
|
this.#postMessageWithObject('toggleWindow');
|
|
949
|
-
}
|
|
971
|
+
};
|
|
950
972
|
|
|
951
|
-
setMenubar
|
|
973
|
+
setMenubar(spec) {
|
|
952
974
|
this.#postMessageWithObject('setMenubar', spec);
|
|
953
|
-
}
|
|
975
|
+
};
|
|
954
976
|
|
|
955
|
-
requestPermission
|
|
977
|
+
requestPermission(options) {
|
|
956
978
|
return new Promise((resolve) => {
|
|
957
|
-
if (this.env === 'app') {
|
|
979
|
+
if ( this.env === 'app' ) {
|
|
958
980
|
return new Promise((resolve) => {
|
|
959
981
|
this.#postMessageWithCallback('requestPermission', resolve, { options });
|
|
960
|
-
})
|
|
982
|
+
});
|
|
961
983
|
} else {
|
|
962
984
|
// TODO: Implement for web
|
|
963
985
|
resolve(false);
|
|
964
986
|
}
|
|
965
|
-
})
|
|
966
|
-
}
|
|
987
|
+
});
|
|
988
|
+
};
|
|
967
989
|
|
|
968
|
-
disableMenuItem
|
|
969
|
-
this.#postMessageWithObject('disableMenuItem', {id: item_id});
|
|
970
|
-
}
|
|
990
|
+
disableMenuItem(item_id) {
|
|
991
|
+
this.#postMessageWithObject('disableMenuItem', { id: item_id });
|
|
992
|
+
};
|
|
971
993
|
|
|
972
|
-
enableMenuItem
|
|
973
|
-
this.#postMessageWithObject('enableMenuItem', {id: item_id});
|
|
974
|
-
}
|
|
994
|
+
enableMenuItem(item_id) {
|
|
995
|
+
this.#postMessageWithObject('enableMenuItem', { id: item_id });
|
|
996
|
+
};
|
|
975
997
|
|
|
976
|
-
setMenuItemIcon
|
|
977
|
-
this.#postMessageWithObject('setMenuItemIcon', {id: item_id, icon: icon});
|
|
978
|
-
}
|
|
998
|
+
setMenuItemIcon(item_id, icon) {
|
|
999
|
+
this.#postMessageWithObject('setMenuItemIcon', { id: item_id, icon: icon });
|
|
1000
|
+
};
|
|
979
1001
|
|
|
980
|
-
setMenuItemIconActive
|
|
981
|
-
this.#postMessageWithObject('setMenuItemIconActive', {id: item_id, icon: icon});
|
|
982
|
-
}
|
|
1002
|
+
setMenuItemIconActive(item_id, icon) {
|
|
1003
|
+
this.#postMessageWithObject('setMenuItemIconActive', { id: item_id, icon: icon });
|
|
1004
|
+
};
|
|
983
1005
|
|
|
984
|
-
setMenuItemChecked
|
|
985
|
-
this.#postMessageWithObject('setMenuItemChecked', {id: item_id, checked: checked});
|
|
986
|
-
}
|
|
1006
|
+
setMenuItemChecked(item_id, checked) {
|
|
1007
|
+
this.#postMessageWithObject('setMenuItemChecked', { id: item_id, checked: checked });
|
|
1008
|
+
};
|
|
987
1009
|
|
|
988
|
-
contextMenu
|
|
1010
|
+
contextMenu(spec) {
|
|
989
1011
|
this.#postMessageWithObject('contextMenu', spec);
|
|
990
|
-
}
|
|
1012
|
+
};
|
|
991
1013
|
|
|
992
1014
|
/**
|
|
993
1015
|
* Asynchronously extracts entries from DataTransferItems, like files and directories.
|
|
994
|
-
*
|
|
1016
|
+
*
|
|
995
1017
|
* @private
|
|
996
1018
|
* @function
|
|
997
1019
|
* @async
|
|
@@ -1000,107 +1022,109 @@ class UI extends EventListener {
|
|
|
1000
1022
|
* @param {boolean} [options.raw=false] - Determines if the file path should be processed.
|
|
1001
1023
|
* @returns {Promise<Array<File|Entry>>} - A promise that resolves to an array of File or Entry objects.
|
|
1002
1024
|
* @throws {Error} - Throws an error if there's an EncodingError and provides information about how to solve it.
|
|
1003
|
-
*
|
|
1025
|
+
*
|
|
1004
1026
|
* @example
|
|
1005
1027
|
* const items = event.dataTransfer.items;
|
|
1006
1028
|
* const entries = await getEntriesFromDataTransferItems(items, { raw: false });
|
|
1007
1029
|
*/
|
|
1008
1030
|
getEntriesFromDataTransferItems = async function(dataTransferItems, options = { raw: false }) {
|
|
1009
1031
|
const checkErr = (err) => {
|
|
1010
|
-
if (this.getEntriesFromDataTransferItems.didShowInfo) return
|
|
1011
|
-
if (err.name !== 'EncodingError') return
|
|
1012
|
-
this.getEntriesFromDataTransferItems.didShowInfo = true
|
|
1032
|
+
if ( this.getEntriesFromDataTransferItems.didShowInfo ) return;
|
|
1033
|
+
if ( err.name !== 'EncodingError' ) return;
|
|
1034
|
+
this.getEntriesFromDataTransferItems.didShowInfo = true;
|
|
1013
1035
|
const infoMsg = `${err.name} occurred within datatransfer-files-promise module\n`
|
|
1014
1036
|
+ `Error message: "${err.message}"\n`
|
|
1015
|
-
+ 'Try serving html over http if currently you are running it from the filesystem.'
|
|
1016
|
-
console.warn(infoMsg)
|
|
1017
|
-
}
|
|
1037
|
+
+ 'Try serving html over http if currently you are running it from the filesystem.';
|
|
1038
|
+
console.warn(infoMsg);
|
|
1039
|
+
};
|
|
1018
1040
|
|
|
1019
1041
|
const readFile = (entry, path = '') => {
|
|
1020
1042
|
return new Promise((resolve, reject) => {
|
|
1021
1043
|
entry.file(file => {
|
|
1022
|
-
if (!options.raw) file.filepath = path + file.name // save full path
|
|
1023
|
-
resolve(file)
|
|
1044
|
+
if ( !options.raw ) file.filepath = path + file.name; // save full path
|
|
1045
|
+
resolve(file);
|
|
1024
1046
|
}, (err) => {
|
|
1025
|
-
checkErr(err)
|
|
1026
|
-
reject(err)
|
|
1027
|
-
})
|
|
1028
|
-
})
|
|
1029
|
-
}
|
|
1047
|
+
checkErr(err);
|
|
1048
|
+
reject(err);
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
};
|
|
1030
1052
|
|
|
1031
1053
|
const dirReadEntries = (dirReader, path) => {
|
|
1032
1054
|
return new Promise((resolve, reject) => {
|
|
1033
1055
|
dirReader.readEntries(async entries => {
|
|
1034
|
-
let files = []
|
|
1035
|
-
for (let entry of entries) {
|
|
1036
|
-
const itemFiles = await getFilesFromEntry(entry, path)
|
|
1037
|
-
files = files.concat(itemFiles)
|
|
1056
|
+
let files = [];
|
|
1057
|
+
for ( let entry of entries ) {
|
|
1058
|
+
const itemFiles = await getFilesFromEntry(entry, path);
|
|
1059
|
+
files = files.concat(itemFiles);
|
|
1038
1060
|
}
|
|
1039
|
-
resolve(files)
|
|
1061
|
+
resolve(files);
|
|
1040
1062
|
}, (err) => {
|
|
1041
|
-
checkErr(err)
|
|
1042
|
-
reject(err)
|
|
1043
|
-
})
|
|
1044
|
-
})
|
|
1045
|
-
}
|
|
1063
|
+
checkErr(err);
|
|
1064
|
+
reject(err);
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
};
|
|
1046
1068
|
|
|
1047
1069
|
const readDir = async (entry, path) => {
|
|
1048
|
-
const dirReader = entry.createReader()
|
|
1049
|
-
const newPath = path + entry.name
|
|
1050
|
-
let files = []
|
|
1051
|
-
let newFiles
|
|
1070
|
+
const dirReader = entry.createReader();
|
|
1071
|
+
const newPath = `${path + entry.name}/`;
|
|
1072
|
+
let files = [];
|
|
1073
|
+
let newFiles;
|
|
1052
1074
|
do {
|
|
1053
|
-
newFiles = await dirReadEntries(dirReader, newPath)
|
|
1054
|
-
files = files.concat(newFiles)
|
|
1055
|
-
} while (newFiles.length > 0)
|
|
1056
|
-
return files
|
|
1057
|
-
}
|
|
1075
|
+
newFiles = await dirReadEntries(dirReader, newPath);
|
|
1076
|
+
files = files.concat(newFiles);
|
|
1077
|
+
} while ( newFiles.length > 0 );
|
|
1078
|
+
return files;
|
|
1079
|
+
};
|
|
1058
1080
|
|
|
1059
1081
|
const getFilesFromEntry = async (entry, path = '') => {
|
|
1060
|
-
if(entry === null)
|
|
1082
|
+
if ( entry === null )
|
|
1083
|
+
{
|
|
1061
1084
|
return;
|
|
1062
|
-
else if (entry.isFile) {
|
|
1063
|
-
const file = await readFile(entry, path)
|
|
1064
|
-
return [file]
|
|
1065
1085
|
}
|
|
1066
|
-
else if (entry.
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
return files
|
|
1086
|
+
else if ( entry.isFile ) {
|
|
1087
|
+
const file = await readFile(entry, path);
|
|
1088
|
+
return [file];
|
|
1070
1089
|
}
|
|
1071
|
-
|
|
1090
|
+
else if ( entry.isDirectory ) {
|
|
1091
|
+
const files = await readDir(entry, path);
|
|
1092
|
+
files.push(entry);
|
|
1093
|
+
return files;
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1072
1096
|
|
|
1073
|
-
let files = []
|
|
1074
|
-
let entries = []
|
|
1097
|
+
let files = [];
|
|
1098
|
+
let entries = [];
|
|
1075
1099
|
|
|
1076
1100
|
// Pull out all entries before reading them
|
|
1077
|
-
for (let i = 0, ii = dataTransferItems.length; i < ii; i++) {
|
|
1078
|
-
entries.push(dataTransferItems[i].webkitGetAsEntry())
|
|
1101
|
+
for ( let i = 0, ii = dataTransferItems.length; i < ii; i++ ) {
|
|
1102
|
+
entries.push(dataTransferItems[i].webkitGetAsEntry());
|
|
1079
1103
|
}
|
|
1080
1104
|
|
|
1081
1105
|
// Recursively read through all entries
|
|
1082
|
-
for (let entry of entries) {
|
|
1083
|
-
const newFiles = await getFilesFromEntry(entry)
|
|
1084
|
-
files = files.concat(newFiles)
|
|
1106
|
+
for ( let entry of entries ) {
|
|
1107
|
+
const newFiles = await getFilesFromEntry(entry);
|
|
1108
|
+
files = files.concat(newFiles);
|
|
1085
1109
|
}
|
|
1086
1110
|
|
|
1087
|
-
return files
|
|
1088
|
-
}
|
|
1111
|
+
return files;
|
|
1112
|
+
};
|
|
1089
1113
|
|
|
1090
|
-
authenticateWithPuter
|
|
1091
|
-
if(this.env !== 'web'){
|
|
1114
|
+
authenticateWithPuter() {
|
|
1115
|
+
if ( this.env !== 'web' ){
|
|
1092
1116
|
return;
|
|
1093
1117
|
}
|
|
1094
1118
|
|
|
1095
1119
|
// if authToken is already present, resolve immediately
|
|
1096
|
-
if(this.authToken){
|
|
1120
|
+
if ( this.authToken ){
|
|
1097
1121
|
return new Promise((resolve) => {
|
|
1098
1122
|
resolve();
|
|
1099
|
-
})
|
|
1123
|
+
});
|
|
1100
1124
|
}
|
|
1101
1125
|
|
|
1102
1126
|
// If a prompt is already open, return a promise that resolves based on the existing prompt's result.
|
|
1103
|
-
if (puter.puterAuthState.isPromptOpen) {
|
|
1127
|
+
if ( puter.puterAuthState.isPromptOpen ) {
|
|
1104
1128
|
return new Promise((resolve, reject) => {
|
|
1105
1129
|
puter.puterAuthState.resolver = { resolve, reject };
|
|
1106
1130
|
});
|
|
@@ -1111,7 +1135,7 @@ class UI extends EventListener {
|
|
|
1111
1135
|
puter.puterAuthState.authGranted = null;
|
|
1112
1136
|
|
|
1113
1137
|
return new Promise((resolve, reject) => {
|
|
1114
|
-
if (!puter.authToken) {
|
|
1138
|
+
if ( !puter.authToken ) {
|
|
1115
1139
|
const puterDialog = new PuterDialog(resolve, reject);
|
|
1116
1140
|
document.body.appendChild(puterDialog);
|
|
1117
1141
|
puterDialog.open();
|
|
@@ -1120,7 +1144,7 @@ class UI extends EventListener {
|
|
|
1120
1144
|
resolve();
|
|
1121
1145
|
}
|
|
1122
1146
|
});
|
|
1123
|
-
}
|
|
1147
|
+
};
|
|
1124
1148
|
|
|
1125
1149
|
// Returns a Promise<AppConnection>
|
|
1126
1150
|
/**
|
|
@@ -1128,16 +1152,16 @@ class UI extends EventListener {
|
|
|
1128
1152
|
* @param {*} nameOrOptions - name of the app as a string, or an options object
|
|
1129
1153
|
* @param {*} args - named parameters that will be passed to the app as arguments
|
|
1130
1154
|
* @param {*} callback - in case you don't want to use `await` or `.then()`
|
|
1131
|
-
* @returns
|
|
1155
|
+
* @returns
|
|
1132
1156
|
*/
|
|
1133
1157
|
launchApp = async function launchApp(nameOrOptions, args, callback) {
|
|
1134
1158
|
let pseudonym = undefined;
|
|
1135
1159
|
let file_paths = undefined;
|
|
1136
1160
|
let items = undefined;
|
|
1137
1161
|
let app_name = nameOrOptions; // becomes string after branch below
|
|
1138
|
-
|
|
1162
|
+
|
|
1139
1163
|
// Handle case where app_name is an options object
|
|
1140
|
-
if (typeof app_name === 'object' && app_name !== null) {
|
|
1164
|
+
if ( typeof app_name === 'object' && app_name !== null ) {
|
|
1141
1165
|
const options = app_name;
|
|
1142
1166
|
app_name = options.name || options.app_name;
|
|
1143
1167
|
file_paths = options.file_paths;
|
|
@@ -1146,22 +1170,22 @@ class UI extends EventListener {
|
|
|
1146
1170
|
pseudonym = options.pseudonym;
|
|
1147
1171
|
items = options.items;
|
|
1148
1172
|
}
|
|
1149
|
-
|
|
1173
|
+
|
|
1150
1174
|
if ( items ) {
|
|
1151
1175
|
if ( ! Array.isArray(items) ) items = [];
|
|
1152
|
-
for ( let i=0 ; i < items.length ; i++ ) {
|
|
1176
|
+
for ( let i = 0 ; i < items.length ; i++ ) {
|
|
1153
1177
|
if ( items[i] instanceof FSItem ) {
|
|
1154
1178
|
items[i] = items[i]._internalProperties.file_signature;
|
|
1155
1179
|
}
|
|
1156
1180
|
}
|
|
1157
1181
|
}
|
|
1158
|
-
|
|
1182
|
+
|
|
1159
1183
|
if ( app_name && app_name.includes('#(as)') ) {
|
|
1160
1184
|
[app_name, pseudonym] = app_name.split('#(as)');
|
|
1161
1185
|
}
|
|
1162
|
-
|
|
1186
|
+
|
|
1163
1187
|
if ( ! app_name ) app_name = puter.appName;
|
|
1164
|
-
|
|
1188
|
+
|
|
1165
1189
|
const app_info = await this.#ipc_stub({
|
|
1166
1190
|
method: 'launchApp',
|
|
1167
1191
|
callback,
|
|
@@ -1173,39 +1197,39 @@ class UI extends EventListener {
|
|
|
1173
1197
|
args,
|
|
1174
1198
|
},
|
|
1175
1199
|
});
|
|
1176
|
-
|
|
1200
|
+
|
|
1177
1201
|
return AppConnection.from(app_info, this.context);
|
|
1178
|
-
}
|
|
1202
|
+
};
|
|
1179
1203
|
|
|
1180
|
-
connectToInstance = async function connectToInstance
|
|
1204
|
+
connectToInstance = async function connectToInstance(app_name) {
|
|
1181
1205
|
const app_info = await this.#ipc_stub({
|
|
1182
1206
|
method: 'connectToInstance',
|
|
1183
1207
|
parameters: {
|
|
1184
1208
|
app_name,
|
|
1185
|
-
}
|
|
1209
|
+
},
|
|
1186
1210
|
});
|
|
1187
1211
|
|
|
1188
1212
|
return AppConnection.from(app_info, this.context);
|
|
1189
|
-
}
|
|
1213
|
+
};
|
|
1190
1214
|
|
|
1191
1215
|
parentApp() {
|
|
1192
1216
|
return this.#parentAppConnection;
|
|
1193
1217
|
}
|
|
1194
1218
|
|
|
1195
|
-
createWindow
|
|
1219
|
+
createWindow(options, callback) {
|
|
1196
1220
|
return new Promise((resolve) => {
|
|
1197
|
-
this.#postMessageWithCallback('createWindow', (res)=>{
|
|
1221
|
+
this.#postMessageWithCallback('createWindow', (res) => {
|
|
1198
1222
|
resolve(res.window);
|
|
1199
1223
|
}, { options: options ?? {} });
|
|
1200
|
-
})
|
|
1201
|
-
}
|
|
1224
|
+
});
|
|
1225
|
+
};
|
|
1202
1226
|
|
|
1203
1227
|
// Menubar
|
|
1204
|
-
menubar
|
|
1228
|
+
menubar(){
|
|
1205
1229
|
// Remove previous style tag
|
|
1206
1230
|
document.querySelectorAll('style.puter-stylesheet').forEach(function(el) {
|
|
1207
1231
|
el.remove();
|
|
1208
|
-
})
|
|
1232
|
+
});
|
|
1209
1233
|
|
|
1210
1234
|
// Add new style tag
|
|
1211
1235
|
const style = document.createElement('style');
|
|
@@ -1341,32 +1365,34 @@ class UI extends EventListener {
|
|
|
1341
1365
|
|
|
1342
1366
|
document.addEventListener('click', function(e){
|
|
1343
1367
|
// Don't hide if clicking on disabled item
|
|
1344
|
-
if(e.target.classList.contains('dropdown-item-disabled'))
|
|
1368
|
+
if ( e.target.classList.contains('dropdown-item-disabled') )
|
|
1369
|
+
{
|
|
1345
1370
|
return false;
|
|
1371
|
+
}
|
|
1346
1372
|
// Hide open menus
|
|
1347
|
-
if(!(e.target).classList.contains('menubar-item')){
|
|
1373
|
+
if ( !(e.target).classList.contains('menubar-item') ){
|
|
1348
1374
|
document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
|
|
1349
1375
|
el.classList.remove('menubar-item-open');
|
|
1350
|
-
})
|
|
1376
|
+
});
|
|
1351
1377
|
|
|
1352
|
-
document.querySelectorAll('.dropdown').forEach(el => el.style.display =
|
|
1378
|
+
document.querySelectorAll('.dropdown').forEach(el => el.style.display = 'none');
|
|
1353
1379
|
}
|
|
1354
1380
|
});
|
|
1355
1381
|
|
|
1356
1382
|
// When focus is gone from this window, hide open menus
|
|
1357
1383
|
window.addEventListener('blur', function(e){
|
|
1358
1384
|
document.querySelectorAll('.dropdown').forEach(function(el) {
|
|
1359
|
-
el.style.display =
|
|
1360
|
-
})
|
|
1385
|
+
el.style.display = 'none';
|
|
1386
|
+
});
|
|
1361
1387
|
document.querySelectorAll('.menubar-item.menubar-item-open').forEach(el => el.classList.remove('menubar-item-open'));
|
|
1362
1388
|
});
|
|
1363
1389
|
|
|
1364
1390
|
// Returns the siblings of the element
|
|
1365
|
-
const siblings = function
|
|
1366
|
-
const siblings = [];
|
|
1391
|
+
const siblings = function(e) {
|
|
1392
|
+
const siblings = [];
|
|
1367
1393
|
|
|
1368
1394
|
// if no parent, return empty list
|
|
1369
|
-
if(!e.parentNode) {
|
|
1395
|
+
if ( !e.parentNode ) {
|
|
1370
1396
|
return siblings;
|
|
1371
1397
|
}
|
|
1372
1398
|
|
|
@@ -1374,8 +1400,8 @@ class UI extends EventListener {
|
|
|
1374
1400
|
let sibling = e.parentNode.firstChild;
|
|
1375
1401
|
|
|
1376
1402
|
// get all other siblings
|
|
1377
|
-
while (sibling) {
|
|
1378
|
-
if (sibling.nodeType === 1 && sibling !== e) {
|
|
1403
|
+
while ( sibling ) {
|
|
1404
|
+
if ( sibling.nodeType === 1 && sibling !== e ) {
|
|
1379
1405
|
siblings.push(sibling);
|
|
1380
1406
|
}
|
|
1381
1407
|
sibling = sibling.nextSibling;
|
|
@@ -1389,24 +1415,26 @@ class UI extends EventListener {
|
|
|
1389
1415
|
document.querySelectorAll('.dropdown').forEach(function(el) {
|
|
1390
1416
|
el.style.display = 'none';
|
|
1391
1417
|
});
|
|
1392
|
-
|
|
1418
|
+
|
|
1393
1419
|
// Remove open class from all menus, except this menu that was just clicked
|
|
1394
1420
|
document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
|
|
1395
|
-
if(el != e.target)
|
|
1421
|
+
if ( el != e.target )
|
|
1422
|
+
{
|
|
1396
1423
|
el.classList.remove('menubar-item-open');
|
|
1424
|
+
}
|
|
1397
1425
|
});
|
|
1398
|
-
|
|
1426
|
+
|
|
1399
1427
|
// If menu is already open, close it
|
|
1400
|
-
if(this.classList.contains('menubar-item-open')){
|
|
1428
|
+
if ( this.classList.contains('menubar-item-open') ){
|
|
1401
1429
|
document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
|
|
1402
1430
|
el.classList.remove('menubar-item-open');
|
|
1403
1431
|
});
|
|
1404
1432
|
}
|
|
1405
1433
|
|
|
1406
1434
|
// If menu is not open, open it
|
|
1407
|
-
else if(!e.target.classList.contains('dropdown-item')){
|
|
1408
|
-
this.classList.add('menubar-item-open')
|
|
1409
|
-
|
|
1435
|
+
else if ( !e.target.classList.contains('dropdown-item') ){
|
|
1436
|
+
this.classList.add('menubar-item-open');
|
|
1437
|
+
|
|
1410
1438
|
// show all sibling
|
|
1411
1439
|
siblings(this).forEach(function(el) {
|
|
1412
1440
|
el.style.display = 'block';
|
|
@@ -1418,16 +1446,16 @@ class UI extends EventListener {
|
|
|
1418
1446
|
// If a menu is open, and you hover over another menu, open that menu
|
|
1419
1447
|
document.querySelectorAll('.--puter-menubar .menubar-item').forEach(el => el.addEventListener('mouseover', function(e){
|
|
1420
1448
|
const open_menus = document.querySelectorAll('.menubar-item.menubar-item-open');
|
|
1421
|
-
if(open_menus.length > 0 && open_menus[0] !== e.target){
|
|
1449
|
+
if ( open_menus.length > 0 && open_menus[0] !== e.target ){
|
|
1422
1450
|
e.target.dispatchEvent(new Event('mousedown'));
|
|
1423
1451
|
}
|
|
1424
|
-
}))
|
|
1425
|
-
}
|
|
1452
|
+
}));
|
|
1453
|
+
};
|
|
1426
1454
|
|
|
1427
1455
|
on(eventName, callback) {
|
|
1428
1456
|
super.on(eventName, callback);
|
|
1429
1457
|
// If we already received a broadcast for this event, run the callback immediately
|
|
1430
|
-
if (this.#eventNames.includes(eventName) && this.#lastBroadcastValue.has(eventName)) {
|
|
1458
|
+
if ( this.#eventNames.includes(eventName) && this.#lastBroadcastValue.has(eventName) ) {
|
|
1431
1459
|
callback(this.#lastBroadcastValue.get(eventName));
|
|
1432
1460
|
}
|
|
1433
1461
|
}
|
|
@@ -1436,10 +1464,10 @@ class UI extends EventListener {
|
|
|
1436
1464
|
#hideTimeout = null;
|
|
1437
1465
|
|
|
1438
1466
|
showSpinner(html) {
|
|
1439
|
-
if (this.#overlayActive) return;
|
|
1440
|
-
|
|
1467
|
+
if ( this.#overlayActive ) return;
|
|
1468
|
+
|
|
1441
1469
|
// Create and add stylesheet for spinner if it doesn't exist
|
|
1442
|
-
if (!document.getElementById('puter-spinner-styles')) {
|
|
1470
|
+
if ( !document.getElementById('puter-spinner-styles') ) {
|
|
1443
1471
|
const styleSheet = document.createElement('style');
|
|
1444
1472
|
styleSheet.id = 'puter-spinner-styles';
|
|
1445
1473
|
styleSheet.textContent = `
|
|
@@ -1480,10 +1508,10 @@ class UI extends EventListener {
|
|
|
1480
1508
|
`;
|
|
1481
1509
|
document.head.appendChild(styleSheet);
|
|
1482
1510
|
}
|
|
1483
|
-
|
|
1511
|
+
|
|
1484
1512
|
const overlay = document.createElement('div');
|
|
1485
1513
|
overlay.classList.add('puter-loading-overlay');
|
|
1486
|
-
|
|
1514
|
+
|
|
1487
1515
|
const styles = {
|
|
1488
1516
|
position: 'fixed',
|
|
1489
1517
|
top: '0',
|
|
@@ -1495,49 +1523,49 @@ class UI extends EventListener {
|
|
|
1495
1523
|
display: 'flex',
|
|
1496
1524
|
justifyContent: 'center',
|
|
1497
1525
|
alignItems: 'center',
|
|
1498
|
-
pointerEvents: 'all'
|
|
1526
|
+
pointerEvents: 'all',
|
|
1499
1527
|
};
|
|
1500
|
-
|
|
1528
|
+
|
|
1501
1529
|
Object.assign(overlay.style, styles);
|
|
1502
|
-
|
|
1530
|
+
|
|
1503
1531
|
// Create container for spinner and text
|
|
1504
1532
|
const container = document.createElement('div');
|
|
1505
1533
|
container.classList.add('puter-loading-container');
|
|
1506
|
-
|
|
1534
|
+
|
|
1507
1535
|
// Add spinner and text
|
|
1508
1536
|
container.innerHTML = `
|
|
1509
1537
|
<div class="puter-loading-spinner"></div>
|
|
1510
1538
|
<div class="puter-loading-text">${html ?? 'Working...'}</div>
|
|
1511
1539
|
`;
|
|
1512
|
-
|
|
1540
|
+
|
|
1513
1541
|
overlay.appendChild(container);
|
|
1514
1542
|
document.body.appendChild(overlay);
|
|
1515
|
-
|
|
1543
|
+
|
|
1516
1544
|
this.#overlayActive = true;
|
|
1517
1545
|
this.#showTime = Date.now(); // Add show time tracking
|
|
1518
1546
|
this.#overlayTimer = setTimeout(() => {
|
|
1519
1547
|
this.#overlayTimer = null;
|
|
1520
1548
|
}, 1000);
|
|
1521
1549
|
}
|
|
1522
|
-
|
|
1550
|
+
|
|
1523
1551
|
hideSpinner() {
|
|
1524
|
-
if (!this.#overlayActive) return;
|
|
1525
|
-
|
|
1526
|
-
if (this.#overlayTimer) {
|
|
1552
|
+
if ( !this.#overlayActive ) return;
|
|
1553
|
+
|
|
1554
|
+
if ( this.#overlayTimer ) {
|
|
1527
1555
|
clearTimeout(this.#overlayTimer);
|
|
1528
1556
|
this.#overlayTimer = null;
|
|
1529
1557
|
}
|
|
1530
|
-
|
|
1558
|
+
|
|
1531
1559
|
// Calculate how long the spinner has been shown
|
|
1532
1560
|
const elapsedTime = Date.now() - this.#showTime;
|
|
1533
1561
|
const remainingTime = Math.max(0, 1200 - elapsedTime);
|
|
1534
|
-
|
|
1562
|
+
|
|
1535
1563
|
// If less than 1 second has passed, delay the hide
|
|
1536
|
-
if (remainingTime > 0) {
|
|
1537
|
-
if (this.#hideTimeout) {
|
|
1564
|
+
if ( remainingTime > 0 ) {
|
|
1565
|
+
if ( this.#hideTimeout ) {
|
|
1538
1566
|
clearTimeout(this.#hideTimeout);
|
|
1539
1567
|
}
|
|
1540
|
-
|
|
1568
|
+
|
|
1541
1569
|
this.#hideTimeout = setTimeout(() => {
|
|
1542
1570
|
this.#removeSpinner();
|
|
1543
1571
|
}, remainingTime);
|
|
@@ -1545,14 +1573,14 @@ class UI extends EventListener {
|
|
|
1545
1573
|
this.#removeSpinner();
|
|
1546
1574
|
}
|
|
1547
1575
|
}
|
|
1548
|
-
|
|
1576
|
+
|
|
1549
1577
|
// Add private method to handle spinner removal
|
|
1550
1578
|
#removeSpinner() {
|
|
1551
1579
|
const overlay = document.querySelector('.puter-loading-overlay');
|
|
1552
|
-
if (overlay) {
|
|
1580
|
+
if ( overlay ) {
|
|
1553
1581
|
overlay.parentNode?.removeChild(overlay);
|
|
1554
1582
|
}
|
|
1555
|
-
|
|
1583
|
+
|
|
1556
1584
|
this.#overlayActive = false;
|
|
1557
1585
|
this.#showTime = null;
|
|
1558
1586
|
this.#hideTimeout = null;
|
|
@@ -1564,16 +1592,16 @@ class UI extends EventListener {
|
|
|
1564
1592
|
|
|
1565
1593
|
/**
|
|
1566
1594
|
* Gets the current language/locale code (e.g., 'en', 'fr', 'es').
|
|
1567
|
-
*
|
|
1595
|
+
*
|
|
1568
1596
|
* @returns {Promise<string>} A promise that resolves with the current language code.
|
|
1569
|
-
*
|
|
1597
|
+
*
|
|
1570
1598
|
* @example
|
|
1571
1599
|
* const currentLang = await puter.ui.getLanguage();
|
|
1572
1600
|
* console.log(`Current language: ${currentLang}`); // e.g., "Current language: fr"
|
|
1573
1601
|
*/
|
|
1574
1602
|
getLanguage() {
|
|
1575
1603
|
// resolve with the current language code if in GUI environment
|
|
1576
|
-
if(this.env === 'gui'){
|
|
1604
|
+
if ( this.env === 'gui' ){
|
|
1577
1605
|
// resolve with the current language code
|
|
1578
1606
|
return new Promise((resolve) => {
|
|
1579
1607
|
resolve(window.locale);
|
|
@@ -1586,4 +1614,4 @@ class UI extends EventListener {
|
|
|
1586
1614
|
}
|
|
1587
1615
|
}
|
|
1588
1616
|
|
|
1589
|
-
export default UI
|
|
1617
|
+
export default UI;
|