lexgui 0.7.15 → 8.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 (134) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +14 -5
  3. package/build/components/AlertDialog.d.ts +7 -0
  4. package/build/components/ArrayInput.d.ts +9 -0
  5. package/build/components/BaseComponent.d.ts +73 -0
  6. package/build/components/Button.d.ts +14 -0
  7. package/build/components/Calendar.d.ts +41 -0
  8. package/build/components/CalendarRange.d.ts +16 -0
  9. package/build/components/CanvasCurve.d.ts +10 -0
  10. package/build/components/CanvasDial.d.ts +11 -0
  11. package/build/components/CanvasMap2D.d.ts +61 -0
  12. package/build/components/Card.d.ts +8 -0
  13. package/build/components/Checkbox.d.ts +8 -0
  14. package/build/components/Color.d.ts +20 -0
  15. package/build/components/ColorInput.d.ts +13 -0
  16. package/build/components/ColorPicker.d.ts +29 -0
  17. package/build/components/ComboButtons.d.ts +8 -0
  18. package/build/components/ContextMenu.d.ts +16 -0
  19. package/build/components/Counter.d.ts +9 -0
  20. package/build/components/Curve.d.ts +10 -0
  21. package/build/components/DatePicker.d.ts +13 -0
  22. package/build/components/Dial.d.ts +10 -0
  23. package/build/components/Dialog.d.ts +20 -0
  24. package/build/components/DropdownMenu.d.ts +32 -0
  25. package/build/components/FileInput.d.ts +8 -0
  26. package/build/components/Footer.d.ts +14 -0
  27. package/build/components/Form.d.ts +8 -0
  28. package/build/components/Layers.d.ts +9 -0
  29. package/build/components/List.d.ts +9 -0
  30. package/build/components/Map2D.d.ts +12 -0
  31. package/build/components/Menubar.d.ts +59 -0
  32. package/build/components/NodeTree.d.ts +26 -0
  33. package/build/components/NumberInput.d.ts +9 -0
  34. package/build/components/OTPInput.d.ts +8 -0
  35. package/build/components/Pad.d.ts +8 -0
  36. package/build/components/Pagination.d.ts +26 -0
  37. package/build/components/PocketDialog.d.ts +11 -0
  38. package/build/components/Popover.d.ts +20 -0
  39. package/build/components/Progress.d.ts +8 -0
  40. package/build/components/RadioGroup.d.ts +8 -0
  41. package/build/components/RangeInput.d.ts +11 -0
  42. package/build/components/Rate.d.ts +8 -0
  43. package/build/components/Select.d.ts +10 -0
  44. package/build/components/Sheet.d.ts +10 -0
  45. package/build/components/Sidebar.d.ts +84 -0
  46. package/build/components/SizeInput.d.ts +8 -0
  47. package/build/components/Skeleton.d.ts +5 -0
  48. package/build/components/Spinner.d.ts +9 -0
  49. package/build/components/TabSections.d.ts +11 -0
  50. package/build/components/Table.d.ts +34 -0
  51. package/build/components/Tabs.d.ts +20 -0
  52. package/build/components/Tags.d.ts +9 -0
  53. package/build/components/TextArea.d.ts +8 -0
  54. package/build/components/TextInput.d.ts +11 -0
  55. package/build/components/Title.d.ts +8 -0
  56. package/build/components/Toggle.d.ts +8 -0
  57. package/build/components/Tour.d.ts +36 -0
  58. package/build/components/Vector.d.ts +9 -0
  59. package/build/core/Area.d.ts +143 -0
  60. package/build/core/Branch.d.ts +19 -0
  61. package/build/core/Core.d.ts +1 -0
  62. package/build/core/Event.d.ts +26 -0
  63. package/build/core/Icons.d.ts +4 -0
  64. package/build/core/Namespace.d.ts +2 -0
  65. package/build/core/Namespace.js +34 -0
  66. package/build/core/Namespace.js.map +1 -0
  67. package/build/core/Panel.d.ts +538 -0
  68. package/build/core/Utils.d.ts +1 -0
  69. package/build/core/Vec2.d.ts +21 -0
  70. package/build/extensions/AssetView.d.ts +136 -0
  71. package/build/extensions/AssetView.js +1367 -0
  72. package/build/extensions/AssetView.js.map +1 -0
  73. package/build/extensions/Audio.d.ts +9 -0
  74. package/build/extensions/Audio.js +163 -0
  75. package/build/extensions/Audio.js.map +1 -0
  76. package/build/extensions/CodeEditor.d.ts +350 -0
  77. package/build/extensions/CodeEditor.js +5022 -0
  78. package/build/extensions/CodeEditor.js.map +1 -0
  79. package/build/extensions/DocMaker.d.ts +27 -0
  80. package/build/extensions/DocMaker.js +327 -0
  81. package/build/extensions/DocMaker.js.map +1 -0
  82. package/build/extensions/GraphEditor.d.ts +276 -0
  83. package/build/extensions/GraphEditor.js +2770 -0
  84. package/build/extensions/GraphEditor.js.map +1 -0
  85. package/build/extensions/ImUi.d.ts +46 -0
  86. package/build/extensions/ImUi.js +227 -0
  87. package/build/extensions/ImUi.js.map +1 -0
  88. package/build/extensions/Timeline.d.ts +670 -0
  89. package/build/extensions/Timeline.js +3955 -0
  90. package/build/extensions/Timeline.js.map +1 -0
  91. package/build/extensions/VideoEditor.d.ts +128 -0
  92. package/build/extensions/VideoEditor.js +898 -0
  93. package/build/extensions/VideoEditor.js.map +1 -0
  94. package/build/extensions/index.d.ts +8 -0
  95. package/build/extensions/index.js +10 -0
  96. package/build/extensions/index.js.map +1 -0
  97. package/build/index.all.d.ts +2 -0
  98. package/build/index.css.d.ts +4 -0
  99. package/build/index.d.ts +56 -0
  100. package/build/lexgui.all.js +28498 -0
  101. package/build/lexgui.all.js.map +1 -0
  102. package/build/lexgui.all.min.js +1 -0
  103. package/build/lexgui.all.module.js +28422 -0
  104. package/build/lexgui.all.module.js.map +1 -0
  105. package/build/lexgui.all.module.min.js +1 -0
  106. package/build/lexgui.css +939 -346
  107. package/build/lexgui.js +13406 -17286
  108. package/build/lexgui.js.map +1 -0
  109. package/build/lexgui.min.css +3 -10
  110. package/build/lexgui.min.js +1 -1
  111. package/build/lexgui.module.js +12762 -16698
  112. package/build/lexgui.module.js.map +1 -0
  113. package/build/lexgui.module.min.js +1 -1
  114. package/changelog.md +170 -74
  115. package/demo.js +162 -48
  116. package/examples/all-components.html +45 -14
  117. package/examples/asset-view.html +110 -47
  118. package/examples/code-editor.html +5 -5
  119. package/examples/dialogs.html +3 -3
  120. package/examples/editor.html +27 -13
  121. package/examples/index.html +19 -14
  122. package/examples/node-graph.html +2 -2
  123. package/examples/previews/video-editor.png +0 -0
  124. package/examples/timeline.html +1 -1
  125. package/examples/video-editor.html +2 -2
  126. package/package.json +25 -9
  127. package/build/extensions/audio.js +0 -212
  128. package/build/extensions/codeeditor.js +0 -6319
  129. package/build/extensions/docmaker.js +0 -432
  130. package/build/extensions/imui.js +0 -325
  131. package/build/extensions/nodegraph.js +0 -3696
  132. package/build/extensions/timeline.js +0 -4636
  133. package/build/extensions/videoeditor.js +0 -953
  134. package/build/lexgui-docs.css +0 -352
