@heyputer/puter.js 2.1.2 → 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/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 (values, context) {
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() { return this.#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 = function(name, resolve, args = {}) {
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 = function(name, value) {
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 = async function ({
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', v: 2,
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 (context, { appInstanceID, parentInstanceID }) {
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: "READY",
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: "windowFocused",
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
- items.push(new FSItem(e.data.items[index]))
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
- items.push(new FSItem(e.data.items[index]))
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
- items.push(new FSItem(e.data.items[index]))
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 === "fileSaved"){
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 === "fileSaveCancelled"){
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 === "fileOpenCancelled"){
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 === "itemChanged" && e.data.data && e.data.data.uid){
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: "mouseMoved",
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: "mouseClicked",
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 = function(callback) {
588
+ onWindowClose(callback) {
577
589
  this.#onWindowClose = callback;
578
- }
590
+ };
579
591
 
580
- onItemsOpened = function(callback) {
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
- fpath = '~/' + fpath;
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 = function() {
630
+ wasLaunchedWithItems() {
617
631
  const URLParams = new URLSearchParams(globalThis.location.search);
618
- return URLParams.has('puter.item.name') &&
619
- URLParams.has('puter.item.uid') &&
620
- URLParams.has('puter.item.read_url');
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 = function(callback) {
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
- fpath = '~/' + fpath;
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 = function() {
671
+ requestEmailConfirmation() {
656
672
  return new Promise((resolve, reject) => {
657
673
  this.#postMessageWithCallback('requestEmailConfirmation', resolve, { });
658
674
  });
659
- }
675
+ };
660
676
 
661
- alert = function(message, buttons, options, callback) {
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 = function(callback) {
689
+ instancesOpen(callback) {
668
690
  return new Promise((resolve) => {
669
691
  this.#postMessageWithCallback('getInstancesOpen', resolve, { });
670
- })
671
- }
692
+ });
693
+ };
672
694
 
673
- socialShare = function(url, message, options, callback) {
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 = function(message, placeholder, options, callback) {
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 = function(options, callback){
707
+ showDirectoryPicker(options, callback){
686
708
  return new Promise((resolve, reject) => {
687
- if (!globalThis.open) {
688
- return reject("This API is not compatible in Web Workers.");
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: "showDirectoryPicker",
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
- title,
707
- 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
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 = function(options, callback){
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("This API is not compatible in Web Workers.");
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: "showOpenFilePicker",
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
- title,
739
- 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
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 = function(options){
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 = function(options){
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 = function() {
790
+ requestUpgrade() {
769
791
  return new Promise((resolve) => {
770
792
  this.#postMessageWithCallback('requestUpgrade', resolve, { });
771
- })
772
- }
793
+ });
794
+ };
773
795
 
774
- showSaveFilePicker = function(content, suggestedName, type){
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("This API is not compatible in Web Workers.");
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: "showSaveFilePicker",
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 === "sendMeFileData"){
823
+ if ( e.data?.msg === 'sendMeFileData' ){
802
824
  // Send the blob URL to the host environment
803
825
  e.source.postMessage({
804
- msg: "showSaveFilePickerPopup",
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
- title,
829
- 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
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 = function(title, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(width, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(height, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(width, height, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(x, y, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(y, window_id, callback) {
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 === "object" && window_id !== null){
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 = function(x, window_id, callback) {
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 === "object" && window_id !== null){
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 = function() {
961
+ showWindow() {
940
962
  this.#postMessageWithObject('showWindow');
941
- }
963
+ };
942
964
 
943
- hideWindow = function() {
965
+ hideWindow() {
944
966
  this.#postMessageWithObject('hideWindow');
945
- }
967
+ };
946
968
 
947
- toggleWindow = function() {
969
+ toggleWindow() {
948
970
  this.#postMessageWithObject('toggleWindow');
949
- }
971
+ };
950
972
 
951
- setMenubar = function(spec) {
973
+ setMenubar(spec) {
952
974
  this.#postMessageWithObject('setMenubar', spec);
953
- }
975
+ };
954
976
 
955
- requestPermission = function(options) {
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 = function(item_id) {
969
- this.#postMessageWithObject('disableMenuItem', {id: item_id});
970
- }
990
+ disableMenuItem(item_id) {
991
+ this.#postMessageWithObject('disableMenuItem', { id: item_id });
992
+ };
971
993
 
972
- enableMenuItem = function(item_id) {
973
- this.#postMessageWithObject('enableMenuItem', {id: item_id});
974
- }
994
+ enableMenuItem(item_id) {
995
+ this.#postMessageWithObject('enableMenuItem', { id: item_id });
996
+ };
975
997
 
976
- setMenuItemIcon = function(item_id, icon) {
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 = function(item_id, icon) {
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 = function(item_id, checked) {
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 = function(spec) {
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.isDirectory) {
1067
- const files = await readDir(entry, path)
1068
- files.push(entry)
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 = function() {
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 (app_name) {
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 = function (options, callback) {
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 = function(){
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 = "none");
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 = "none";
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 (e) {
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;