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