@api-client/ui 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/.eslintrc +16 -1
  2. package/demo/elements/index.html +3 -0
  3. package/demo/elements/store/file-picker.html +15 -0
  4. package/demo/elements/store/file-picker.ts +134 -0
  5. package/demo/elements/store/index.html +19 -0
  6. package/demo/index.html +3 -0
  7. package/demo/layout/index.html +91 -0
  8. package/demo/layout/index.ts +182 -0
  9. package/demo/store/StorePlugin.js +1 -0
  10. package/dist/bindings/base/StoreBindings.d.ts +5 -0
  11. package/dist/bindings/base/StoreBindings.d.ts.map +1 -1
  12. package/dist/bindings/base/StoreBindings.js +15 -1
  13. package/dist/bindings/base/StoreBindings.js.map +1 -1
  14. package/dist/define/store/file-picker.d.ts +9 -0
  15. package/dist/define/store/file-picker.d.ts.map +1 -0
  16. package/dist/define/store/file-picker.js +10 -0
  17. package/dist/define/store/file-picker.js.map +1 -0
  18. package/dist/define/{files → store}/share-file.d.ts +1 -1
  19. package/dist/define/store/share-file.d.ts.map +1 -0
  20. package/dist/define/{files → store}/share-file.js +2 -2
  21. package/dist/define/store/share-file.js.map +1 -0
  22. package/dist/elements/layout/SplitItem.d.ts +1 -9
  23. package/dist/elements/layout/SplitItem.d.ts.map +1 -1
  24. package/dist/elements/layout/SplitItem.js +27 -20
  25. package/dist/elements/layout/SplitItem.js.map +1 -1
  26. package/dist/elements/layout/SplitLayout.d.ts +16 -14
  27. package/dist/elements/layout/SplitLayout.d.ts.map +1 -1
  28. package/dist/elements/layout/SplitLayout.js +47 -42
  29. package/dist/elements/layout/SplitLayout.js.map +1 -1
  30. package/dist/elements/layout/SplitPanel.d.ts +7 -2
  31. package/dist/elements/layout/SplitPanel.d.ts.map +1 -1
  32. package/dist/elements/layout/SplitPanel.js +130 -52
  33. package/dist/elements/layout/SplitPanel.js.map +1 -1
  34. package/dist/elements/layout/SplitView.d.ts.map +1 -1
  35. package/dist/elements/layout/SplitView.js +18 -14
  36. package/dist/elements/layout/SplitView.js.map +1 -1
  37. package/dist/elements/layout/type.d.ts +3 -3
  38. package/dist/elements/layout/type.d.ts.map +1 -1
  39. package/dist/elements/layout/type.js.map +1 -1
  40. package/dist/elements/store/FilePicker.element.d.ts +87 -0
  41. package/dist/elements/store/FilePicker.element.d.ts.map +1 -0
  42. package/dist/elements/store/FilePicker.element.js +263 -0
  43. package/dist/elements/store/FilePicker.element.js.map +1 -0
  44. package/dist/elements/store/FilePicker.styles.d.ts +3 -0
  45. package/dist/elements/store/FilePicker.styles.d.ts.map +1 -0
  46. package/dist/elements/store/FilePicker.styles.js +72 -0
  47. package/dist/elements/store/FilePicker.styles.js.map +1 -0
  48. package/dist/elements/store/FilesLib.d.ts +10 -0
  49. package/dist/elements/store/FilesLib.d.ts.map +1 -0
  50. package/dist/elements/store/FilesLib.js +38 -0
  51. package/dist/elements/store/FilesLib.js.map +1 -0
  52. package/dist/elements/{files/ShareFile.d.ts → store/ShareFile.element.d.ts} +1 -1
  53. package/dist/elements/store/ShareFile.element.d.ts.map +1 -0
  54. package/dist/elements/{files/ShareFile.js → store/ShareFile.element.js} +1 -1
  55. package/dist/elements/store/ShareFile.element.js.map +1 -0
  56. package/dist/elements/store/ShareFile.styles.d.ts.map +1 -0
  57. package/dist/elements/{files → store}/ShareFile.styles.js.map +1 -1
  58. package/dist/events/EventTypes.d.ts +1 -0
  59. package/dist/events/EventTypes.d.ts.map +1 -1
  60. package/dist/events/EventTypes.js +1 -0
  61. package/dist/events/EventTypes.js.map +1 -1
  62. package/dist/events/Events.d.ts +1 -0
  63. package/dist/events/Events.d.ts.map +1 -1
  64. package/dist/events/StoreEvents.d.ts +8 -1
  65. package/dist/events/StoreEvents.d.ts.map +1 -1
  66. package/dist/events/StoreEvents.js +19 -0
  67. package/dist/events/StoreEvents.js.map +1 -1
  68. package/dist/pages/ApplicationScreen.d.ts +1 -1
  69. package/dist/pages/ApplicationScreen.d.ts.map +1 -1
  70. package/dist/pages/ApplicationScreen.js +4 -2
  71. package/dist/pages/ApplicationScreen.js.map +1 -1
  72. package/dist/pages/api-client/ApiClient.screen.d.ts +0 -6
  73. package/dist/pages/api-client/ApiClient.screen.d.ts.map +1 -1
  74. package/dist/pages/api-client/ApiClient.screen.js +16 -29
  75. package/dist/pages/api-client/ApiClient.screen.js.map +1 -1
  76. package/dist/pages/api-client/Authenticate.screen.d.ts +1 -1
  77. package/dist/pages/api-client/Authenticate.screen.d.ts.map +1 -1
  78. package/dist/pages/api-client/Authenticate.screen.js +2 -2
  79. package/dist/pages/api-client/Authenticate.screen.js.map +1 -1
  80. package/dist/pages/api-client/pages/Files.page.d.ts +6 -35
  81. package/dist/pages/api-client/pages/Files.page.d.ts.map +1 -1
  82. package/dist/pages/api-client/pages/Files.page.js +32 -141
  83. package/dist/pages/api-client/pages/Files.page.js.map +1 -1
  84. package/dist/pages/api-client/pages/Shared.page.d.ts +1 -5
  85. package/dist/pages/api-client/pages/Shared.page.d.ts.map +1 -1
  86. package/dist/pages/api-client/pages/Shared.page.js +1 -40
  87. package/dist/pages/api-client/pages/Shared.page.js.map +1 -1
  88. package/dist/pages/demo/DemoPage.d.ts +7 -0
  89. package/dist/pages/demo/DemoPage.d.ts.map +1 -1
  90. package/dist/pages/demo/DemoPage.js +14 -0
  91. package/dist/pages/demo/DemoPage.js.map +1 -1
  92. package/dist/pages/http-project/HttpClientCommands.d.ts.map +1 -1
  93. package/dist/pages/http-project/HttpClientCommands.js +28 -12
  94. package/dist/pages/http-project/HttpClientCommands.js.map +1 -1
  95. package/dist/store/FileSystem.d.ts +90 -0
  96. package/dist/store/FileSystem.d.ts.map +1 -0
  97. package/dist/store/FileSystem.js +260 -0
  98. package/dist/store/FileSystem.js.map +1 -0
  99. package/dist/styles/global-styles.d.ts.map +1 -1
  100. package/dist/styles/global-styles.js +7 -0
  101. package/dist/styles/global-styles.js.map +1 -1
  102. package/dist/ui/icons/Icons.d.ts +2 -1
  103. package/dist/ui/icons/Icons.d.ts.map +1 -1
  104. package/dist/ui/icons/Icons.js +1 -0
  105. package/dist/ui/icons/Icons.js.map +1 -1
  106. package/dist/ui/list/UiDropdownList.d.ts +9 -1
  107. package/dist/ui/list/UiDropdownList.d.ts.map +1 -1
  108. package/dist/ui/list/UiDropdownList.js +39 -17
  109. package/dist/ui/list/UiDropdownList.js.map +1 -1
  110. package/dist/ui/list/UiList.d.ts +6 -1
  111. package/dist/ui/list/UiList.d.ts.map +1 -1
  112. package/dist/ui/list/UiList.js +24 -9
  113. package/dist/ui/list/UiList.js.map +1 -1
  114. package/dist/ui/table/DataTable.d.ts +4 -0
  115. package/dist/ui/table/DataTable.d.ts.map +1 -1
  116. package/dist/ui/table/DataTable.js +23 -1
  117. package/dist/ui/table/DataTable.js.map +1 -1
  118. package/package.json +2 -1
  119. package/src/bindings/base/StoreBindings.ts +16 -1
  120. package/src/define/store/file-picker.ts +12 -0
  121. package/src/define/{files → store}/share-file.ts +2 -2
  122. package/src/elements/layout/SplitItem.ts +29 -21
  123. package/src/elements/layout/SplitLayout.ts +53 -43
  124. package/src/elements/layout/SplitPanel.ts +140 -57
  125. package/src/elements/layout/SplitView.ts +18 -15
  126. package/src/elements/layout/type.ts +3 -4
  127. package/src/elements/store/FilePicker.element.ts +297 -0
  128. package/src/elements/store/FilePicker.styles.ts +72 -0
  129. package/src/elements/store/FilesLib.ts +32 -0
  130. package/src/events/EventTypes.ts +1 -0
  131. package/src/events/StoreEvents.ts +21 -1
  132. package/src/pages/ApplicationScreen.ts +5 -3
  133. package/src/pages/api-client/ApiClient.screen.ts +16 -31
  134. package/src/pages/api-client/Authenticate.screen.ts +2 -2
  135. package/src/pages/api-client/pages/Files.page.ts +37 -164
  136. package/src/pages/api-client/pages/Shared.page.ts +2 -40
  137. package/src/pages/demo/DemoPage.ts +17 -0
  138. package/src/pages/http-project/HttpClientCommands.ts +28 -12
  139. package/src/store/FileSystem.ts +325 -0
  140. package/src/styles/global-styles.ts +7 -0
  141. package/src/ui/icons/Icons.ts +2 -1
  142. package/src/ui/list/UiDropdownList.ts +44 -17
  143. package/src/ui/list/UiList.ts +26 -10
  144. package/src/ui/table/DataTable.ts +29 -3
  145. package/test/elements/layout/SplitItem.test.ts +76 -75
  146. package/test/elements/layout/SplitLayoutManager.test.ts +70 -69
  147. package/test/elements/layout/SplitPanel.test.ts +10 -7
  148. package/test/elements/store/FilePicker.test.ts +241 -0
  149. package/test/env.js +3 -0
  150. package/test/helpers/StoreHelper.ts +390 -0
  151. package/tsconfig.eslint.json +10 -0
  152. package/web-test-runner.config.mjs +51 -2
  153. package/dist/define/files/share-file.d.ts.map +0 -1
  154. package/dist/define/files/share-file.js.map +0 -1
  155. package/dist/define/layout/layout-panel.d.ts +0 -7
  156. package/dist/define/layout/layout-panel.d.ts.map +0 -1
  157. package/dist/define/layout/layout-panel.js +0 -3
  158. package/dist/define/layout/layout-panel.js.map +0 -1
  159. package/dist/elements/files/ShareFile.d.ts.map +0 -1
  160. package/dist/elements/files/ShareFile.js.map +0 -1
  161. package/dist/elements/files/ShareFile.styles.d.ts.map +0 -1
  162. package/dist/elements/layout/LayoutManager.d.ts +0 -327
  163. package/dist/elements/layout/LayoutManager.d.ts.map +0 -1
  164. package/dist/elements/layout/LayoutManager.js +0 -747
  165. package/dist/elements/layout/LayoutManager.js.map +0 -1
  166. package/dist/elements/layout/LayoutPanelElement.d.ts +0 -62
  167. package/dist/elements/layout/LayoutPanelElement.d.ts.map +0 -1
  168. package/dist/elements/layout/LayoutPanelElement.js +0 -628
  169. package/dist/elements/layout/LayoutPanelElement.js.map +0 -1
  170. package/src/define/layout/layout-panel.ts +0 -9
  171. package/src/elements/layout/LayoutManager.ts +0 -930
  172. package/src/elements/layout/LayoutPanelElement.ts +0 -651
  173. /package/dist/elements/{files → store}/ShareFile.styles.d.ts +0 -0
  174. /package/dist/elements/{files → store}/ShareFile.styles.js +0 -0
  175. /package/src/elements/{files/ShareFile.ts → store/ShareFile.element.ts} +0 -0
  176. /package/src/elements/{files → store}/ShareFile.styles.ts +0 -0
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-param-reassign */
1
2
  /* eslint-disable import/no-cycle */
