flexdesk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Tree View Component
3
+ *
4
+ * A reusable hierarchical tree view component for displaying nested data.
5
+ * Used by Functions Page (modules/functions) and Data Page (datasets/series).
6
+ *
7
+ * Structure:
8
+ * - TreeView: Container for multiple categories
9
+ * - TreeCategory: Top-level collapsible group (e.g., "Built-in", "Project")
10
+ * - TreeNode: Expandable node with children (e.g., module, dataset)
11
+ * - TreeItem: Leaf item (e.g., function, series)
12
+ */
13
+
14
+ /**
15
+ * Create a tree category (top-level collapsible section)
16
+ * @param {Object} options - Category options
17
+ * @param {string} options.id - Unique category ID
18
+ * @param {string} options.label - Display label
19
+ * @param {string} [options.icon] - Material icon name
20
+ * @param {string} [options.iconClass] - Additional icon CSS class
21
+ * @param {number} [options.count] - Item count to display
22
+ * @param {boolean} [options.expanded=false] - Initial expanded state
23
+ * @param {boolean} [options.readonly=false] - Whether items are readonly
24
+ * @returns {HTMLElement} - Category element
25
+ */
26
+ export function createTreeCategory(options) {
27
+ const {
28
+ id,
29
+ label,
30
+ icon = 'folder',
31
+ iconClass = '',
32
+ count = 0,
33
+ expanded = false,
34
+ readonly = false,
35
+ } = options;
36
+
37
+ const node = document.createElement('div');
38
+ node.className = 'twm-collapsible-box twm-tree-category';
39
+ node.dataset.categoryId = id;
40
+ node.dataset.collapsibleId = `tree-category-${id}`;
41
+ node.dataset.collapsibleDefault = expanded ? 'expanded' : 'collapsed';
42
+ if (readonly) node.dataset.readonly = 'true';
43
+
44
+ node.innerHTML = `
45
+ <div class="twm-collapsible-header" data-collapsible-header="true">
46
+ <button class="twm-arrow-toggle" type="button">
47
+ <span class="twm-collapsible-arrow material-symbols-outlined${expanded ? '' : ' collapsed'}">expand_more</span>
48
+ </button>
49
+ <span class="twm-tree-category__icon ${iconClass}">
50
+ <span class="material-symbols-outlined">${icon}</span>
51
+ </span>
52
+ <span class="twm-tree-category__label">${label}</span>
53
+ <span class="twm-tree-category__count">(${count})</span>
54
+ </div>
55
+ <div class="twm-collapsible-content${expanded ? ' visible' : ''}" data-collapsible-content="true"></div>
56
+ `;
57
+
58
+ return node;
59
+ }
60
+
61
+ /**
62
+ * Create a tree node (expandable container with children)
63
+ * @param {Object} options - Node options
64
+ * @param {string} options.id - Unique node ID
65
+ * @param {string} options.label - Display label
66
+ * @param {string} [options.icon] - Material icon name (optional)
67
+ * @param {number} [options.count] - Child count to display
68
+ * @param {boolean} [options.expanded=false] - Initial expanded state
69
+ * @param {boolean} [options.readonly=false] - Whether node is readonly
70
+ * @param {Object} [options.actions] - Action buttons config
71
+ * @returns {HTMLElement} - Node element
72
+ */
73
+ export function createTreeNode(options) {
74
+ const {
75
+ id,
76
+ label,
77
+ icon = null,
78
+ count = 0,
79
+ expanded = false,
80
+ readonly = false,
81
+ actions = null,
82
+ } = options;
83
+
84
+ const node = document.createElement('div');
85
+ node.className = `twm-tree-node${readonly ? ' readonly' : ''}`;
86
+ node.dataset.nodeId = id;
87
+
88
+ const iconHtml = icon
89
+ ? `<span class="twm-tree-node__icon"><span class="material-symbols-outlined">${icon}</span></span>`
90
+ : '';
91
+
92
+ const actionsHtml = actions ? '<span class="twm-tree-node__actions"></span>' : '';
93
+
94
+ node.innerHTML = `
95
+ <div class="twm-tree-node__header">
96
+ <span class="twm-tree-node__toggle">
97
+ <span class="material-symbols-outlined">${expanded ? 'expand_more' : 'chevron_right'}</span>
98
+ </span>
99
+ ${iconHtml}
100
+ <span class="twm-tree-node__label">${label}</span>
101
+ <span class="twm-tree-node__count">(${count})</span>
102
+ ${actionsHtml}
103
+ </div>
104
+ <div class="twm-tree-node__children" style="display: ${expanded ? 'block' : 'none'};"></div>
105
+ `;
106
+
107
+ // Add action buttons if specified
108
+ if (actions) {
109
+ const actionsEl = node.querySelector('.twm-tree-node__actions');
110
+ Object.entries(actions).forEach(([actionId, actionConfig]) => {
111
+ const btn = document.createElement('button');
112
+ btn.className = 'twm-tree-node__action-btn';
113
+ btn.dataset.action = actionId;
114
+ btn.title = actionConfig.title || actionId;
115
+ btn.innerHTML = `<span class="material-symbols-outlined">${actionConfig.icon}</span>`;
116
+ if (actionConfig.onClick) {
117
+ btn.addEventListener('click', (e) => {
118
+ e.stopPropagation();
119
+ actionConfig.onClick(id, e);
120
+ });
121
+ }
122
+ actionsEl.appendChild(btn);
123
+ });
124
+ }
125
+
126
+ return node;
127
+ }
128
+
129
+ /**
130
+ * Create a tree item (leaf node)
131
+ * @param {Object} options - Item options
132
+ * @param {string} options.id - Unique item ID
133
+ * @param {string} options.label - Display label
134
+ * @param {string} [options.icon] - Material icon or symbol (e.g., 'ƒ', 'insights')
135
+ * @param {boolean} [options.isMaterialIcon=true] - Whether icon is Material icon
136
+ * @param {boolean} [options.readonly=false] - Whether item is readonly
137
+ * @param {boolean} [options.selected=false] - Whether item is selected
138
+ * @param {string} [options.subtitle] - Optional subtitle text
139
+ * @param {Object} [options.data] - Additional data to attach
140
+ * @param {Object} [options.badge] - Optional badge config
141
+ * @param {string} [options.badge.text] - Badge text (if empty, renders as status dot)
142
+ * @param {string} [options.badge.variant] - 'muted'|'pass'|'fail'|'error'
143
+ * @returns {HTMLElement} - Item element
144
+ */
145
+ export function createTreeItem(options) {
146
+ const {
147
+ id,
148
+ label,
149
+ icon = null,
150
+ isMaterialIcon = true,
151
+ readonly = false,
152
+ selected = false,
153
+ subtitle = null,
154
+ data = null,
155
+ badge = null,
156
+ } = options;
157
+
158
+ const item = document.createElement('div');
159
+ item.className = `twm-tree-item${readonly ? ' readonly' : ''}${selected ? ' selected' : ''}`;
160
+ item.dataset.itemId = id;
161
+
162
+ // Attach custom data
163
+ if (data) {
164
+ Object.entries(data).forEach(([key, value]) => {
165
+ item.dataset[key] = value;
166
+ });
167
+ }
168
+
169
+ const iconHtml = icon
170
+ ? isMaterialIcon
171
+ ? `<span class="twm-tree-item__icon"><span class="material-symbols-outlined">${icon}</span></span>`
172
+ : `<span class="twm-tree-item__icon twm-tree-item__icon--symbol">${icon}</span>`
173
+ : '';
174
+
175
+ const subtitleHtml = subtitle
176
+ ? `<span class="twm-tree-item__subtitle">${subtitle}</span>`
177
+ : '';
178
+
179
+ let badgeHtml = '';
180
+ if (badge) {
181
+ const variant = badge.variant || 'muted';
182
+ if (badge.text) {
183
+ badgeHtml = `<span class="twm-tree-item__badge tree-item__badge--${variant}">${badge.text}</span>`;
184
+ } else {
185
+ badgeHtml = `<span class="twm-tree-item__status-dot tree-item__status-dot--${variant}"></span>`;
186
+ }
187
+ }
188
+
189
+ item.innerHTML = `
190
+ ${iconHtml}
191
+ <span class="twm-tree-item__label">${label}</span>
192
+ ${subtitleHtml}
193
+ ${badgeHtml}
194
+ `;
195
+
196
+ return item;
197
+ }
198
+
199
+ /**
200
+ * Toggle a collapsible category
201
+ * @param {HTMLElement} categoryEl - Category element
202
+ * @param {boolean} [force] - Force specific state (true = expand, false = collapse)
203
+ */
204
+ export function toggleCategory(categoryEl, force) {
205
+ if (!categoryEl) return;
206
+
207
+ const arrow = categoryEl.querySelector(':scope > .twm-collapsible-header .twm-collapsible-arrow');
208
+ const content = categoryEl.querySelector(':scope > .twm-collapsible-content');
209
+
210
+ if (!content) return;
211
+
212
+ // Determine whether we should expand
213
+ // force=true means expand, force=false means collapse, undefined means toggle
214
+ const isCurrentlyExpanded = content.classList.contains('visible');
215
+ const shouldExpand = force !== undefined ? force : !isCurrentlyExpanded;
216
+
217
+ if (shouldExpand) {
218
+ // Expand
219
+ if (arrow) {
220
+ arrow.classList.remove('collapsed');
221
+ }
222
+ content.classList.add('visible');
223
+ } else {
224
+ // Collapse
225
+ if (arrow) {
226
+ arrow.classList.add('collapsed');
227
+ }
228
+ content.classList.remove('visible');
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Toggle a tree node
234
+ * @param {HTMLElement} nodeEl - Node element
235
+ * @param {boolean} [force] - Force specific state
236
+ */
237
+ export function toggleNode(nodeEl, force) {
238
+ if (!nodeEl) return;
239
+
240
+ const toggle = nodeEl.querySelector(':scope > .twm-tree-node__header .twm-tree-node__toggle .material-symbols-outlined');
241
+ const children = nodeEl.querySelector(':scope > .twm-tree-node__children');
242
+
243
+ if (!children) return;
244
+
245
+ const isExpanded = force !== undefined
246
+ ? force
247
+ : children.style.display === 'none';
248
+
249
+ if (isExpanded) {
250
+ children.style.display = 'block';
251
+ if (toggle) toggle.textContent = 'expand_more';
252
+ } else {
253
+ children.style.display = 'none';
254
+ if (toggle) toggle.textContent = 'chevron_right';
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Filter tree items by text match
260
+ * @param {HTMLElement} container - Tree container
261
+ * @param {string} filter - Filter text (case-insensitive)
262
+ * @param {Object} [options] - Filter options
263
+ * @param {boolean} [options.expandMatches=true] - Auto-expand nodes with matches
264
+ */
265
+ export function filterTree(container, filter, options = {}) {
266
+ const { expandMatches = true } = options;
267
+ const normalizedFilter = (filter || '').toLowerCase().trim();
268
+
269
+ const items = container.querySelectorAll('.twm-tree-item');
270
+ const nodes = container.querySelectorAll('.twm-tree-node');
271
+ const categories = container.querySelectorAll('.twm-tree-category');
272
+
273
+ // Reset visibility
274
+ items.forEach((item) => {
275
+ const label = item.querySelector('.twm-tree-item__label')?.textContent || '';
276
+ const matches = !normalizedFilter || label.toLowerCase().includes(normalizedFilter);
277
+ item.style.display = matches ? '' : 'none';
278
+ item.classList.toggle('twm-filter-match', matches && !!normalizedFilter);
279
+ });
280
+
281
+ // Show/hide nodes based on visible children
282
+ nodes.forEach((node) => {
283
+ const visibleChildren = node.querySelectorAll('.twm-tree-item:not([style*="display: none"])');
284
+ const hasVisibleChildren = visibleChildren.length > 0;
285
+ node.style.display = hasVisibleChildren ? '' : 'none';
286
+
287
+ // Auto-expand nodes with matches
288
+ if (expandMatches && hasVisibleChildren && normalizedFilter) {
289
+ toggleNode(node, true);
290
+ }
291
+ });
292
+
293
+ // Show/hide categories based on visible nodes
294
+ categories.forEach((category) => {
295
+ const visibleNodes = category.querySelectorAll('.twm-tree-node:not([style*="display: none"])');
296
+ const hasVisibleNodes = visibleNodes.length > 0;
297
+ category.style.display = hasVisibleNodes ? '' : 'none';
298
+
299
+ // Auto-expand categories with matches
300
+ if (expandMatches && hasVisibleNodes && normalizedFilter) {
301
+ toggleCategory(category, true);
302
+ }
303
+ });
304
+ }
305
+
306
+ /**
307
+ * Select a tree item
308
+ * @param {HTMLElement} container - Tree container
309
+ * @param {string} itemId - Item ID to select
310
+ * @param {Object} [options] - Selection options
311
+ * @param {boolean} [options.scrollIntoView=true] - Scroll item into view
312
+ * @param {boolean} [options.expandParents=true] - Expand parent nodes
313
+ */
314
+ export function selectItem(container, itemId, options = {}) {
315
+ const { scrollIntoView = true, expandParents = true } = options;
316
+
317
+ // Deselect all
318
+ container.querySelectorAll('.twm-tree-item.selected').forEach((el) => {
319
+ el.classList.remove('selected');
320
+ });
321
+
322
+ // Find and select item
323
+ const item = container.querySelector(`.twm-tree-item[data-item-id="${itemId}"]`);
324
+ if (!item) return null;
325
+
326
+ item.classList.add('selected');
327
+
328
+ // Expand parent nodes
329
+ if (expandParents) {
330
+ let parent = item.parentElement;
331
+ while (parent && parent !== container) {
332
+ if (parent.classList.contains('twm-tree-node__children')) {
333
+ const node = parent.closest('.twm-tree-node');
334
+ if (node) toggleNode(node, true);
335
+ }
336
+ if (parent.classList.contains('twm-collapsible-content')) {
337
+ const category = parent.closest('.twm-tree-category');
338
+ if (category) toggleCategory(category, true);
339
+ }
340
+ parent = parent.parentElement;
341
+ }
342
+ }
343
+
344
+ // Scroll into view
345
+ if (scrollIntoView) {
346
+ item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
347
+ }
348
+
349
+ return item;
350
+ }
351
+
352
+ /**
353
+ * TreeView class for managing a complete tree structure
354
+ */
355
+ export class TreeView {
356
+ constructor(container, options = {}) {
357
+ this.container = container;
358
+ this.options = {
359
+ onItemClick: null,
360
+ onItemDoubleClick: null,
361
+ onNodeToggle: null,
362
+ onCategoryToggle: null,
363
+ ...options,
364
+ };
365
+
366
+ this._boundHandleClick = this._handleClick.bind(this);
367
+ this._boundHandleDblClick = this._handleDoubleClick.bind(this);
368
+
369
+ if (container) {
370
+ container.classList.add('twm-tree-view');
371
+ container.addEventListener('click', this._boundHandleClick);
372
+ container.addEventListener('dblclick', this._boundHandleDblClick);
373
+ }
374
+ }
375
+
376
+ _handleClick(e) {
377
+ // Handle category toggle
378
+ const collapsibleHeader = e.target.closest('.twm-collapsible-header');
379
+ if (collapsibleHeader) {
380
+ const category = collapsibleHeader.closest('.twm-tree-category');
381
+ if (category) {
382
+ toggleCategory(category);
383
+ this.options.onCategoryToggle?.(category.dataset.categoryId, category);
384
+ }
385
+ return;
386
+ }
387
+
388
+ // Handle node toggle
389
+ const nodeHeader = e.target.closest('.twm-tree-node__header');
390
+ if (nodeHeader) {
391
+ const node = nodeHeader.closest('.twm-tree-node');
392
+ if (node) {
393
+ toggleNode(node);
394
+ this.options.onNodeToggle?.(node.dataset.nodeId, node);
395
+ }
396
+ return;
397
+ }
398
+
399
+ // Handle item click
400
+ const item = e.target.closest('.twm-tree-item');
401
+ if (item) {
402
+ this.select(item.dataset.itemId);
403
+ this.options.onItemClick?.(item.dataset.itemId, item, e);
404
+ }
405
+ }
406
+
407
+ _handleDoubleClick(e) {
408
+ const item = e.target.closest('.twm-tree-item');
409
+ if (item) {
410
+ this.options.onItemDoubleClick?.(item.dataset.itemId, item, e);
411
+ }
412
+ }
413
+
414
+ addCategory(options) {
415
+ const category = createTreeCategory(options);
416
+ this.container.appendChild(category);
417
+ return category;
418
+ }
419
+
420
+ addNode(categoryId, options) {
421
+ const category = this.container.querySelector(`.twm-tree-category[data-category-id="${categoryId}"]`);
422
+ const content = category?.querySelector('.twm-collapsible-content');
423
+ if (!content) return null;
424
+
425
+ const node = createTreeNode(options);
426
+ content.appendChild(node);
427
+ return node;
428
+ }
429
+
430
+ addItem(nodeId, options) {
431
+ const node = this.container.querySelector(`.twm-tree-node[data-node-id="${nodeId}"]`);
432
+ const children = node?.querySelector('.twm-tree-node__children');
433
+ if (!children) return null;
434
+
435
+ const item = createTreeItem(options);
436
+ children.appendChild(item);
437
+ return item;
438
+ }
439
+
440
+ select(itemId) {
441
+ return selectItem(this.container, itemId);
442
+ }
443
+
444
+ filter(text) {
445
+ filterTree(this.container, text);
446
+ }
447
+
448
+ clear() {
449
+ this.container.innerHTML = '';
450
+ }
451
+
452
+ dispose() {
453
+ if (this.container) {
454
+ this.container.removeEventListener('click', this._boundHandleClick);
455
+ this.container.removeEventListener('dblclick', this._boundHandleDblClick);
456
+ }
457
+ }
458
+ }
459
+
460
+ export default TreeView;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * WindowTaskbar - Singleton component that manages minimized windows in the global bottom bar.
3
+ *
4
+ * Listens for window events and renders taskbar buttons for minimized windows.
5
+ * Integrates with the existing global-bottom-bar element.
6
+ */
7
+
8
+ import { ManagedWindow } from './managed_window.js';
9
+
10
+ class WindowTaskbarManager {
11
+ constructor() {
12
+ this._minimizedWindows = new Map(); // id -> { title, icon }
13
+ this._containerEl = null;
14
+ this._leftDivider = null;
15
+ this._rightDivider = null;
16
+ this._initialized = false;
17
+ }
18
+
19
+ /**
20
+ * Initialize the taskbar. Call once after DOM is ready.
21
+ */
22
+ init() {
23
+ if (this._initialized) return;
24
+
25
+ // Find or create the container in the global bottom bar
26
+ const bottomBar = document.querySelector('.twm-global-bottom-bar');
27
+ if (!bottomBar) {
28
+ console.warn('[WindowTaskbar] twm-global-bottom-bar not found');
29
+ return;
30
+ }
31
+
32
+ // Create left divider (always present but hidden when no windows)
33
+ this._leftDivider = document.createElement('div');
34
+ this._leftDivider.className = 'twm-bar-windows__divider twm-bar-windows__divider--left';
35
+
36
+ // Insert bar-windows container between bar-left and bar-center
37
+ this._containerEl = document.createElement('div');
38
+ this._containerEl.className = 'twm-bar-windows';
39
+
40
+ // Create right divider (shown when there are windows)
41
+ this._rightDivider = document.createElement('div');
42
+ this._rightDivider.className = 'twm-bar-windows__divider twm-bar-windows__divider--right';
43
+
44
+ const barCenter = bottomBar.querySelector('.twm-bar-center');
45
+ if (barCenter) {
46
+ bottomBar.insertBefore(this._rightDivider, barCenter);
47
+ bottomBar.insertBefore(this._containerEl, this._rightDivider);
48
+ bottomBar.insertBefore(this._leftDivider, this._containerEl);
49
+ } else {
50
+ bottomBar.appendChild(this._leftDivider);
51
+ bottomBar.appendChild(this._containerEl);
52
+ bottomBar.appendChild(this._rightDivider);
53
+ }
54
+
55
+ // Listen for window events
56
+ window.addEventListener('managed-window-minimized', (e) => this._onWindowMinimized(e));
57
+ window.addEventListener('managed-window-restored', (e) => this._onWindowRestored(e));
58
+ window.addEventListener('managed-window-closed', (e) => this._onWindowClosed(e));
59
+
60
+ this._initialized = true;
61
+ this._updateDividers();
62
+ }
63
+
64
+ /**
65
+ * Handle window minimized event.
66
+ */
67
+ _onWindowMinimized(e) {
68
+ const { id, title, icon } = e.detail;
69
+ this._minimizedWindows.set(id, { title, icon });
70
+ this._render();
71
+ }
72
+
73
+ /**
74
+ * Handle window restored event.
75
+ */
76
+ _onWindowRestored(e) {
77
+ const { id } = e.detail;
78
+ this._minimizedWindows.delete(id);
79
+ this._render();
80
+ }
81
+
82
+ /**
83
+ * Handle window closed event.
84
+ */
85
+ _onWindowClosed(e) {
86
+ const { id } = e.detail;
87
+ this._minimizedWindows.delete(id);
88
+ this._render();
89
+ }
90
+
91
+ /**
92
+ * Update divider visibility based on window count.
93
+ */
94
+ _updateDividers() {
95
+ const hasWindows = this._minimizedWindows.size > 0;
96
+ if (this._leftDivider) {
97
+ this._leftDivider.style.display = hasWindows ? '' : 'none';
98
+ }
99
+ if (this._rightDivider) {
100
+ this._rightDivider.style.display = hasWindows ? '' : 'none';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Render the taskbar buttons.
106
+ */
107
+ _render() {
108
+ if (!this._containerEl) return;
109
+
110
+ this._containerEl.innerHTML = '';
111
+
112
+ for (const [id, { title, icon }] of this._minimizedWindows) {
113
+ const btn = document.createElement('button');
114
+ btn.className = 'twm-bar-windows__item';
115
+ btn.setAttribute('data-window-id', id);
116
+ btn.title = `Restore: ${title}`;
117
+
118
+ // Add icon
119
+ if (icon) {
120
+ const iconEl = document.createElement('span');
121
+ iconEl.className = 'twm-bar-windows__item-icon material-symbols-outlined';
122
+ iconEl.textContent = icon;
123
+ btn.appendChild(iconEl);
124
+ }
125
+
126
+ // Add title
127
+ const titleEl = document.createElement('span');
128
+ titleEl.className = 'twm-bar-windows__item-title';
129
+ titleEl.textContent = title;
130
+ btn.appendChild(titleEl);
131
+
132
+ btn.addEventListener('click', () => {
133
+ ManagedWindow.restore(id);
134
+ });
135
+ this._containerEl.appendChild(btn);
136
+ }
137
+
138
+ this._updateDividers();
139
+ }
140
+ }
141
+
142
+ // Singleton instance
143
+ export const WindowTaskbar = new WindowTaskbarManager();