@@ -0,0 +1,1367 @@
1
+ // This is a generated file. Do not edit.
2
+ import { LX } from '../core/Namespace.js';
3
+
4
+ // AssetView.ts @jxarco
5
+ if (!LX) {
6
+ throw ('Missing LX namespace!');
7
+ }
8
+ LX.extensions.push('AssetView');
9
+ const Area = LX.Area;
10
+ LX.Panel;
11
+ LX.NodeTree;
12
+ LX.TreeEvent;
13
+ /**
14
+ * @class AssetView
15
+ * @description Asset container with Tree for file system
16
+ */
17
+ class AssetView {
18
+ static LAYOUT_GRID = 0;
19
+ static LAYOUT_COMPACT = 1;
20
+ static LAYOUT_LIST = 2;
21
+ static CONTENT_SORT_ASC = 0;
22
+ static CONTENT_SORT_DESC = 1;
23
+ root;
24
+ area = null;
25
+ content; // "!" to avoid TS strict property initialization error
26
+ leftPanel = null;
27
+ toolsPanel;
28
+ contentPanel;
29
+ previewPanel;
30
+ tree = null;
31
+ prevData = [];
32
+ nextData = [];
33
+ data = [];
34
+ currentData = [];
35
+ currentFolder = undefined;
36
+ rootItem;
37
+ path = [];
38
+ rootPath = '';
39
+ selectedItem = undefined;
40
+ allowedTypes;
41
+ searchValue = '';
42
+ filter = 'None';
43
+ gridScale = 1.0;
44
+ // Options
45
+ layout = AssetView.LAYOUT_GRID;
46
+ sortMode = AssetView.CONTENT_SORT_ASC;
47
+ skipBrowser = false;
48
+ skipPreview = false;
49
+ useNativeTitle = false;
50
+ onlyFolders = true;
51
+ allowMultipleSelection = false;
52
+ previewActions = [];
53
+ contextMenu = [];
54
+ onRefreshContent = null;
55
+ itemContextMenuOptions = null;
56
+ onItemDragged = null;
57
+ _assetsPerPage = 24;
58
+ get assetsPerPage() {
59
+ return this._assetsPerPage;
60
+ }
61
+ set assetsPerPage(v) {
62
+ this._setAssetsPerPage(v);
63
+ }
64
+ _callbacks = {};
65
+ _lastSortBy = '';
66
+ _paginator;
67
+ _scriptCodeDialog;
68
+ _moveItemDialog;
69
+ _movingItem;
70
+ constructor(options = {}) {
71
+ this.rootPath = 'https://raw.githubusercontent.com/jxarco/lexgui.js/master/';
72
+ this.layout = options.layout ?? this.layout;
73
+ this.sortMode = options.sortMode ?? this.sortMode;
74
+ if (options.rootPath) {
75
+ if (options.rootPath.constructor !== String) {
76
+ console.warn(`Asset Root Path must be a String (now is a ${options.rootPath.constructor.name})`);
77
+ }
78
+ else {
79
+ this.rootPath = options.rootPath;
80
+ }
81
+ }
82
+ let div = document.createElement('div');
83
+ div.className = 'lexassetbrowser';
84
+ this.root = div;
85
+ let area = new Area({ width: '100%', height: '100%' });
86
+ div.appendChild(area.root);
87
+ let left, right, contentArea = area;
88
+ this.skipBrowser = options.skipBrowser ?? this.skipBrowser;
89
+ this.skipPreview = options.skipPreview ?? this.skipPreview;
90
+ this.useNativeTitle = options.useNativeTitle ?? this.useNativeTitle;
91
+ this.onlyFolders = options.onlyFolders ?? this.onlyFolders;
92
+ this.allowMultipleSelection = options.allowMultipleSelection ?? this.allowMultipleSelection;
93
+ this.previewActions = options.previewActions ?? [];
94
+ this.itemContextMenuOptions = options.itemContextMenuOptions;
95
+ this.onRefreshContent = options.onRefreshContent;
96
+ this.onItemDragged = options.onItemDragged;
97
+ this.gridScale = options.gridScale ?? this.gridScale;
98
+ if (this.gridScale !== 1.0) {
99
+ const r = document.querySelector(':root');
100
+ r.style.setProperty('--av-grid-scale', this.gridScale);
101
+ }
102
+ // Append temporarily to the dom
103
+ document.body.appendChild(this.root);
104
+ if (!this.skipBrowser) {
105
+ [left, right] = area.split({ type: 'horizontal', sizes: ['15%', '85%'] });
106
+ contentArea = right;
107
+ left.setLimitBox(210, 0);
108
+ right.setLimitBox(512, 0);
109
+ }
110
+ if (!this.skipPreview) {
111
+ [contentArea, right] = contentArea.split({ type: 'horizontal', sizes: ['80%', '20%'] });
112
+ }
113
+ this.allowedTypes = {
114
+ 'None': {},
115
+ 'Image': { color: 'yellow-500' },
116
+ 'JSON': { color: 'sky-200' },
117
+ 'Video': { color: 'indigo-400' },
118
+ ...(options.allowedTypes ?? {})
119
+ };
120
+ this.path = ['@'];
121
+ this.rootItem = { id: '/', children: this.data, type: 'folder', metadata: { uid: LX.guidGenerator() } };
122
+ this.currentFolder = this.rootItem;
123
+ this._processData(this.data);
124
+ this.currentData = this.data;
125
+ if (!this.skipBrowser) {
126
+ this._createTreePanel(left);
127
+ }
128
+ this._createContentPanel(contentArea);
129
+ // Create resource preview panel
130
+ if (!this.skipPreview) {
131
+ this.previewPanel = right.addPanel({ className: 'lexassetcontentpanel', style: { overflow: 'scroll' } });
132
+ }
133
+ // Clean up
134
+ document.body.removeChild(this.root);
135
+ }
136
+ /**
137
+ * @method on
138
+ * @description Stores an event callback for the desired action
139
+ */
140
+ on(eventName, callback) {
141
+ this._callbacks[eventName] = callback;
142
+ }
143
+ /**
144
+ * @method load
145
+ * @description Loads and processes the input data
146
+ */
147
+ load(data) {
148
+ this.prevData.length = 0;
149
+ this.nextData.length = 0;
150
+ this.data = data;
151
+ // Update root children
152
+ this.rootItem.children = this.data;
153
+ this._processData(this.data);
154
+ this.currentData = this.data;
155
+ this.path = ['@'];
156
+ if (!this.skipBrowser) {
157
+ this.tree.refresh({ id: '/', children: this.data, type: 'folder',
158
+ metadata: { uid: LX.guidGenerator() } });
159
+ }
160
+ this._refreshContent();
161
+ }
162
+ /**
163
+ * @method addItem
164
+ * @description Creates an item DOM element
165
+ */
166
+ addItem(item, childIndex, updateTree = true) {
167
+ const isListLayout = this.layout == AssetView.LAYOUT_LIST;
168
+ const isGridLayout = this.layout == AssetView.LAYOUT_GRID; // default
169
+ const type = item.type.charAt(0).toUpperCase() + item.type.slice(1);
170
+ const extension = LX.getExtension(item.id);
171
+ const isFolder = type === 'Folder';
172
+ const that = this;
173
+ let itemEl = document.createElement('li');
174
+ itemEl.className = 'lexassetitem ' + item.type.toLowerCase();
175
+ itemEl.tabIndex = -1;
176
+ LX.insertChildAtIndex(this.content, itemEl, childIndex);
177
+ const typeColor = this.allowedTypes[type]?.color;
178
+ if (typeColor) {
179
+ // Add type tag
180
+ LX.makeElement('span', `rounded-full w-2 h-2 z-100 flex absolute ml-2 mt-2 bg-${typeColor}`, '', itemEl);
181
+ }
182
+ const metadata = item.metadata;
183
+ if (!metadata.uid) {
184
+ metadata.uid = LX.guidGenerator();
185
+ }
186
+ if (metadata.lastModified && !metadata.lastModifiedDate) {
187
+ metadata.lastModifiedDate = this._lastModifiedToStringDate(metadata.lastModified);
188
+ }
189
+ if (!this.useNativeTitle) {
190
+ let desc = document.createElement('span');
191
+ desc.className = 'lexitemdesc';
192
+ desc.id = `floatingTitle_${metadata.uid}`;
193
+ desc.innerHTML = `File: ${item.id}<br>Type: ${type}`;
194
+ LX.insertChildAtIndex(this.content, desc, childIndex ? childIndex + 1 : undefined);
195
+ itemEl.addEventListener('mousemove', (e) => {
196
+ if (!isGridLayout) {
197
+ return;
198
+ }
199
+ const target = e.target;
200
+ const dialog = itemEl.closest('dialog');
201
+ const rect = itemEl.getBoundingClientRect();
202
+ const targetRect = target.getBoundingClientRect();
203
+ let localOffsetX = rect.x + e.offsetX;
204
+ let localOffsetY = rect.y + e.offsetY;
205
+ if (dialog) {
206
+ const dialogRect = dialog.getBoundingClientRect();
207
+ localOffsetX -= dialogRect.x;
208
+ localOffsetY -= dialogRect.y;
209
+ }
210
+ if (target.classList.contains('lexassettitle')) {
211
+ localOffsetY += targetRect.y - rect.y;
212
+ }
213
+ desc.style.left = localOffsetX + 'px';
214
+ desc.style.top = (localOffsetY - 36) + 'px';
215
+ });
216
+ }
217
+ else {
218
+ itemEl.title = type + ': ' + item.id;
219
+ }
220
+ if (this.allowMultipleSelection) {
221
+ let checkbox = document.createElement('input');
222
+ checkbox.type = 'checkbox';
223
+ checkbox.className = 'lexcheckbox';
224
+ checkbox.checked = metadata.selected;
225
+ checkbox.addEventListener('change', (e) => {
226
+ metadata.selected = !metadata.selected;
227
+ const onCheck = that._callbacks['check'];
228
+ if (onCheck !== undefined) {
229
+ const event = {
230
+ type: 'check',
231
+ items: [item],
232
+ userInitiated: true
233
+ };
234
+ onCheck(event);
235
+ // event.multiple = !!e.shiftKey;
236
+ }
237
+ e.stopPropagation();
238
+ e.stopImmediatePropagation();
239
+ });
240
+ itemEl.appendChild(checkbox);
241
+ }
242
+ let title = document.createElement('span');
243
+ title.className = 'lexassettitle';
244
+ title.innerText = item.id;
245
+ itemEl.appendChild(title);
246
+ if (!this.skipPreview) {
247
+ if (item.type === 'video') {
248
+ const itemVideo = LX.makeElement('video', 'absolute left-0 top-0 w-full border-none pointer-events-none', '', itemEl);
249
+ itemVideo.setAttribute('disablePictureInPicture', false);
250
+ itemVideo.setAttribute('disableRemotePlayback', false);
251
+ itemVideo.setAttribute('loop', true);
252
+ itemVideo.setAttribute('async', true);
253
+ itemVideo.style.transition = 'opacity 0.2s ease-out';
254
+ itemVideo.style.opacity = metadata.preview ? '0' : '1';
255
+ itemVideo.src = item.src;
256
+ itemVideo.volume = metadata.volume ?? 0.4;
257
+ }
258
+ let preview = null;
259
+ const previewSrc = metadata.preview ?? item.src;
260
+ const hasImage = previewSrc && ((() => {
261
+ const ext = LX.getExtension(previewSrc.split('?')[0].split('#')[0]); // get final source without url parameters/anchors
262
+ return ext ? ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'avif'].includes(ext.toLowerCase()) : false;
263
+ })()
264
+ || previewSrc.startsWith('data:image/'));
265
+ if (hasImage || isFolder || !isGridLayout) {
266
+ const defaultPreviewPath = `${this.rootPath}images/file.png`;
267
+ const defaultFolderPath = `${this.rootPath}images/folder.png`;
268
+ preview = document.createElement('img');
269
+ let realSrc = metadata.unknownExtension
270
+ ? defaultPreviewPath
271
+ : (isFolder ? defaultFolderPath : previewSrc);
272
+ preview.src = isGridLayout || isFolder ? realSrc : defaultPreviewPath;
273
+ preview.setAttribute('draggable', 'false');
274
+ preview.className = 'pointer-events-none';
275
+ itemEl.appendChild(preview);
276
+ }
277
+ else {
278
+ preview = document.createElement('svg');
279
+ preview.className = 'asset-file-preview';
280
+ itemEl.appendChild(preview);
281
+ let textEl = document.createElement('text');
282
+ textEl.innerText = (!extension || extension == item.id)
283
+ ? item.type.toUpperCase()
284
+ : (`${extension.toUpperCase()}`); // If no extension, e.g. Clip, use the type...
285
+ preview.appendChild(textEl);
286
+ var newLength = textEl.innerText.length;
287
+ var charsPerLine = 2.5;
288
+ var newEmSize = charsPerLine / newLength;
289
+ var textBaseSize = 64;
290
+ if (newEmSize < 1) {
291
+ var newFontSize = newEmSize * textBaseSize;
292
+ textEl.style.fontSize = newFontSize + 'px';
293
+ preview.style.paddingTop = `calc(50% - ${(textEl.offsetHeight * 0.5 + 10)}px)`;
294
+ }
295
+ }
296
+ }
297
+ // Add item type info
298
+ let itemInfoHtml = type;
299
+ if (isListLayout) {
300
+ if (metadata.bytesize)
301
+ itemInfoHtml += ` | ${LX.formatBytes(metadata.bytesize)}`;
302
+ if (metadata.lastModifiedDate)
303
+ itemInfoHtml += ` | ${metadata.lastModifiedDate}`;
304
+ }
305
+ LX.makeContainer(['auto', 'auto'], 'lexassetinfo', itemInfoHtml, itemEl);
306
+ itemEl.addEventListener('click', function (e) {
307
+ e.stopImmediatePropagation();
308
+ e.stopPropagation();
309
+ const isDoubleClick = e.detail == LX.MOUSE_DOUBLE_CLICK;
310
+ if (!isDoubleClick) {
311
+ if (!e.shiftKey) {
312
+ that.content.querySelectorAll('.lexassetitem').forEach((i) => i.classList.remove('selected'));
313
+ }
314
+ this.classList.add('selected');
315
+ that.selectedItem = item;
316
+ if (!that.skipPreview) {
317
+ that._previewAsset(item);
318
+ }
319
+ }
320
+ else if (isFolder) {
321
+ that._enterFolder(item);
322
+ return;
323
+ }
324
+ const onSelect = that._callbacks['select'];
325
+ const onDblClick = that._callbacks['dblClick'];
326
+ if (isDoubleClick && onDblClick !== undefined) {
327
+ const event = {
328
+ type: 'dbl_click',
329
+ items: [item],
330
+ userInitiated: true
331
+ };
332
+ onDblClick(event);
333
+ // event.multiple = !!e.shiftKey;
334
+ }
335
+ else if (!isDoubleClick && onSelect !== undefined) {
336
+ const event = {
337
+ type: 'select',
338
+ items: [item],
339
+ userInitiated: true
340
+ };
341
+ onSelect(event);
342
+ // event.multiple = !!e.shiftKey;
343
+ }
344
+ });
345
+ itemEl.addEventListener('contextmenu', function (e) {
346
+ e.preventDefault();
347
+ e.stopImmediatePropagation();
348
+ e.stopPropagation();
349
+ const multiple = that.content.querySelectorAll('.selected').length;
350
+ const options = [
351
+ {
352
+ name: (multiple > 1) ? (multiple + ' selected') : item.id,
353
+ icon: LX.makeIcon('CircleSmall', { svgClass: `fill-current fg-${typeColor}` }),
354
+ className: 'text-sm',
355
+ disabled: true
356
+ },
357
+ null
358
+ ];
359
+ if (multiple <= 1) {
360
+ options.push({ name: 'Rename', icon: 'TextCursor',
361
+ callback: that._renameItemPopover.bind(that, item) });
362
+ }
363
+ if (!isFolder) {
364
+ options.push({ name: 'Clone', icon: 'Copy', callback: that._requestCloneItem.bind(that, item) });
365
+ }
366
+ options.push({ name: 'Move', icon: 'FolderInput', callback: () => that._moveItem(item) });
367
+ if (type == 'Script' && LX.has('CodeEditor')) {
368
+ options.push({ name: 'Open in Editor', icon: 'Code',
369
+ callback: that._openScriptInEditor.bind(that, item) });
370
+ }
371
+ if (that.itemContextMenuOptions) {
372
+ options.push(null);
373
+ for (let o of that.itemContextMenuOptions) {
374
+ if (!o.name || !o.callback)
375
+ continue;
376
+ options.push({ name: o.name, icon: o.icon, callback: o.callback?.bind(that, item) });
377
+ }
378
+ }
379
+ options.push(null, { name: 'Delete', icon: 'Trash2', className: 'fg-error',
380
+ callback: that._requestDeleteItem.bind(that, item) });
381
+ LX.addClass(that.contentPanel.root, 'pointer-events-none');
382
+ LX.addDropdownMenu(e.target, options, { side: 'right', align: 'start', event: e, onBlur: () => {
383
+ LX.removeClass(that.contentPanel.root, 'pointer-events-none');
384
+ } });
385
+ });
386
+ const onDrop = function (src, target) {
387
+ const targetType = target.type.charAt(0).toUpperCase() + target.type.slice(1);
388
+ if (!(targetType === 'Folder') || (src.metadata.uid == target.metadata.uid)) {
389
+ console.error('[AssetView Error] Cannot drop: Target item is not a folder or target is the dragged element!');
390
+ return;
391
+ }
392
+ // Animate dragged element
393
+ const draggedEl = src.domEl;
394
+ if (draggedEl) {
395
+ draggedEl.classList.add('moving-to-folder');
396
+ // When animation ends, finalize move
397
+ draggedEl.addEventListener('animationend', () => {
398
+ draggedEl.classList.remove('moving-to-folder');
399
+ that._requestMoveItemToFolder(src, target);
400
+ }, { once: true });
401
+ }
402
+ };
403
+ itemEl.addEventListener('dragstart', (e) => {
404
+ window.__av_item_dragged = item;
405
+ var img = new Image();
406
+ img.src = '';
407
+ if (e.dataTransfer) {
408
+ e.dataTransfer.setDragImage(img, 0, 0);
409
+ e.dataTransfer.effectAllowed = 'move';
410
+ }
411
+ const desc = that.content.querySelector(`#floatingTitle_${metadata.uid}`);
412
+ if (desc)
413
+ desc.style.display = 'none';
414
+ }, false);
415
+ itemEl.addEventListener('dragend', (e) => {
416
+ e.preventDefault(); // Prevent default action (open as link for some elements)
417
+ let dragged = window.__av_item_dragged;
418
+ if (dragged && dragged._nodeTarget) { // We dropped into a NodeTree element
419
+ onDrop(dragged, dragged._nodeTarget);
420
+ }
421
+ delete window.__av_item_dragged;
422
+ }, false);
423
+ itemEl.addEventListener('dragenter', (e) => {
424
+ e.preventDefault(); // Prevent default action (open as link for some elements)
425
+ let dragged = window.__av_item_dragged;
426
+ if (!dragged || !isFolder || (dragged.metadata.uid == metadata.uid))
427
+ return;
428
+ LX.addClass(item.domEl, 'animate-pulse');
429
+ });
430
+ itemEl.addEventListener('dragleave', (e) => {
431
+ e.preventDefault(); // Prevent default action (open as link for some elements)
432
+ let dragged = window.__av_item_dragged;
433
+ if (!dragged) {
434
+ return;
435
+ }
436
+ LX.removeClass(item.domEl, 'animate-pulse');
437
+ });
438
+ itemEl.addEventListener('drop', (e) => {
439
+ e.preventDefault(); // Prevent default action (open as link for some elements)
440
+ let dragged = window.__av_item_dragged;
441
+ if (dragged)
442
+ onDrop(dragged, item);
443
+ });
444
+ itemEl.addEventListener('mouseenter', (e) => {
445
+ if (!that.useNativeTitle && isGridLayout) {
446
+ const desc = that.content.querySelector(`#floatingTitle_${metadata.uid}`);
447
+ if (desc)
448
+ desc.style.display = 'unset';
449
+ }
450
+ if (item.type !== 'video')
451
+ return;
452
+ e.preventDefault();
453
+ const video = itemEl.querySelector('video');
454
+ video.style.opacity = '1';
455
+ video.play();
456
+ });
457
+ itemEl.addEventListener('mouseleave', (e) => {
458
+ if (!that.useNativeTitle && isGridLayout) {
459
+ setTimeout(() => {
460
+ const desc = that.content.querySelector(`#floatingTitle_${metadata.uid}`);
461
+ if (desc)
462
+ desc.style.display = 'none';
463
+ }, 100);
464
+ }
465
+ if (item.type !== 'video')
466
+ return;
467
+ e.preventDefault();
468
+ const video = itemEl.querySelector('video');
469
+ video.pause();
470
+ video.currentTime = 0;
471
+ if (metadata.preview) {
472
+ video.style.opacity = '0';
473
+ }
474
+ });
475
+ if (!this.skipBrowser && updateTree) {
476
+ this.tree.refresh();
477
+ }
478
+ return itemEl;
479
+ }
480
+ /**
481
+ * @method clear
482
+ * @description Creates all AssetView container panels
483
+ */
484
+ clear() {
485
+ if (this.previewPanel) {
486
+ this.previewPanel.clear();
487
+ }
488
+ if (this.leftPanel) {
489
+ this.leftPanel.clear();
490
+ }
491
+ if (this.toolsPanel) {
492
+ this.toolsPanel.clear();
493
+ }
494
+ }
495
+ _processData(data, parent) {
496
+ // Processing an item
497
+ if (data.constructor !== Array) {
498
+ data.parent = parent;
499
+ data.dir = parent?.children;
500
+ data.children = data.children ?? [];
501
+ data.metadata = data.metadata || {};
502
+ }
503
+ // Get the new parent
504
+ const newParent = parent ? data : this.rootItem;
505
+ for (let item of newParent.children) {
506
+ this._processData(item, newParent);
507
+ }
508
+ }
509
+ _updatePath() {
510
+ this.path.length = 0;
511
+ if (this.currentFolder && this.currentFolder.parent) {
512
+ this.path.push(this.currentFolder.id);
513
+ const _pushParentsId = (i) => {
514
+ if (!i)
515
+ return;
516
+ this.path.push(i.parent ? i.id : '@');
517
+ _pushParentsId(i.parent);
518
+ };
519
+ _pushParentsId(this.currentFolder.parent);
520
+ }
521
+ else {
522
+ this.path.push('@');
523
+ }
524
+ LX.emitSignal('@on_folder_change', this.path.reverse().join('/'));
525
+ }
526
+ _createNavigationBar(panel) {
527
+ panel.sameLine(4, 'justify-center');
528
+ panel.addButton(null, 'GoBackButton', () => {
529
+ if (!this.prevData.length || !this.currentFolder)
530
+ return;
531
+ this.nextData.push(this.currentFolder);
532
+ this._enterFolder(this.prevData.pop(), false);
533
+ }, { buttonClass: 'bg-none', title: 'Go Back', tooltip: true, icon: 'ArrowLeft' });
534
+ panel.addButton(null, 'GoForwardButton', () => {
535
+ if (!this.nextData.length || !this.currentFolder)
536
+ return;
537
+ this._enterFolder(this.nextData.pop());
538
+ }, { buttonClass: 'bg-none', title: 'Go Forward', tooltip: true, icon: 'ArrowRight' });
539
+ panel.addButton(null, 'GoUpButton', () => {
540
+ const parentFolder = this.currentFolder?.parent;
541
+ if (parentFolder)
542
+ this._enterFolder(parentFolder);
543
+ }, { buttonClass: 'bg-none', title: 'Go Upper Folder', tooltip: true, icon: 'ArrowUp' });
544
+ panel.addButton(null, 'GoUpButton', () => {
545
+ this._refreshContent();
546
+ }, { buttonClass: 'bg-none', title: 'Refresh', tooltip: true, icon: 'Refresh' });
547
+ }
548
+ _createTreePanel(area) {
549
+ if (this.leftPanel) {
550
+ this.leftPanel.clear();
551
+ }
552
+ else {
553
+ this.leftPanel = area.addPanel({ className: 'lexassetbrowserpanel' });
554
+ }
555
+ this._createNavigationBar(this.leftPanel);
556
+ const treeData = { id: '/', children: this.data };
557
+ const tree = this.leftPanel.addTree('Content Browser', treeData, {
558
+ // icons: tree_icons,
559
+ filter: false,
560
+ onlyFolders: this.onlyFolders,
561
+ onevent: (event) => {
562
+ let node = event.node;
563
+ let value = event.value;
564
+ switch (event.type) {
565
+ case LX.TreeEvent.NODE_SELECTED:
566
+ {
567
+ if (event.multiple) {
568
+ return;
569
+ }
570
+ if (!node.parent) {
571
+ if (this.currentFolder) {
572
+ this.prevData.push(this.currentFolder);
573
+ }
574
+ this.currentFolder = undefined;
575
+ this.currentData = this.data;
576
+ this._refreshContent();
577
+ this._updatePath();
578
+ }
579
+ else {
580
+ this._enterFolder(node.type === 'folder' ? node : node.parent);
581
+ this._previewAsset(node);
582
+ if (node.type !== 'folder') {
583
+ this.content.querySelectorAll('.lexassetitem').forEach((i) => i.classList.remove('selected'));
584
+ const dom = node.domEl;
585
+ dom?.classList.add('selected');
586
+ }
587
+ this.selectedItem = node;
588
+ }
589
+ break;
590
+ }
591
+ case LX.TreeEvent.NODE_DRAGGED:
592
+ {
593
+ if (node.parent) {
594
+ const idx = node.parent.children.indexOf(node);
595
+ node.parent.children.splice(idx, 1);
596
+ }
597
+ if (!value.children) {
598
+ value.children = [];
599
+ }
600
+ value.children.push(node);
601
+ node.parent = value;
602
+ node.dir = value.children;
603
+ if (this.onItemDragged) {
604
+ this.onItemDragged(node, value);
605
+ }
606
+ this._refreshContent();
607
+ break;
608
+ }
609
+ }
610
+ }
611
+ });
612
+ this.tree = tree.innerTree;
613
+ }
614
+ _setContentLayout(layoutMode) {
615
+ this.layout = layoutMode;
616
+ this.toolsPanel.refresh();
617
+ this._refreshContent();
618
+ }
619
+ _createContentPanel(area) {
620
+ const that = this;
621
+ area.root.classList.add('flex', 'flex-col');
622
+ if (this.toolsPanel) {
623
+ this.contentPanel.clear();
624
+ }
625
+ else {
626
+ this.toolsPanel = area.addPanel({ className: 'flex-auto', height: 'auto' });
627
+ this.contentPanel = area.addPanel({
628
+ className: 'lexassetcontentpanel flex flex-col flex-auto-fill content-center overflow-hidden'
629
+ });
630
+ this._paginator = new LX.Pagination({
631
+ className: 'ml-auto',
632
+ pages: Math.max(Math.ceil(this.data.length / this.assetsPerPage), 1),
633
+ onChange: () => this._refreshContent()
634
+ });
635
+ this.contentPanel.root.addEventListener('wheel', (e) => {
636
+ if (!e.ctrlKey)
637
+ return;
638
+ e.preventDefault();
639
+ this.gridScale *= e.deltaY < 0 ? 1.05 : 0.95;
640
+ this.gridScale = LX.clamp(this.gridScale, 0.5, 2.0);
641
+ const r = document.querySelector(':root');
642
+ r.style.setProperty('--av-grid-scale', this.gridScale);
643
+ });
644
+ }
645
+ const _onSort = (value, event) => {
646
+ LX.addDropdownMenu(event.target, [
647
+ { name: 'Name', icon: 'ALargeSmall', callback: () => this._sortData('id') },
648
+ { name: 'Type', icon: 'Type', callback: () => this._sortData('type') },
649
+ null,
650
+ { name: 'Ascending', icon: 'SortAsc',
651
+ callback: () => this._sortData(undefined, AssetView.CONTENT_SORT_ASC) },
652
+ { name: 'Descending', icon: 'SortDesc',
653
+ callback: () => this._sortData(undefined, AssetView.CONTENT_SORT_DESC) }
654
+ ], { side: 'bottom', align: 'start' });
655
+ };
656
+ const _onChangeView = (value, event) => {
657
+ LX.addDropdownMenu(event.target, [
658
+ { name: 'Grid', icon: 'LayoutGrid', callback: () => this._setContentLayout(AssetView.LAYOUT_GRID) },
659
+ { name: 'Compact', icon: 'LayoutList',
660
+ callback: () => this._setContentLayout(AssetView.LAYOUT_COMPACT) },
661
+ { name: 'List', icon: 'List', callback: () => this._setContentLayout(AssetView.LAYOUT_LIST) }
662
+ ], { side: 'bottom', align: 'start' });
663
+ };
664
+ this.toolsPanel.refresh = () => {
665
+ this.toolsPanel.clear();
666
+ const typeEntries = Object.keys(this.allowedTypes);
667
+ // Put it in the content panel if no browser
668
+ if (this.skipBrowser) {
669
+ this._createNavigationBar(this.toolsPanel);
670
+ }
671
+ this.toolsPanel.sameLine();
672
+ const sortButton = this.toolsPanel.addButton(null, '', _onSort.bind(this), { title: 'Sort',
673
+ tooltip: true, icon: (this.sortMode === AssetView.CONTENT_SORT_ASC) ? 'SortAsc' : 'SortDesc' });
674
+ this.toolsPanel.addButton(null, '', _onChangeView.bind(this), { title: 'View', tooltip: true,
675
+ icon: (this.layout === AssetView.LAYOUT_GRID) ? 'LayoutGrid' : 'LayoutList' });
676
+ this.toolsPanel.addSelect(null, typeEntries, this.filter ?? typeEntries[0], (v) => {
677
+ this._refreshContent(undefined, v);
678
+ }, { overflowContainer: null });
679
+ this.toolsPanel.addText(null, this.searchValue ?? '', (v) => this._refreshContent(v), {
680
+ className: 'flex flex-auto-fill',
681
+ placeholder: 'Search assets..'
682
+ });
683
+ this.toolsPanel.endLine();
684
+ if (this._paginator) {
685
+ const inlineContainer = sortButton.root.parentElement;
686
+ inlineContainer.appendChild(this._paginator.root);
687
+ }
688
+ };
689
+ // Start content panel
690
+ this.content = document.createElement('ul');
691
+ this.content.className = 'lexassetscontent';
692
+ this.contentPanel.attach(this.content);
693
+ if (!this.skipBrowser) {
694
+ this.contentPanel.addText(null, this.path.join('/'), null, {
695
+ inputClass: 'bg-none fg-quinary text-end',
696
+ disabled: true,
697
+ signal: '@on_folder_change'
698
+ });
699
+ }
700
+ this.content.addEventListener('dragenter', function (e) {
701
+ e.preventDefault();
702
+ this.classList.add('dragging');
703
+ });
704
+ this.content.addEventListener('dragleave', function (e) {
705
+ e.preventDefault();
706
+ this.classList.remove('dragging');
707
+ });
708
+ this.content.addEventListener('drop', (e) => {
709
+ e.preventDefault();
710
+ this._processDrop(e);
711
+ });
712
+ this.content.addEventListener('click', function () {
713
+ this.querySelectorAll('.lexassetitem').forEach((i) => i.classList.remove('selected'));
714
+ });
715
+ this.content.addEventListener('contextmenu', function (e) {
716
+ e.preventDefault();
717
+ const options = [
718
+ {
719
+ name: 'New Folder',
720
+ icon: LX.makeIcon('FolderPlus'),
721
+ callback: () => {
722
+ that._requestCreateFolder();
723
+ }
724
+ }
725
+ ];
726
+ LX.addClass(that.contentPanel.root, 'pointer-events-none');
727
+ LX.addDropdownMenu(e.target, options, { side: 'right', align: 'start', event: e, onBlur: () => {
728
+ LX.removeClass(that.contentPanel.root, 'pointer-events-none');
729
+ } });
730
+ });
731
+ this._refreshContent();
732
+ // After content to update the size of the content based on the toolbar
733
+ LX.doAsync(() => this.toolsPanel.refresh(), 100);
734
+ }
735
+ _makeNameFilterFn(searchValue) {
736
+ const q = searchValue.trim();
737
+ if (q.includes('*') || q.includes('?')) {
738
+ const regex = LX.wildcardToRegExp(q);
739
+ return (name) => regex.test(name);
740
+ }
741
+ // default case, only check include
742
+ return (name) => name.toLowerCase().includes(q.toLowerCase());
743
+ }
744
+ _refreshContent(searchValue, filter) {
745
+ const isCompactLayout = this.layout == AssetView.LAYOUT_COMPACT;
746
+ const isListLayout = this.layout == AssetView.LAYOUT_LIST;
747
+ this.filter = filter ?? (this.filter ?? 'None');
748
+ this.searchValue = searchValue ?? (this.searchValue ?? '');
749
+ this.content.innerHTML = '';
750
+ this.content.className = `lexassetscontent${isCompactLayout ? ' compact' : (isListLayout ? ' list' : '')}`;
751
+ if (!this.currentData.length) {
752
+ return;
753
+ }
754
+ const fr = new FileReader();
755
+ const nameFilterFn = this._makeNameFilterFn(this.searchValue);
756
+ const filteredData = this.currentData.filter((_i) => {
757
+ const typeMatch = this.filter !== 'None' ? _i.type.toLowerCase() === this.filter.toLowerCase() : true;
758
+ const nameMatch = nameFilterFn(_i.id);
759
+ return typeMatch && nameMatch;
760
+ });
761
+ this._paginator?.setPages(Math.max(Math.ceil(filteredData.length / this.assetsPerPage), 1));
762
+ // Show all data if using filters
763
+ const start = this._paginator ? (this._paginator.page - 1) * this.assetsPerPage : 0;
764
+ const end = this._paginator ? Math.min(start + this.assetsPerPage, filteredData.length) : filteredData.length;
765
+ for (let i = start; i < end; ++i) {
766
+ let item = filteredData[i];
767
+ if (item.path) {
768
+ LX.request({ url: item.path, dataType: 'blob', success: (f) => {
769
+ item.metadata.bytesize = f.size;
770
+ fr.readAsDataURL(f);
771
+ fr.onload = (e) => {
772
+ const target = e.currentTarget;
773
+ item.src = target.result; // This is a base64 string...
774
+ item.metadata.path = item.path;
775
+ delete item.path;
776
+ this._refreshContent(searchValue, filter);
777
+ };
778
+ } });
779
+ }
780
+ else {
781
+ item.domEl = this.addItem(item, undefined, false);
782
+ }
783
+ }
784
+ if (this.onRefreshContent) {
785
+ this.onRefreshContent(searchValue, filter);
786
+ }
787
+ }
788
+ _previewAsset(file) {
789
+ if (this.skipPreview) {
790
+ return;
791
+ }
792
+ const is_base_64 = file.src && file.src.includes('data:image/');
793
+ file.metadata = file.metadata ?? {};
794
+ this.previewPanel.clear();
795
+ this.previewPanel.branch('Asset');
796
+ if (file.type == 'image' || file.src) {
797
+ const hasImage = ['png', 'jpg'].indexOf(LX.getExtension(file.src)) > -1 || is_base_64;
798
+ if (hasImage) {
799
+ this.previewPanel.addImage(null, file.src, { style: { width: '100%' } });
800
+ }
801
+ }
802
+ if (file.metadata.lastModified && !file.metadata.lastModifiedDate) {
803
+ file.metadata.lastModifiedDate = this._lastModifiedToStringDate(file.metadata.lastModified);
804
+ }
805
+ const options = { disabled: true };
806
+ this.previewPanel.addText('Filename', file.id, null, options);
807
+ if (file.metadata.lastModifiedDate) {
808
+ this.previewPanel.addText('Last Modified', file.metadata.lastModifiedDate, null, options);
809
+ }
810
+ if (file.metadata.path || file.src) {
811
+ this.previewPanel.addText('URL', file.metadata.path ? file.metadata.path : file.src, null, options);
812
+ }
813
+ this.previewPanel.addText('Path', this.path.join('/'), null, options);
814
+ this.previewPanel.addText('Type', file.type, null, options);
815
+ if (file.metadata.bytesize) {
816
+ this.previewPanel.addText('Size', LX.formatBytes(file.metadata.bytesize), null, options);
817
+ }
818
+ if (file.type == 'folder') {
819
+ this.previewPanel.addText('Files', file.children ? file.children.length.toString() : '0', null, options);
820
+ }
821
+ this.previewPanel.addSeparator();
822
+ const previewActions = [...this.previewActions];
823
+ if (!previewActions.length && file.type !== 'folder') {
824
+ // By default
825
+ previewActions.push({
826
+ name: 'Download',
827
+ callback: () => LX.downloadURL(file.src, file.id)
828
+ });
829
+ }
830
+ for (let action of previewActions) {
831
+ if (action.type && action.type !== file.type || action.path && action.path !== this.path.join('/')) {
832
+ continue;
833
+ }
834
+ this.previewPanel.addButton(null, action.name, action.callback.bind(this, file));
835
+ }
836
+ this.previewPanel.merge();
837
+ }
838
+ _processDrop(e) {
839
+ if (!e.dataTransfer || !e.dataTransfer.files || e.dataTransfer.files.length == 0) {
840
+ return;
841
+ }
842
+ const fr = new FileReader();
843
+ const num_files = e.dataTransfer.files.length;
844
+ for (let i = 0; i < e.dataTransfer.files.length; ++i) {
845
+ const file = e.dataTransfer.files[i];
846
+ const result = this.currentData.find((e) => e.id === file.name);
847
+ if (result)
848
+ continue;
849
+ fr.readAsDataURL(file);
850
+ fr.onload = (e) => {
851
+ let ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
852
+ let type = null;
853
+ switch (ext) {
854
+ case 'png':
855
+ case 'jpg':
856
+ type = 'image';
857
+ break;
858
+ case 'js':
859
+ case 'css':
860
+ type = 'script';
861
+ break;
862
+ case 'json':
863
+ type = 'json';
864
+ break;
865
+ case 'obj':
866
+ type = 'mesh';
867
+ break;
868
+ default:
869
+ type = ext;
870
+ break;
871
+ }
872
+ let item = {
873
+ id: file.name,
874
+ src: e.currentTarget.result,
875
+ type,
876
+ children: [],
877
+ metadata: {
878
+ extension: ext,
879
+ lastModified: file.lastModified,
880
+ lastModifiedDate: this._lastModifiedToStringDate(file.lastModified),
881
+ unknownExtension: type == ext
882
+ }
883
+ };
884
+ this.currentData.push(item);
885
+ if (i == (num_files - 1)) {
886
+ this._refreshContent();
887
+ this.tree?.refresh(); // Refresh if tree exists
888
+ }
889
+ };
890
+ }
891
+ }
892
+ _sortData(sortBy, sortMode) {
893
+ sortBy = sortBy ?? (this._lastSortBy ?? 'id');
894
+ sortMode = sortMode ?? this.sortMode;
895
+ const sortDesc = sortMode === AssetView.CONTENT_SORT_DESC;
896
+ this.currentData = this.currentData.sort((a, b) => {
897
+ var r = sortDesc ? b[sortBy].localeCompare(a[sortBy]) : a[sortBy].localeCompare(b[sortBy]);
898
+ if (r == 0)
899
+ r = sortDesc ? b['id'].localeCompare(a['id']) : a['id'].localeCompare(b['id']);
900
+ return r;
901
+ });
902
+ this._lastSortBy = sortBy;
903
+ this.sortMode = sortMode;
904
+ this.toolsPanel.refresh();
905
+ this._refreshContent();
906
+ }
907
+ _enterFolder(folderItem, storeCurrent = true) {
908
+ if (!folderItem) {
909
+ return;
910
+ }
911
+ const child = this.currentData[0];
912
+ const sameFolder = child?.parent?.id === folderItem.id;
913
+ if (storeCurrent) {
914
+ this.prevData.push(this.currentFolder ?? {
915
+ id: '/',
916
+ children: this.data,
917
+ type: 'root',
918
+ metadata: {}
919
+ });
920
+ }
921
+ let mustRefresh = !sameFolder;
922
+ const onEnterFolder = this._callbacks['enterFolder'];
923
+ if (onEnterFolder !== undefined) {
924
+ const event = {
925
+ type: 'enter_folder',
926
+ to: folderItem,
927
+ userInitiated: true
928
+ };
929
+ const r = onEnterFolder(event);
930
+ mustRefresh = mustRefresh || r;
931
+ }
932
+ // Update this after the event since the user might have added or modified the data
933
+ this.currentFolder = folderItem;
934
+ this.currentData = this.currentFolder?.children ?? [];
935
+ if (mustRefresh) {
936
+ this._processData(this.data);
937
+ this._refreshContent();
938
+ }
939
+ this._updatePath();
940
+ }
941
+ _removeItemFromParent(item) {
942
+ const oldParent = item.parent;
943
+ if (oldParent) {
944
+ const idx = oldParent.children?.indexOf(item) ?? -1;
945
+ if (idx < 0) {
946
+ return false;
947
+ }
948
+ oldParent.children?.splice(idx, 1);
949
+ }
950
+ else {
951
+ const oldDir = item.dir;
952
+ if (oldDir) {
953
+ const idx = oldDir.indexOf(item);
954
+ if (idx < 0) {
955
+ return false;
956
+ }
957
+ oldDir.splice(idx, 1);
958
+ }
959
+ }
960
+ return true;
961
+ }
962
+ _requestDeleteItem(item) {
963
+ const onBeforeDelete = this._callbacks['beforeDelete'];
964
+ const onDelete = this._callbacks['delete'];
965
+ const resolve = (...args) => {
966
+ this._deleteItem(item);
967
+ const event = {
968
+ type: 'delete',
969
+ items: [item],
970
+ userInitiated: true
971
+ };
972
+ if (onDelete)
973
+ onDelete(event, ...args);
974
+ };
975
+ if (onBeforeDelete) {
976
+ const event = {
977
+ type: 'delete',
978
+ items: [item],
979
+ userInitiated: true
980
+ };
981
+ onBeforeDelete(event, resolve);
982
+ }
983
+ else {
984
+ resolve();
985
+ }
986
+ }
987
+ _deleteItem(item) {
988
+ const ok = this._removeItemFromParent(item);
989
+ if (!ok) {
990
+ console.error('[AssetView Error] Cannot delete. Item not found.');
991
+ return;
992
+ }
993
+ this._refreshContent(this.searchValue, this.filter);
994
+ this.tree?.refresh();
995
+ this.previewPanel?.clear();
996
+ }
997
+ _requestMoveItemToFolder(item, folder) {
998
+ const onBeforeMove = this._callbacks['beforeMove'];
999
+ const onMove = this._callbacks['move'];
1000
+ const resolve = (...args) => {
1001
+ this._moveItemToFolder(item, folder);
1002
+ const event = {
1003
+ type: 'move',
1004
+ items: [item],
1005
+ from: item.parent,
1006
+ to: folder,
1007
+ userInitiated: true
1008
+ };
1009
+ if (onMove)
1010
+ onMove(event, ...args);
1011
+ };
1012
+ if (onBeforeMove) {
1013
+ const event = {
1014
+ type: 'move',
1015
+ items: [item],
1016
+ from: item.parent,
1017
+ to: folder,
1018
+ userInitiated: true
1019
+ };
1020
+ onBeforeMove(event, resolve);
1021
+ }
1022
+ else {
1023
+ resolve();
1024
+ }
1025
+ }
1026
+ _moveItemToFolder(item, folder) {
1027
+ const ok = this._removeItemFromParent(item);
1028
+ if (!ok) {
1029
+ console.error('[AssetView Error] Cannot move. Item not found.');
1030
+ return;
1031
+ }
1032
+ folder.children = folder.children ?? [];
1033
+ folder.children.push(item);
1034
+ item.parent = folder;
1035
+ item.dir = folder.children;
1036
+ this._refreshContent();
1037
+ this.tree?.refresh();
1038
+ this._moveItemDialog?.destroy();
1039
+ this._movingItem = undefined;
1040
+ }
1041
+ _moveItem(item, defaultFolder) {
1042
+ if (this._moveItemDialog) {
1043
+ this._moveItemDialog.destroy();
1044
+ }
1045
+ this._movingItem = item;
1046
+ let targetFolder = null;
1047
+ let bcContainer;
1048
+ const _openFolder = function (p, container, updateBc = true) {
1049
+ container.innerHTML = '';
1050
+ targetFolder = p;
1051
+ for (let pi of (targetFolder.children ?? targetFolder)) {
1052
+ const row = LX.makeContainer(['100%', 'auto'], 'flex flex-row px-1 items-center', '', container);
1053
+ const isFolder = pi.type === 'folder';
1054
+ const rowItem = LX.makeContainer(['100%', 'auto'], `move-item flex flex-row gap-1 py-1 px-3 cursor-pointer ${isFolder ? 'fg-primary font-medium' : 'fg-quinary'} rounded-xxl ${isFolder ? 'hover:bg-secondary' : 'hover:bg-primary'}`, `${isFolder ? LX.makeIcon('FolderOpen', { svgClass: '' }).innerHTML : ''}${pi.id}`, row);
1055
+ if (isFolder) {
1056
+ rowItem.addEventListener('click', () => {
1057
+ container.querySelectorAll('.move-item').forEach((el) => LX.removeClass(el, 'bg-quinary'));
1058
+ LX.addClass(rowItem, 'bg-quinary');
1059
+ targetFolder = pi;
1060
+ });
1061
+ const fPathButton = new LX.Button(null, 'FPathButton', () => {
1062
+ _openFolder(pi, container);
1063
+ }, { icon: 'ChevronRight', className: 'ml-auto h-8', buttonClass: 'bg-none hover:bg-secondary' });
1064
+ row.appendChild(fPathButton.root);
1065
+ }
1066
+ }
1067
+ if (!updateBc) {
1068
+ return;
1069
+ }
1070
+ const path = [];
1071
+ if (targetFolder && targetFolder.parent) {
1072
+ path.push(targetFolder.id);
1073
+ const _pushParentsId = (i) => {
1074
+ if (!i)
1075
+ return;
1076
+ path.push(i.parent ? i.id : '@');
1077
+ _pushParentsId(i.parent);
1078
+ };
1079
+ _pushParentsId(targetFolder.parent);
1080
+ }
1081
+ else {
1082
+ path.push('@');
1083
+ }
1084
+ bcContainer.innerHTML = '';
1085
+ bcContainer.appendChild(LX.makeBreadcrumb(path.reverse().map((p) => {
1086
+ return { title: p };
1087
+ }), {
1088
+ maxItems: 4,
1089
+ separatorIcon: 'ChevronRight'
1090
+ }));
1091
+ };
1092
+ this._moveItemDialog = new LX.Dialog(`Moving: ${item.id}`, (p) => {
1093
+ const area = new LX.Area({ className: 'flex flex-col rounded-lg' });
1094
+ p.attach(area);
1095
+ const content = LX.makeContainer(['auto', '100%'], 'flex flex-auto-fill flex-col overflow-scroll py-2 gap-1', ``);
1096
+ {
1097
+ const headerPanel = area.addPanel({ className: 'p-2 border-bottom flex flex-auto', height: 'auto' });
1098
+ headerPanel.sameLine(2, 'w-full');
1099
+ headerPanel.addButton(null, 'BackButton', () => {
1100
+ if (targetFolder && targetFolder.parent)
1101
+ _openFolder(targetFolder.parent, content);
1102
+ }, { icon: 'ArrowLeft', title: 'Back', tooltip: true, className: 'flex-auto',
1103
+ buttonClass: 'bg-none hover:bg-secondary' });
1104
+ bcContainer = LX.makeElement('div');
1105
+ headerPanel.addContent('ITEM_MOVE_PATH', bcContainer, { signal: '@item_move_path',
1106
+ className: 'flex-auto-fill' });
1107
+ }
1108
+ area.attach(content);
1109
+ _openFolder(defaultFolder ?? this.data, content);
1110
+ {
1111
+ const footerPanel = area.addPanel({ className: 'p-2 border-top flex flex-auto justify-between',
1112
+ height: 'auto' });
1113
+ footerPanel.addButton(null, 'NewFolderButton', () => {
1114
+ this._requestCreateFolder(targetFolder);
1115
+ }, { width: 'auto', icon: 'FolderPlus', title: 'Create Folder', tooltip: true, className: 'ml-2',
1116
+ buttonClass: 'bg-none hover:bg-secondary' });
1117
+ footerPanel.sameLine(2, 'mr-2');
1118
+ footerPanel.addButton(null, 'Cancel', () => {
1119
+ this._moveItemDialog.close();
1120
+ }, { buttonClass: 'bg-none fg-error' });
1121
+ footerPanel.addButton(null, 'Move', () => {
1122
+ this._requestMoveItemToFolder(item, targetFolder);
1123
+ }, { className: '', buttonClass: 'contrast' });
1124
+ }
1125
+ }, { modal: true, size: ['616px', '500px'], closable: true, onBeforeClose: () => {
1126
+ delete this._moveItemDialog;
1127
+ } });
1128
+ }
1129
+ _requestCloneItem(item) {
1130
+ if (item.type === 'folder') {
1131
+ console.error('[AssetView Error] Cannot clone a folder.');
1132
+ return;
1133
+ }
1134
+ const dir = item.dir ?? [];
1135
+ const idx = dir.indexOf(item);
1136
+ if (idx < 0) {
1137
+ console.error('[AssetView Error] Cannot clone. Item not found.');
1138
+ return false;
1139
+ }
1140
+ const onBeforeClone = this._callbacks['beforeClone'];
1141
+ const onClone = this._callbacks['clone'];
1142
+ const resolve = (...args) => {
1143
+ const clonedItem = this._cloneItem(item);
1144
+ const event = {
1145
+ type: 'clone',
1146
+ items: [item],
1147
+ result: [clonedItem],
1148
+ userInitiated: true
1149
+ };
1150
+ if (onClone)
1151
+ onClone(event, ...args);
1152
+ };
1153
+ if (onBeforeClone) {
1154
+ const event = {
1155
+ type: 'clone',
1156
+ items: [item],
1157
+ userInitiated: true
1158
+ };
1159
+ onBeforeClone(event, resolve);
1160
+ }
1161
+ else {
1162
+ resolve();
1163
+ }
1164
+ }
1165
+ _cloneItem(item) {
1166
+ const parent = item.parent;
1167
+ const dir = item.dir ?? [];
1168
+ const idx = dir.indexOf(item);
1169
+ delete item.domEl;
1170
+ delete item.dir;
1171
+ delete item.parent;
1172
+ const newItem = LX.deepCopy(item);
1173
+ newItem.id = this._getClonedName(item.id, dir);
1174
+ newItem.dir = item.dir = dir;
1175
+ newItem.parent = item.parent = parent;
1176
+ newItem.metadata.uid = LX.guidGenerator(); // generate new uid
1177
+ dir.splice(idx + 1, 0, newItem);
1178
+ this._refreshContent(this.searchValue, this.filter);
1179
+ return newItem;
1180
+ }
1181
+ _getClonedName(originalName, siblings) {
1182
+ const dotIndex = originalName.lastIndexOf('.');
1183
+ let base = originalName;
1184
+ let ext = '';
1185
+ if (dotIndex > 0) {
1186
+ base = originalName.substring(0, dotIndex);
1187
+ ext = originalName.substring(dotIndex); // includes the dot
1188
+ }
1189
+ // core name without (N)
1190
+ const match = base.match(/^(.*)\s\((\d+)\)$/);
1191
+ if (match) {
1192
+ base = match[1];
1193
+ }
1194
+ let maxN = -1;
1195
+ for (const s of siblings) {
1196
+ if (!s.id)
1197
+ continue;
1198
+ let sBase = s.id;
1199
+ let sExt = '';
1200
+ const sDot = sBase.lastIndexOf('.');
1201
+ if (sDot > 0) {
1202
+ sExt = sBase.substring(sDot);
1203
+ sBase = sBase.substring(0, sDot);
1204
+ }
1205
+ // Only compare same extension and same base!
1206
+ if (sExt !== ext)
1207
+ continue;
1208
+ const m = sBase.match(new RegExp('^' + LX.escapeRegExp(base) + '\\s\\((\\d+)\\)$'));
1209
+ if (m) {
1210
+ const num = parseInt(m[1]);
1211
+ if (num > maxN)
1212
+ maxN = num;
1213
+ }
1214
+ else if (sBase === base) {
1215
+ // Base name exists without number
1216
+ maxN = Math.max(maxN, 0);
1217
+ }
1218
+ }
1219
+ return maxN === -1 ? originalName : `${base} (${maxN + 1})${ext}`;
1220
+ }
1221
+ _requestRenameItem(item, newName) {
1222
+ const onBeforeRename = this._callbacks['beforeRename'];
1223
+ const onRename = this._callbacks['rename'];
1224
+ const oldName = item.id;
1225
+ const resolve = (...args) => {
1226
+ this._renameItem(item, newName);
1227
+ const event = {
1228
+ type: 'rename',
1229
+ items: [item],
1230
+ oldName,
1231
+ newName,
1232
+ userInitiated: true
1233
+ };
1234
+ if (onRename)
1235
+ onRename(event, ...args);
1236
+ };
1237
+ if (onBeforeRename) {
1238
+ const event = {
1239
+ type: 'rename',
1240
+ items: [item],
1241
+ oldName,
1242
+ newName,
1243
+ userInitiated: true
1244
+ };
1245
+ onBeforeRename(event, resolve);
1246
+ }
1247
+ else {
1248
+ resolve();
1249
+ }
1250
+ }
1251
+ _renameItem(item, newName) {
1252
+ const idx = this.currentData.indexOf(item);
1253
+ if (idx < 0) {
1254
+ return;
1255
+ }
1256
+ const wasSelected = LX.hasClass(item.domEl, 'selected');
1257
+ const hoverTitle = this.content.querySelector(`#floatingTitle_${item.id.replace(/\s/g, '_').replaceAll('.', '_')}`);
1258
+ if (hoverTitle)
1259
+ hoverTitle.remove();
1260
+ item.domEl?.remove();
1261
+ item.id = newName;
1262
+ item.domEl = this.addItem(item, idx * 2);
1263
+ if (wasSelected) {
1264
+ this._previewAsset(item);
1265
+ }
1266
+ this.tree?.refresh();
1267
+ this._processData(this.data);
1268
+ }
1269
+ _renameItemPopover(item) {
1270
+ const idx = this.currentData.indexOf(item);
1271
+ if (idx < 0) {
1272
+ return;
1273
+ }
1274
+ const onRename = (value) => {
1275
+ p.destroy();
1276
+ this._requestRenameItem(item, value);
1277
+ };
1278
+ let newName = item.id;
1279
+ const panel = new LX.Panel();
1280
+ panel.addText(null, item.id, (v, e) => {
1281
+ newName = v;
1282
+ if (e.constructor === KeyboardEvent)
1283
+ onRename(v);
1284
+ });
1285
+ panel.addButton(null, 'Save', () => {
1286
+ onRename(newName);
1287
+ }, { buttonClass: 'contrast' });
1288
+ const p = new LX.Popover(item.domEl, [panel], { align: 'center', side: 'bottom', sideOffset: -128 });
1289
+ }
1290
+ _requestCreateFolder(folder) {
1291
+ folder = folder ?? this.currentFolder;
1292
+ if (!folder) {
1293
+ return;
1294
+ }
1295
+ const onBeforeCreateFolder = this._callbacks['beforeCreateFolder'];
1296
+ const onCreateFolder = this._callbacks['createFolder'];
1297
+ const resolve = (...args) => {
1298
+ const newFolder = this._createFolder(folder);
1299
+ const event = {
1300
+ type: 'create-folder',
1301
+ result: [newFolder],
1302
+ where: folder,
1303
+ userInitiated: true
1304
+ };
1305
+ if (onCreateFolder)
1306
+ onCreateFolder(event, ...args);
1307
+ };
1308
+ if (onBeforeCreateFolder) {
1309
+ const event = {
1310
+ type: 'create-folder',
1311
+ where: folder,
1312
+ userInitiated: true
1313
+ };
1314
+ onBeforeCreateFolder(event, resolve);
1315
+ }
1316
+ else {
1317
+ resolve();
1318
+ }
1319
+ }
1320
+ _createFolder(folder) {
1321
+ folder = folder ?? this.currentFolder;
1322
+ if (!folder) {
1323
+ throw ('_createFolder: Something went wrong!');
1324
+ }
1325
+ const newFolder = {
1326
+ id: this._getClonedName('New Folder', folder.children),
1327
+ type: 'folder',
1328
+ children: [],
1329
+ parent: this.currentFolder,
1330
+ metadata: {}
1331
+ };
1332
+ folder.children.push(newFolder);
1333
+ this._refreshContent();
1334
+ this.tree?.refresh();
1335
+ if (this._moveItemDialog && this._movingItem) {
1336
+ this._moveItem(this._movingItem, folder);
1337
+ }
1338
+ return folder;
1339
+ }
1340
+ _openScriptInEditor(script) {
1341
+ if (this._scriptCodeDialog) {
1342
+ this._scriptCodeDialog.destroy();
1343
+ }
1344
+ this._scriptCodeDialog = new LX.Dialog(null, (p) => {
1345
+ const area = new LX.Area({ className: 'rounded-lg' });
1346
+ p.attach(area);
1347
+ new LX.CodeEditor(area, {
1348
+ allowAddScripts: false,
1349
+ files: [script.src]
1350
+ });
1351
+ }, { size: ['50%', '600px'], closable: true, onBeforeClose: () => {
1352
+ delete this._scriptCodeDialog;
1353
+ } });
1354
+ }
1355
+ _setAssetsPerPage(n) {
1356
+ this._assetsPerPage = n;
1357
+ this._refreshContent();
1358
+ }
1359
+ _lastModifiedToStringDate(lm) {
1360
+ const d = new Date(lm).toLocaleString();
1361
+ return d.substring(0, d.indexOf(','));
1362
+ }
1363
+ }
1364
+ LX.AssetView = AssetView;
1365
+
1366
+ export { AssetView };
1367
+ //# sourceMappingURL=AssetView.js.map