2
3
  import { html, TemplateResult } from "lit";
3
4
  import { uuidV4 } from "@api-client/core/build/browser.js";
@@ -108,7 +109,7 @@ export class SplitPanel {
108
109
  }
109
110
 
110
111
  getParent(): SplitPanel | undefined {
111
- return this.manager.getParent(this.key);
112
+ return this.manager.getParents(this.key)[0];
112
113
  }
113
114
 
114
115
  /**
@@ -226,6 +227,14 @@ export class SplitPanel {
226
227
  return instance;
227
228
  }
228
229
 
230
+ const panel = this.splitByRegion(region);
231
+ // we don't call request update here because
232
+ // the app has to redo the view rendering first as
233
+ // the panel purpose has changed.
234
+ return panel.addItem(item, { index });
235
+ }
236
+
237
+ protected splitByRegion(region: SplitRegion): SplitPanel {
229
238
  let panel: SplitPanel;
230
239
  if (region === 'east') {
231
240
  [, panel] = this.split({
@@ -252,10 +261,7 @@ export class SplitPanel {
252
261
  noSideEffects: true,
253
262
  });
254
263
  }
255
- // we don't call request update here because
256
- // the app has to redo the view rendering first as
257
- // the panel purpose has changed.
258
- return panel.addItem(item, { index });
264
+ return panel;
259
265
  }
260
266
 
261
267
  /**
@@ -273,11 +279,8 @@ export class SplitPanel {
273
279
  this.items = [];
274
280
  this.selected = undefined;
275
281
 
276
- const p1 = this.addPanel()
277
- const p2 = this.addPanel()
278
-
279
-
280
-
282
+ const p1 = this.addPanel();
283
+ const p2 = this.addPanel();
281
284
 
282
285
  // const p1 = new SplitPanel(this.manager);
283
286
  // const p2 = new SplitPanel(this.manager);
@@ -368,24 +371,19 @@ export class SplitPanel {
368
371
  }
369
372
  }
370
373
 
371
- /**
372
- * Removes an item from the layout
373
- * @param key The `key` of the item.
374
- * @returns The removed item, if any.
375
- */
376
- removeItem(key: string, opts: SplitPanelRemoveItemOptions = {}): SplitItem | undefined {
377
- const removed = this.manager.findItem(key);
378
- if (!removed) {
379
- return undefined;
380
- }
381
- const index = this.items.findIndex(i => i.key === key);
374
+ removeChildItem(item: SplitItem, opts: SplitPanelRemoveItemOptions = {}): SplitItem | undefined {
375
+ const index = this.items.findIndex(i => i.key === item.key);
382
376
  if (index < 0) {
383
377
  return undefined;
384
378
  }
385
379
  const itemInfo = this.items[index];
386
380
  this.items.splice(index, 1);
387
- this.manager.definitions.delete(itemInfo.key);
388
- this.decreaseIndex(removed.index || 0);
381
+ this.decreaseIndex(item.index || 0);
382
+ const parents = this.manager.getParents(item.key);
383
+ if (!parents.length) {
384
+ // remove the definition since no other panel have this item.
385
+ this.manager.definitions.delete(itemInfo.key);
386
+ }
389
387
 
390
388
  if (this.items.length === 0) {
391
389
  // remove panel (self)
@@ -393,29 +391,82 @@ export class SplitPanel {
393
391
  if (parent) {
394
392
  parent.removePanel(this.key);
395
393
  // Note, ignore `opts.noSideEffects` for tab close. It won't get dispatched elsewhere.
396
- this.manager.notifyTabClose(key);
394
+ this.manager.notifyTabClose(item.key, this.key);
397
395
  // no point doing the stuff below as the whole panel is removed.
398
- return removed;
396
+ return item;
399
397
  }
400
398
  }
401
- if (this.selected === key) {
399
+
400
+ if (this.selected === item.key) {
402
401
  let nextKey: string | undefined;
403
402
  if (this.items[index]) {
404
403
  nextKey = this.items[index].key;
405
404
  } else if (this.items[index - 1]) {
406
405
  nextKey = this.items[index - 1].key;
407
406
  } else if (this.items.length) {
408
- const [item] = this.items;
409
- nextKey = item.key;
407
+ const [other] = this.items;
408
+ nextKey = other.key;
410
409
  }
411
410
  this.selected = nextKey;
412
411
  }
412
+
413
413
  if (!opts.noSideEffects) {
414
- this.manager.notifyTabClose(key);
414
+ this.manager.notifyTabClose(item.key, this.key);
415
415
  this.manager.notifyChange();
416
416
  this.manager.updateView(this.key);
417
417
  }
418
- return removed;
418
+ return item;
419
+ }
420
+
421
+ /**
422
+ * Removes an item from the layout
423
+ * @param key The `key` of the item.
424
+ * @returns The removed item, if any.
425
+ */
426
+ removeItem(key: string, opts?: SplitPanelRemoveItemOptions): SplitItem | undefined {
427
+ const removed = this.manager.findItem(key);
428
+ if (!removed) {
429
+ return undefined;
430
+ }
431
+ return this.removeChildItem(removed, opts);
432
+ // const index = this.items.findIndex(i => i.key === key);
433
+ // if (index < 0) {
434
+ // return undefined;
435
+ // }
436
+ // const itemInfo = this.items[index];
437
+ // this.items.splice(index, 1);
438
+ // this.manager.definitions.delete(itemInfo.key);
439
+ // this.decreaseIndex(removed.index || 0);
440
+
441
+ // if (this.items.length === 0) {
442
+ // // remove panel (self)
443
+ // const parent = this.getParent();
444
+ // if (parent) {
445
+ // parent.removePanel(this.key);
446
+ // // Note, ignore `opts.noSideEffects` for tab close. It won't get dispatched elsewhere.
447
+ // this.manager.notifyTabClose(key);
448
+ // // no point doing the stuff below as the whole panel is removed.
449
+ // return removed;
450
+ // }
451
+ // }
452
+ // if (this.selected === key) {
453
+ // let nextKey: string | undefined;
454
+ // if (this.items[index]) {
455
+ // nextKey = this.items[index].key;
456
+ // } else if (this.items[index - 1]) {
457
+ // nextKey = this.items[index - 1].key;
458
+ // } else if (this.items.length) {
459
+ // const [item] = this.items;
460
+ // nextKey = item.key;
461
+ // }
462
+ // this.selected = nextKey;
463
+ // }
464
+ // if (!opts.noSideEffects) {
465
+ // this.manager.notifyTabClose(key);
466
+ // this.manager.notifyChange();
467
+ // this.manager.updateView(this.key);
468
+ // }
469
+ // return removed;
419
470
  }
420
471
 
421
472
  /**
@@ -423,30 +474,30 @@ export class SplitPanel {
423
474
  * @param dir The direction to which close other items. Default to both directions leaving only the `key` item
424
475
  */
425
476
  removeRelative(key: string, dir: SplitCloseDirection = SplitCloseDirection.both): void {
426
- const index = this.items.findIndex(i => i.key === key);
477
+ const items = this.sortedItems();
478
+ const index = items.findIndex(i => i.key === key);
427
479
  if (index < 0) {
428
480
  return;
429
481
  }
430
- // note, even though we keep indexes in the item, the logic actually
431
- // keeps track of them and orders the `items` accordingly.
432
- const item = this.items[index];
433
- let removed: IPanelObject[];
482
+
483
+ const item = items[index];
484
+ let removed: string[] = [];
434
485
  if (dir === SplitCloseDirection.both) {
435
- removed = this.items.filter(i => i !== item);
436
- this.items = [item];
437
- this.selected = item.key;
486
+ removed = items.filter(i => i !== item).map(i => i.key);
438
487
  } else if (dir === SplitCloseDirection.left) {
439
- removed = this.items.slice(0, index);
440
- this.items = this.items.splice(index);
441
- this.selected = item.key;
488
+ removed = items.slice(0, index).map(i => i.key);
442
489
  } else {
443
- removed = this.items.slice(index + 1);
444
- this.items = this.items.splice(0, index + 1);
445
- this.selected = item.key;
490
+ removed = this.items.slice(index + 1).map(i => i.key);
446
491
  }
492
+ this.items = this.items.filter(i => !removed.includes(i.key));
493
+ this.selected = item.key;
494
+
447
495
  removed.forEach(i => {
448
- this.manager.definitions.delete(i.key);
449
- this.manager.notifyTabClose(i.key);
496
+ const parents = this.manager.getParents(i);
497
+ if (!parents.length) {
498
+ this.manager.definitions.delete(i);
499
+ }
500
+ this.manager.notifyTabClose(i, this.key);
450
501
  });
451
502
  this.manager.notifyChange();
452
503
  this.manager.updateView(this.key);
@@ -490,7 +541,7 @@ export class SplitPanel {
490
541
  * @param key The item key
491
542
  * @param toIndex The new index. When not set it moves the item to the end.
492
543
  */
493
- moveItem(key: string, toIndex?: number): void {
544
+ moveItem(key: string, opts: SplitLayoutAddOptions = {}): void {
494
545
  const info = this.items.find(i => i.key === key);
495
546
  if (!info) {
496
547
  return;
@@ -499,22 +550,54 @@ export class SplitPanel {
499
550
  if (!item) {
500
551
  return;
501
552
  }
502
- const hasIndex = typeof toIndex === 'number';
503
- if (hasIndex && item.index === toIndex) {
553
+ const { index, region } = opts;
554
+ if (region) {
555
+ this.moveToRegion(item, region);
556
+ } else if (typeof index === 'number') {
557
+ this.moveToIndex(item, index);
558
+ } else {
559
+ this.moveToEnd(item);
560
+ }
561
+ }
562
+
563
+ moveToRegion(item: SplitItem, region: SplitRegion): void {
564
+ if (this.items.length < 2) {
565
+ // nothing to split.
504
566
  return;
505
567
  }
506
- let hasTargetAtTarget = false;
507
- if (hasIndex) {
508
- hasTargetAtTarget = !!this.items[toIndex];
568
+ if (region === SplitRegion.center) {
569
+ // the item is already there.
570
+ return;
509
571
  }
510
- if (item.index !== undefined) {
511
- this.decreaseIndex(item.index);
572
+ const itemsIndex = this.items.findIndex(i => i.key === item.key);
573
+ if (itemsIndex < 0) {
574
+ return;
575
+ }
576
+ this.items.splice(itemsIndex, 1);
577
+ const index = typeof item.index === 'number' ? item.index : this.nextIndex();
578
+ const panel = this.splitByRegion(region);
579
+ panel.addItem(item, { index });
580
+ }
581
+
582
+ moveToIndex(item: SplitItem, index: number): void {
583
+ if (item.index === index) {
584
+ return;
512
585
  }
513
- const finalIndex = hasIndex ? toIndex as number : this.nextIndex();
586
+ const hasTargetAtTarget = !!this.items[index as number];
587
+ this.decreaseIndex(item.index);
514
588
  if (hasTargetAtTarget) {
515
- this.increaseIndex(finalIndex);
589
+ this.increaseIndex(index);
590
+ }
591
+ item.index = index;
592
+ this.manager.notifyChange();
593
+ this.manager.updateView(this.key);
594
+ }
595
+
596
+ moveToEnd(item: SplitItem): void {
597
+ if (item.index !== undefined) {
598
+ this.decreaseIndex(item.index);
516
599
  }
517
- item.index = finalIndex;
600
+ item.index = this.nextIndex();
518
601
  this.manager.notifyChange();
519
602
  this.manager.updateView(this.key);
520
603
  }
@@ -70,7 +70,7 @@ export default class SplitView extends LitElement {
70
70
  e.stopPropagation();
71
71
  dataTransfer.dropEffect = 'copy';
72
72
  }
73
-
73
+
74
74
  protected handleDragOver(e: DragEvent): void {
75
75
  const { dataTransfer } = e;
76
76
  if (!dataTransfer || !this.panelCanDrop(e)) {
@@ -90,7 +90,8 @@ export default class SplitView extends LitElement {
90
90
 
91
91
  protected handleDrop(e: DragEvent): void {
92
92
  const { dataTransfer } = e;
93
- if (!dataTransfer || !this.panelCanDrop(e)) {
93
+ const { panel } = this;
94
+ if (!dataTransfer || !panel || !this.panelCanDrop(e)) {
94
95
  return;
95
96
  }
96
97
  e.preventDefault();
@@ -100,22 +101,23 @@ export default class SplitView extends LitElement {
100
101
  if (!kind || !key) {
101
102
  return;
102
103
  }
104
+ const layoutKey = dataTransfer.getData('layout/key');
103
105
  this.inDrag = false;
104
- const { panel } = this;
105
- let dispatch = true;
106
- if (panel) {
106
+ // TODO: When the drag originated in another panel then we
107
+ // move the panel between them. Otherwise we add the item to the panel.
108
+ if (layoutKey) {
109
+ // move operation not add.
110
+ panel.manager.moveItem(layoutKey, panel.key, key, { region: this.dragRegion });
111
+ } else {
107
112
  panel.addItem({ key, kind, label: 'New tab' }, { region: this.dragRegion });
108
113
  panel.manager.requestNameUpdate(key);
109
- dispatch = true;
110
- }
111
- if (dispatch) {
112
- this.dispatchEvent(new CustomEvent('datadrop', {
113
- detail: { kind, key, region: this.dragRegion },
114
- composed: true,
115
- bubbles: true,
116
- cancelable: true,
117
- }));
118
114
  }
115
+ this.dispatchEvent(new CustomEvent('datadrop', {
116
+ detail: { kind, key, region: this.dragRegion },
117
+ composed: true,
118
+ bubbles: true,
119
+ cancelable: true,
120
+ }));
119
121
  this.requestUpdate();
120
122
  }
121
123
 
@@ -282,7 +284,7 @@ export default class SplitView extends LitElement {
282
284
  protected moveTab(fromLayout: string, toLayout: string, key: string, toIndex?: number): void {
283
285
  const { panel } = this;
284
286
  if (panel) {
285
- panel.manager.moveItem(fromLayout, toLayout, key, toIndex);
287
+ panel.manager.moveItem(fromLayout, toLayout, key, { index: toIndex });
286
288
  }
287
289
  }
288
290
 
@@ -452,6 +454,7 @@ export default class SplitView extends LitElement {
452
454
  data-index="${index}"
453
455
  data-parent="${ifDefined(parent)}"
454
456
  data-dirty="${isDirty}"
457
+ data-panel="${ifDefined(panel?.key)}"
455
458
  role="tab"
456
459
  class="${classMap(classes)}"
457
460
  draggable="true"
@@ -1,7 +1,6 @@
1
- /* eslint-disable import/no-cycle */
2
- import { TemplateResult } from "lit";
3
- import { SplitItem } from "./SplitItem.js";
4
- import { ISplitLayout } from "./SplitLayout.js";
1
+ import type { TemplateResult } from "lit";
2
+ import type { SplitItem } from "./SplitItem.js";
3
+ import type { ISplitLayout } from "./SplitLayout.js";
5
4
 
6
5
  export enum SplitDirection {
7
6
  horizontal = 'horizontal',
@@ -0,0 +1,297 @@
1
+ import {
2
+ FolderKind, IFile,
3
+ ListFileKind
4
+ } from "@api-client/core/build/browser.js";
5
+ import { CSSResult, html, nothing, TemplateResult } from "lit";
6
+ import { eventOptions, property, query, state } from "lit/decorators.js";
7
+ import { classMap } from "lit/directives/class-map.js";
8
+ import ApiElement from "../ApiElement.js";
9
+ import styles from './FilePicker.styles.js';
10
+ import { fileIcon } from "./FilesLib.js";
11
+ import type UiList from "../../ui/list/UiList.js";
12
+ import type { UiListSelection } from "../../ui/list/UiList.js";
13
+ import { FileSystem } from "../../store/FileSystem.js";
14
+ import '../../define/ui/ui-divider.js';
15
+ import '../../define/ui/ui-button.js';
16
+ import '../../define/ui/ui-icon-button.js';
17
+ import '../../define/ui/ui-icon.js';
18
+ import '../../define/ui/ui-list.js';
19
+ import '../../define/ui/ui-list-item.js';
20
+ import '../../define/ui/ui-progress.js';
21
+
22
+ export interface FilePickerClosingReason {
23
+ /**
24
+ * Whether the picker was canceled.
25
+ */
26
+ canceled: boolean;
27
+ /**
28
+ * The file selected by the user. This is always and only selected when the `canceled` is false.
29
+ */
30
+ file?: IFile;
31
+ }
32
+
33
+ /**
34
+ * A portable element that allow to pick a file from the Store.
35
+ *
36
+ * @fires close - When a file is selected or the user cancelled the dialog. The detail object is the picked file object. Note, this event bubbles.
37
+ * @fires querycomplete - When the page of results was loaded.
38
+ */
39
+ export default class FilePicker extends ApiElement {
40
+ static override get styles(): CSSResult[] {
41
+ return styles;
42
+ }
43
+
44
+ /**
45
+ * The key of the parent folder to query for files.
46
+ * @attribute
47
+ */
48
+ @property({ type: String, hasChanged: () => false })
49
+ get folder(): string | undefined {
50
+ return this.fs.parent;
51
+ }
52
+
53
+ set folder(value: string | undefined) {
54
+ this.fs.selectFolder(value);
55
+ }
56
+
57
+ /**
58
+ * The page limit. Defaults to the store defaults.
59
+ * @attribute
60
+ */
61
+ @property({ type: Number, hasChanged: () => false })
62
+ set limit(value: number | undefined) {
63
+ this.fs.limit = value;
64
+ }
65
+
66
+ get limit(): number | undefined {
67
+ return this.fs.limit;
68
+ }
69
+
70
+ /**
71
+ * The timeout for the page query debouncer.
72
+ * @attribute
73
+ */
74
+ @property({ type: Number, hasChanged: () => false })
75
+ get debounceTimeout(): number {
76
+ return this.fs.debounceTimeout;
77
+ }
78
+
79
+ set debounceTimeout(value: number) {
80
+ this.fs.debounceTimeout = value || 0;
81
+ }
82
+
83
+ /**
84
+ * The list of file kinds to list.
85
+ * Folders are always included.
86
+ */
87
+ @property({ type: Array, hasChanged: () => false })
88
+ get kinds(): ListFileKind[] | undefined {
89
+ return this.fs.kinds;
90
+ }
91
+
92
+ set kinds(value: ListFileKind[] | undefined) {
93
+ this.fs.kinds = value;
94
+ this.fs.resetList();
95
+ this.fs.debounceQuery();
96
+ }
97
+
98
+ @query('ui-list') protected list?: UiList;
99
+
100
+ /**
101
+ * The currently selected in the picker file.
102
+ */
103
+ @state() protected selectedFile?: IFile;
104
+
105
+ @state() errorMessage?: string;
106
+
107
+ /**
108
+ * When set it does not query for files when attached to the DOM.
109
+ * It will still query for files when attributes change. This is primarily used in tests.
110
+ * @attribute
111
+ */
112
+ @property({ type: Boolean }) manualQuery?: boolean;
113
+
114
+ get selectedKey(): string | undefined {
115
+ return this.selectedFile?.key;
116
+ }
117
+
118
+ fs = new FileSystem();
119
+
120
+ constructor() {
121
+ super();
122
+ this.fs.eventsTarget = this;
123
+ this.fs.addEventListener('change', () => this.requestUpdate());
124
+ this.fs.addEventListener('error', (e: Event) => {
125
+ const event = e as CustomEvent<string>;
126
+ this.errorMessage = event.detail;
127
+ });
128
+ this.fs.addEventListener('querycomplete', () => {
129
+ this.dispatchEvent(new Event('querycomplete'));
130
+ });
131
+ this.fs.addEventListener('delete', (e: Event) => {
132
+ const event = e as CustomEvent<string>;
133
+ if (this.selectedFile && this.selectedFile.key === event.detail) {
134
+ this.selectedFile = undefined;
135
+ }
136
+ });
137
+ }
138
+
139
+ override connectedCallback(): void {
140
+ super.connectedCallback();
141
+ this.fs.observe();
142
+ if (!this.manualQuery) {
143
+ this.fs.debounceQuery();
144
+ }
145
+ if (!this.hasAttribute('tabindex')) {
146
+ this.setAttribute('tabindex', '0');
147
+ }
148
+ }
149
+
150
+ override disconnectedCallback(): void {
151
+ super.disconnectedCallback();
152
+ this.fs.unobserve();
153
+ }
154
+
155
+ override focus(options?: FocusOptions | undefined): void {
156
+ const { list } = this;
157
+ if (list) {
158
+ list.focus(options);
159
+ }
160
+ }
161
+
162
+ protected handleFileSelect(e: CustomEvent<UiListSelection>): void {
163
+ // this prevents the dropdown list to close the UI.
164
+ e.preventDefault();
165
+ const file = this.fs.files[e.detail.index];
166
+ if (!file) {
167
+ return;
168
+ }
169
+ if (file.kind === FolderKind) {
170
+ this.fs.selectFolder(file.key);
171
+ this.selectedFile = undefined;
172
+ return;
173
+ }
174
+ this.selectedFile = file;
175
+ }
176
+
177
+ protected handleParentUp(): void {
178
+ this.fs.parentUp();
179
+ this.selectedFile = undefined;
180
+ }
181
+
182
+ protected notifyClose(canceled: boolean): void {
183
+ if (!canceled && !this.selectedFile) {
184
+ return;
185
+ }
186
+ const detail: FilePickerClosingReason = {
187
+ canceled,
188
+ };
189
+ if (!canceled) {
190
+ detail.file = this.selectedFile;
191
+ }
192
+ // this event bubbles so any dropdown on the way can close itself.
193
+ const e = new CustomEvent<FilePickerClosingReason>('close', {
194
+ bubbles: true,
195
+ cancelable: true,
196
+ composed: true,
197
+ detail,
198
+ });
199
+ this.dispatchEvent(e);
200
+ }
201
+
202
+ protected handleSelect(): void {
203
+ this.notifyClose(false);
204
+ }
205
+
206
+ protected handleCancel(): void {
207
+ this.notifyClose(true);
208
+ }
209
+
210
+ @eventOptions({ passive: true })
211
+ protected handleListScroll(e: Event): void {
212
+ const list = e.target as UiList;
213
+ if (this.fs.isListEnd(list)) {
214
+ this.fs.debounceQuery();
215
+ }
216
+ }
217
+
218
+ protected override render(): TemplateResult {
219
+ return html`
220
+ <div class="content">
221
+ ${this.renderHeader()}
222
+ <ui-divider></ui-divider>
223
+ ${this.renderFiles()}
224
+ ${this.errorMessage ? html`<p class="error">${this.errorMessage}</p>` : nothing}
225
+ <ui-divider></ui-divider>
226
+ ${this.renderActions()}
227
+ </div>
228
+ `;
229
+ }
230
+
231
+ protected renderHeader(): TemplateResult {
232
+ const { breadcrumbs = [], reading } = this.fs;
233
+ let content: TemplateResult;
234
+ let withIcon = false;
235
+ if (!breadcrumbs.length) {
236
+ content = html`<span class="title-large label">My files</span>`;
237
+ } else {
238
+ withIcon = true;
239
+ const [parent] = breadcrumbs;
240
+ content = html`
241
+ <ui-icon-button aria-label="Open parent folder" @click="${this.handleParentUp}" class="back-button">
242
+ <ui-icon role="presentation" icon="arrowBack"></ui-icon>
243
+ </ui-icon-button>
244
+ <span class="title-large label">${parent.name}</span>
245
+ `;
246
+ }
247
+ const classes = {
248
+ header: true,
249
+ withIcon,
250
+ }
251
+ return html`
252
+ <div class="${classMap(classes)}">
253
+ ${content}
254
+ ${reading ? html`<ui-progress indeterminate class="progress"></ui-progress>` : nothing}
255
+ </div>
256
+ `;
257
+ }
258
+
259
+ protected renderFiles(): TemplateResult {
260
+ const { files } = this.fs;
261
+ if (!files || !files.length) {
262
+ return this.renderEmptyList();
263
+ }
264
+ return html`
265
+ <ui-list class="list" role="menu" @select="${this.handleFileSelect}" selectActive @scroll="${this.handleListScroll}">
266
+ ${files.map(item => this.renderFileItem(item))}
267
+ </ui-list>
268
+ `;
269
+ }
270
+
271
+ protected renderEmptyList(): TemplateResult {
272
+ return html`
273
+ <p class="no-files body-large">No files in this view.</p>
274
+ `;
275
+ }
276
+
277
+ protected renderFileItem(item: IFile): TemplateResult {
278
+ const icon = fileIcon(item);
279
+ return html`
280
+ <ui-list-item role="menuitem" data-key="${item.key}" data-kind="${item.kind}">
281
+ ${icon ? html`<ui-icon icon="${icon}" class="file-icon"></ui-icon>` : nothing}
282
+ ${item.info.name || 'Unnamed file'}
283
+ </ui-list-item>
284
+ `;
285
+ }
286
+
287
+ protected renderActions(): TemplateResult {
288
+ const { selectedFile } = this;
289
+ const disableSelect = !selectedFile;
290
+ return html`
291
+ <div class="actions">
292
+ <ui-button type="text" @click="${this.handleCancel}" value="cancel">Cancel</ui-button>
293
+ <ui-button type="tonal" ?disabled="${disableSelect}" @click="${this.handleSelect}" value="select">Select</ui-button>
294
+ </div>
295
+ `;
296
+ }
297
+ }