@oat-sa/tao-core-ui 3.23.0 → 3.24.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/dist/figure/FigureStateActive.js +64 -32
- package/dist/image/ImgStateActive/initHelper.js +64 -32
- package/dist/image/ImgStateActive.js +64 -32
- package/dist/previewer.js +1 -1
- package/dist/resourcemgr/assetSearch.js +793 -0
- package/dist/resourcemgr/assetSearchContract.js +477 -0
- package/dist/resourcemgr/css/resourcemgr.css +1137 -97
- package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
- package/dist/resourcemgr/fileBrowser.js +456 -79
- package/dist/resourcemgr/filePreview.js +40 -4
- package/dist/resourcemgr/fileSelector.js +362 -92
- package/dist/resourcemgr.js +309 -57
- package/dist/searchModal/advancedSearch.js +233 -31
- package/package.json +15 -6
- package/src/image/ImgStateActive/initHelper.js +69 -31
- package/src/previewer.js +1 -1
- package/src/resourcemgr/assetSearch.js +847 -0
- package/src/resourcemgr/assetSearchContract.js +489 -0
- package/src/resourcemgr/css/resourcemgr.css +1137 -97
- package/src/resourcemgr/css/resourcemgr.css.map +1 -1
- package/src/resourcemgr/fileBrowser.js +470 -91
- package/src/resourcemgr/filePreview.js +46 -4
- package/src/resourcemgr/fileSelector.js +338 -46
- package/src/resourcemgr/scss/resourcemgr.scss +1351 -88
- package/src/resourcemgr/tpl/advanced-search.tpl +10 -0
- package/src/resourcemgr/tpl/fileSelect.tpl +25 -28
- package/src/resourcemgr/tpl/folder.tpl +4 -1
- package/src/resourcemgr/tpl/layout.tpl +113 -54
- package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
- package/src/resourcemgr.js +289 -47
- package/src/searchModal/advancedSearch.js +242 -31
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
define(['jquery', 'lodash', '
|
|
1
|
+
define(['jquery', 'lodash', 'ui/pagination', 'handlebars', 'lib/handlebars/helpers', 'core/logger', 'ui/resourcemgr/assetSearchContract'], function ($$1, _, paginationComponent, Handlebars, Helpers0, loggerFactory, assetSearchContract) { 'use strict';
|
|
2
2
|
|
|
3
3
|
$$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
|
|
4
4
|
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
|
|
5
|
-
request = request && Object.prototype.hasOwnProperty.call(request, 'default') ? request['default'] : request;
|
|
6
5
|
paginationComponent = paginationComponent && Object.prototype.hasOwnProperty.call(paginationComponent, 'default') ? paginationComponent['default'] : paginationComponent;
|
|
7
6
|
Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
|
|
8
7
|
Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
|
|
8
|
+
loggerFactory = loggerFactory && Object.prototype.hasOwnProperty.call(loggerFactory, 'default') ? loggerFactory['default'] : loggerFactory;
|
|
9
9
|
|
|
10
10
|
if (!Helpers0.__initialized) {
|
|
11
11
|
Helpers0(Handlebars);
|
|
@@ -14,12 +14,15 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
14
14
|
var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
|
|
15
15
|
this.compilerInfo = [4,'>= 1.0.0'];
|
|
16
16
|
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
|
|
17
|
-
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression
|
|
17
|
+
var buffer = "", stack1, self=this, functionType="function", escapeExpression=this.escapeExpression;
|
|
18
18
|
|
|
19
19
|
function program1(depth0,data) {
|
|
20
20
|
|
|
21
21
|
var buffer = "", stack1, helper;
|
|
22
|
-
buffer += "\n <li class=\"root\">\n <a\n class=\"root-folder
|
|
22
|
+
buffer += "\n <li class=\"root\">\n <a\n class=\"root-folder";
|
|
23
|
+
stack1 = helpers.unless.call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
|
|
24
|
+
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
25
|
+
buffer += "\"\n href=\"#\"\n data-path=\"";
|
|
23
26
|
if (helper = helpers.path) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
|
24
27
|
else { helper = (depth0 && depth0.path); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
|
25
28
|
buffer += escapeExpression(stack1)
|
|
@@ -31,13 +34,27 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
31
34
|
if (helper = helpers.childrenLimit) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
|
32
35
|
else { helper = (depth0 && depth0.childrenLimit); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
|
33
36
|
buffer += escapeExpression(stack1)
|
|
34
|
-
+ "\"\n data-level=\"0\"\n style=\"--tree-level: 0;\">\n ";
|
|
37
|
+
+ "\"\n data-level=\"0\"\n style=\"--tree-level: 0;\">\n <span class=\"tree-toggle";
|
|
38
|
+
stack1 = helpers['if'].call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
|
|
39
|
+
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
40
|
+
buffer += "\" aria-hidden=\"true\"></span>\n <span class=\"tree-folder icon-folder\" aria-hidden=\"true\"></span>\n ";
|
|
35
41
|
if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
|
36
42
|
else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
|
37
43
|
buffer += escapeExpression(stack1)
|
|
38
44
|
+ "\n </a>\n <ul></ul>\n </li>\n";
|
|
39
45
|
return buffer;
|
|
40
46
|
}
|
|
47
|
+
function program2(depth0,data) {
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
return " is-leaf";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function program4(depth0,data) {
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
return " icon-right";
|
|
57
|
+
}
|
|
41
58
|
|
|
42
59
|
stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.permissions)),stack1 == null || stack1 === false ? stack1 : stack1.read), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
|
|
43
60
|
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
@@ -81,13 +98,30 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
81
98
|
if (helper = helpers.level) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
|
82
99
|
else { helper = (depth0 && depth0.level); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
|
83
100
|
buffer += escapeExpression(stack1)
|
|
84
|
-
+ ";\"\n href=\"#\"
|
|
101
|
+
+ ";\"\n href=\"#\"\n ";
|
|
102
|
+
stack1 = helpers.unless.call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
|
|
103
|
+
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
104
|
+
buffer += ">\n <span class=\"tree-toggle";
|
|
105
|
+
stack1 = helpers['if'].call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
|
|
106
|
+
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
107
|
+
buffer += "\" aria-hidden=\"true\"></span>\n <span class=\"tree-folder icon-folder\" aria-hidden=\"true\"></span>\n ";
|
|
85
108
|
if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
|
86
109
|
else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
|
87
110
|
buffer += escapeExpression(stack1)
|
|
88
111
|
+ "\n </a>\n </li>\n";
|
|
89
112
|
return buffer;
|
|
90
113
|
}
|
|
114
|
+
function program2(depth0,data) {
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
return "class=\"is-leaf\"";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function program4(depth0,data) {
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
return " icon-right";
|
|
124
|
+
}
|
|
91
125
|
|
|
92
126
|
stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.permissions)),stack1 == null || stack1 === false ? stack1 : stack1.read), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
|
|
93
127
|
if(stack1 || stack1 === 0) { buffer += stack1; }
|
|
@@ -164,18 +198,25 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
164
198
|
*
|
|
165
199
|
* You should have received a copy of the GNU General Public License
|
|
166
200
|
* along with this program; if not, write to the Free Software
|
|
167
|
-
* Foundation, Inc.,
|
|
201
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
168
202
|
*
|
|
169
|
-
* Copyright (c) 2018-
|
|
203
|
+
* Copyright (c) 2018-2026 (original work) Open Assessment Technologies SA;
|
|
170
204
|
*/
|
|
171
|
-
const
|
|
205
|
+
const NS = 'resourcemgr';
|
|
206
|
+
const LOGGER = loggerFactory(`ui/${NS}`);
|
|
207
|
+
const DEFAULT_AJAX_TIMEOUT_MS = 10000;
|
|
172
208
|
function fileBrowser (options) {
|
|
209
|
+
if (!options.browseUrl && options.url) {
|
|
210
|
+
options.browseUrl = options.url;
|
|
211
|
+
}
|
|
173
212
|
const root = options.root || 'local';
|
|
174
213
|
const rootPath = options.path || '/';
|
|
214
|
+
const initialPath = options.initialPath || rootPath;
|
|
175
215
|
const $container = options.$target;
|
|
176
216
|
const $fileBrowser = $$1('.file-browser .file-browser-wrapper', $container);
|
|
177
217
|
const $divContainer = $$1(`.${root}`, $fileBrowser);
|
|
178
218
|
const $folderContainer = $$1('.folders', $divContainer);
|
|
219
|
+
const $paginationContainer = $$1('.pagination-bottom', $container);
|
|
179
220
|
const fileTree = {};
|
|
180
221
|
// for pagination
|
|
181
222
|
let selectedClass = {
|
|
@@ -184,25 +225,81 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
184
225
|
total: 0,
|
|
185
226
|
page: 1
|
|
186
227
|
};
|
|
228
|
+
let searchMode = false;
|
|
229
|
+
let sort = Object.assign({}, assetSearchContract.DEFAULT_SORT);
|
|
230
|
+
const ajaxTimeoutMs = Number.isFinite(Number(options.ajaxTimeoutMs)) && Number(options.ajaxTimeoutMs) > 0 ? Number(options.ajaxTimeoutMs) : DEFAULT_AJAX_TIMEOUT_MS;
|
|
231
|
+
$container.on(`searchmode.${NS}`, function (e, enabled) {
|
|
232
|
+
searchMode = !!enabled;
|
|
233
|
+
});
|
|
234
|
+
$container.on(`sortchange.${NS}`, function (e, nextSort) {
|
|
235
|
+
sort = Object.assign({}, assetSearchContract.DEFAULT_SORT, nextSort || {});
|
|
236
|
+
selectedClass.page = 1;
|
|
237
|
+
invalidateFolderFiles(selectedClass.path);
|
|
238
|
+
if (searchMode || !isActiveBrowser()) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
reloadSortedFolder();
|
|
242
|
+
});
|
|
243
|
+
$container.on(`searchclear.${NS}`, function (e, path) {
|
|
244
|
+
const targetPath = path || selectedClass.path;
|
|
245
|
+
selectedClass.page = 1;
|
|
246
|
+
invalidateFolderFiles(targetPath);
|
|
247
|
+
if (!isActiveBrowser()) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath) || fileTree;
|
|
251
|
+
getFolderContent(subTree, targetPath, function (content) {
|
|
252
|
+
if (content) {
|
|
253
|
+
selectFolder(content, targetPath);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Reopen with resolved parent (AC3 edit/change): leave search, open folder again.
|
|
259
|
+
$container.on(`applycontext.${NS}`, function (e, ctx) {
|
|
260
|
+
const path = ctx && ctx.path || rootPath;
|
|
261
|
+
const pathIsTaomedia = path && String(path).indexOf('taomedia://') === 0;
|
|
262
|
+
if (pathIsTaomedia && root === 'local') {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (!pathIsTaomedia && root !== 'local') {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
$container.data('activeFileBrowserRoot', root);
|
|
269
|
+
if (searchMode) {
|
|
270
|
+
$container.trigger(`requestexitsearch.${NS}`);
|
|
271
|
+
}
|
|
272
|
+
selectedClass.page = 1;
|
|
273
|
+
openInitialPath(path);
|
|
274
|
+
});
|
|
187
275
|
|
|
188
276
|
//load the content of the ROOT
|
|
189
277
|
getFolderContent(fileTree, rootPath, function (content) {
|
|
278
|
+
if (!content) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
190
281
|
indexTree(content);
|
|
191
282
|
|
|
192
283
|
//create the tree node for the ROOT folder by default once the initial content loaded
|
|
193
|
-
$folderContainer.append(rootFolderTpl(content
|
|
284
|
+
$folderContainer.append(rootFolderTpl(Object.assign({}, content, {
|
|
285
|
+
showToggle: hasNestedFolderChildren(content) !== false
|
|
286
|
+
})));
|
|
194
287
|
const $rootNode = $$1('.root-folder', $folderContainer);
|
|
195
288
|
//create an inner list and append found elements
|
|
196
289
|
const $innerList = $$1('.root ul', $folderContainer);
|
|
197
|
-
if (content
|
|
290
|
+
if (hasNestedFolderChildren(content)) {
|
|
198
291
|
$rootNode.addClass('opened');
|
|
292
|
+
} else if (hasNestedFolderChildren(content) === false) {
|
|
293
|
+
setFolderToggleState($rootNode, false);
|
|
199
294
|
}
|
|
200
295
|
updateFolders(content, $innerList);
|
|
201
|
-
if (content.permissions.read && !options.hasAlreadySelected) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
296
|
+
if (content.permissions && content.permissions.read && !options.hasAlreadySelected) {
|
|
297
|
+
if (initialPath && initialPath !== rootPath) {
|
|
298
|
+
openInitialPath(initialPath);
|
|
299
|
+
} else {
|
|
300
|
+
selectFolder(content, content.path);
|
|
301
|
+
syncTreeActiveFolder(content.path || rootPath);
|
|
302
|
+
}
|
|
206
303
|
if (root !== 'local') {
|
|
207
304
|
options.hasAlreadySelected = true;
|
|
208
305
|
}
|
|
@@ -213,9 +310,8 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
213
310
|
$divContainer.off('click', '.folders a').on('click', '.folders a', function (e) {
|
|
214
311
|
e.preventDefault();
|
|
215
312
|
const $selected = $$1(this);
|
|
216
|
-
const $folders = $$1('.folders li', $fileBrowser);
|
|
217
313
|
const fullPath = $selected.data('path');
|
|
218
|
-
const subTree =
|
|
314
|
+
const subTree = getByExactPath(fileTree, fullPath);
|
|
219
315
|
|
|
220
316
|
//get the folder content
|
|
221
317
|
getFolderContent(subTree, fullPath, function (content) {
|
|
@@ -223,10 +319,12 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
223
319
|
if (content) {
|
|
224
320
|
//either create the inner list of the content is new or just show it
|
|
225
321
|
let $innerList = $selected.siblings('ul');
|
|
226
|
-
|
|
322
|
+
const nested = hasNestedFolderChildren(content);
|
|
323
|
+
if (!$innerList.length && nested) {
|
|
227
324
|
$innerList = $$1('<ul></ul>').insertAfter($selected);
|
|
228
325
|
updateFolders(content, $innerList);
|
|
229
326
|
$selected.addClass('opened');
|
|
327
|
+
setFolderToggleState($selected, true);
|
|
230
328
|
} else if ($innerList.length) {
|
|
231
329
|
if ($innerList.css('display') === 'none') {
|
|
232
330
|
$innerList.show();
|
|
@@ -235,62 +333,185 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
235
333
|
$innerList.hide();
|
|
236
334
|
$selected.removeClass('opened');
|
|
237
335
|
}
|
|
336
|
+
} else if (nested === false) {
|
|
337
|
+
// Leaf folder: keep alignment spacer, hide expand chevron.
|
|
338
|
+
setFolderToggleState($selected, false);
|
|
238
339
|
}
|
|
239
|
-
|
|
240
|
-
//toggle active element
|
|
241
|
-
$folders.removeClass('active');
|
|
242
|
-
$selected.parent('li').addClass('active');
|
|
340
|
+
syncTreeActiveFolder(fullPath, $selected);
|
|
243
341
|
|
|
244
342
|
//internal event to set the file-selector content
|
|
245
|
-
|
|
246
|
-
$container.trigger(`folderselect.${ns}`, [content.label, getPage(content.children), content.path, content]);
|
|
247
|
-
renderPagination();
|
|
343
|
+
selectFolder(content, fullPath);
|
|
248
344
|
}
|
|
249
345
|
});
|
|
250
346
|
});
|
|
251
|
-
$container.on(`filenew.${
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
347
|
+
$container.on(`filenew.${NS}`, function (e, file, path) {
|
|
348
|
+
const targetPath = path || selectedClass.path;
|
|
349
|
+
if (!targetPath || searchMode) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
353
|
+
if (activeRoot && activeRoot !== root) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath);
|
|
357
|
+
if (subTree && root === 'local' && file && file.name && _.find(subTree.children, {
|
|
358
|
+
name: file.name
|
|
359
|
+
})) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (targetPath !== selectedClass.path) {
|
|
363
|
+
selectedClass.path = targetPath;
|
|
364
|
+
selectedClass.page = 1;
|
|
365
|
+
}
|
|
366
|
+
invalidateFolderFiles(targetPath);
|
|
367
|
+
reloadSortedFolder();
|
|
368
|
+
});
|
|
369
|
+
$container.on(`filedelete.${NS}`, function (e, path) {
|
|
370
|
+
if (searchMode) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
374
|
+
if (activeRoot && activeRoot !== root) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (path) {
|
|
378
|
+
removeFromPath(fileTree, path);
|
|
379
|
+
}
|
|
380
|
+
invalidateFolderFiles(selectedClass.path);
|
|
381
|
+
reloadSortedFolder();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Open and select an initial folder path after the root tree is available.
|
|
386
|
+
* @param {String} path
|
|
387
|
+
*/
|
|
388
|
+
function findFolderLink(path) {
|
|
389
|
+
return $folderContainer.find('a').filter(function () {
|
|
390
|
+
return $$1(this).data('path') === path;
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Render directory children under an expanded folder anchor (replace / reopen).
|
|
396
|
+
* @param {String} parentPath
|
|
397
|
+
* @param {Object} parentContent
|
|
398
|
+
*/
|
|
399
|
+
function mountFolderBranch(parentPath, parentContent) {
|
|
400
|
+
const $parentLink = findFolderLink(parentPath);
|
|
401
|
+
if (!$parentLink.length || !parentContent) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
let $innerList = $parentLink.siblings('ul');
|
|
405
|
+
if (!$innerList.length) {
|
|
406
|
+
$innerList = $$1('<ul></ul>').insertAfter($parentLink);
|
|
407
|
+
}
|
|
408
|
+
updateFolders(parentContent, $innerList);
|
|
409
|
+
$parentLink.addClass('opened');
|
|
410
|
+
if ($innerList.css('display') === 'none') {
|
|
411
|
+
$innerList.show();
|
|
412
|
+
}
|
|
413
|
+
setFolderToggleState($parentLink, hasNestedFolderChildren(parentContent) !== false);
|
|
414
|
+
}
|
|
415
|
+
function markFolderActive(path) {
|
|
416
|
+
const $targetLink = findFolderLink(path);
|
|
417
|
+
if ($targetLink.length) {
|
|
418
|
+
$targetLink.parents('li').each(function () {
|
|
419
|
+
const $li = $$1(this);
|
|
420
|
+
const $anchor = $li.children('a');
|
|
421
|
+
const $list = $li.children('ul');
|
|
422
|
+
$anchor.addClass('opened');
|
|
423
|
+
if ($list.length) {
|
|
424
|
+
$list.show();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
syncTreeActiveFolder(path, $targetLink);
|
|
428
|
+
} else if (path === rootPath) {
|
|
429
|
+
syncTreeActiveFolder(rootPath);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function openInitialPath(path) {
|
|
433
|
+
getFolderContent(fileTree, path, function (content) {
|
|
434
|
+
indexTree(fileTree);
|
|
435
|
+
if (!content) {
|
|
436
|
+
const rootContent = getByPath(fileTree, rootPath) || fileTree;
|
|
437
|
+
const fallbackPath = rootContent.path || rootPath;
|
|
438
|
+
selectFolder(rootContent, fallbackPath);
|
|
439
|
+
syncTreeActiveFolder(fallbackPath);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
function finishOpen() {
|
|
443
|
+
markFolderActive(path);
|
|
444
|
+
selectFolder(content, path);
|
|
445
|
+
}
|
|
446
|
+
if (findFolderLink(path).length) {
|
|
447
|
+
finishOpen();
|
|
448
|
+
return;
|
|
256
449
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// Only file object has link property
|
|
263
|
-
return Object.prototype.hasOwnProperty.call(child, 'link');
|
|
450
|
+
const parentFolderPath = content.parentFolderPath;
|
|
451
|
+
if (parentFolderPath && parentFolderPath !== path) {
|
|
452
|
+
getFolderContent(fileTree, parentFolderPath, function (parentContent) {
|
|
453
|
+
mountFolderBranch(parentFolderPath, parentContent);
|
|
454
|
+
finishOpen();
|
|
264
455
|
});
|
|
265
|
-
|
|
266
|
-
// all children loaded new file can be pushed to the end of tree
|
|
267
|
-
// if not all, new file will be loaded with next page
|
|
268
|
-
subTree.children.push(file);
|
|
269
|
-
}
|
|
270
|
-
subTree.total = Number.isFinite(Number(subTree.total)) ? Number(subTree.total) + 1 : 1;
|
|
271
|
-
if (selectedClass.path === path) {
|
|
272
|
-
selectedClass.total = subTree.total;
|
|
273
|
-
}
|
|
274
|
-
$container.trigger(`folderselect.${ns}`, [subTree.label, getPage(subTree.children), path, subTree]);
|
|
275
|
-
renderPagination();
|
|
456
|
+
return;
|
|
276
457
|
}
|
|
458
|
+
finishOpen();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Mark the folder row that matches path as active (open folder icon for leaves).
|
|
464
|
+
* @param {String} path
|
|
465
|
+
* @param {jQuery} [$link]
|
|
466
|
+
*/
|
|
467
|
+
function syncTreeActiveFolder(path, $link) {
|
|
468
|
+
$$1('.folders li', $fileBrowser).removeClass('active');
|
|
469
|
+
const $target = $link && $link.length ? $link : $folderContainer.find('a').filter(function () {
|
|
470
|
+
return $$1(this).data('path') === path;
|
|
471
|
+
});
|
|
472
|
+
if ($target.length) {
|
|
473
|
+
$target.parent('li').addClass('active');
|
|
277
474
|
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Whether this media source currently owns the file table.
|
|
479
|
+
* @returns {Boolean}
|
|
480
|
+
*/
|
|
481
|
+
function isActiveBrowser() {
|
|
482
|
+
return $container.data('activeFileBrowserRoot') === root;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Select a folder and publish its page of files to the selector.
|
|
487
|
+
* @param {Object} content
|
|
488
|
+
* @param {String} path
|
|
489
|
+
*/
|
|
490
|
+
function selectFolder(content, path) {
|
|
491
|
+
if (!content) {
|
|
492
|
+
return;
|
|
283
493
|
}
|
|
284
|
-
|
|
494
|
+
$container.data('activeFileBrowserRoot', root);
|
|
495
|
+
if (searchMode) {
|
|
496
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
497
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getPage(content.children || []), path, content]);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
updateSelectedClass(path, content.total, content.childrenLimit);
|
|
501
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
502
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getPage(content.children || []), path, content]);
|
|
503
|
+
renderPagination();
|
|
504
|
+
}
|
|
505
|
+
|
|
285
506
|
/**
|
|
286
507
|
* Get files for page
|
|
287
508
|
* @param {Array} children
|
|
288
509
|
* @returns {Array} files for this page
|
|
289
510
|
*/
|
|
290
511
|
function getPage(children) {
|
|
291
|
-
const files = _.filter(children, function (item) {
|
|
512
|
+
const files = assetSearchContract.sortAssetItems(_.filter(children, function (item) {
|
|
292
513
|
return !!item.uri;
|
|
293
|
-
});
|
|
514
|
+
}), sort);
|
|
294
515
|
if (selectedClass.childrenLimit) {
|
|
295
516
|
return files.slice((selectedClass.page - 1) * selectedClass.childrenLimit, selectedClass.page * selectedClass.childrenLimit);
|
|
296
517
|
}
|
|
@@ -302,6 +523,12 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
302
523
|
* @param {String} path - the folder path (relative to the root)
|
|
303
524
|
* @param {Function} cb - called back with the content in 1st parameter
|
|
304
525
|
*/
|
|
526
|
+
function markFolderEmptyAtPath(tree, path) {
|
|
527
|
+
const node = getByExactPath(tree, path);
|
|
528
|
+
if (node) {
|
|
529
|
+
node.empty = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
305
532
|
function getFolderContent(tree, path, cb) {
|
|
306
533
|
let content = getByPath(tree, path);
|
|
307
534
|
if (!content || !content.children && !content.empty) {
|
|
@@ -309,32 +536,47 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
309
536
|
if (!tree.path) {
|
|
310
537
|
tree = _.merge(tree, data);
|
|
311
538
|
} else if (data.children) {
|
|
539
|
+
setToPath(tree, path, data);
|
|
312
540
|
if (!_.find(data.children, 'path')) {
|
|
313
541
|
// no subfolders inside folder
|
|
314
|
-
tree
|
|
542
|
+
markFolderEmptyAtPath(tree, path);
|
|
315
543
|
}
|
|
316
|
-
setToPath(tree, path, data);
|
|
317
544
|
} else {
|
|
318
|
-
tree
|
|
545
|
+
setToPath(tree, path, data);
|
|
546
|
+
markFolderEmptyAtPath(tree, path);
|
|
319
547
|
}
|
|
320
548
|
cb(data);
|
|
549
|
+
}).catch(function () {
|
|
550
|
+
cb(null);
|
|
321
551
|
});
|
|
322
552
|
} else if (content.children) {
|
|
323
553
|
const files = _.filter(content.children, function (item) {
|
|
324
554
|
return !!item.uri;
|
|
325
555
|
});
|
|
326
|
-
//
|
|
327
|
-
|
|
556
|
+
// Use folder total (not selectedClass): openInitialPath runs before selectFolder.
|
|
557
|
+
const expectedTotal = Number(content.total);
|
|
558
|
+
const pageSize = Number(content.childrenLimit) || selectedClass.childrenLimit || 10;
|
|
559
|
+
const page = selectedClass.page || 1;
|
|
560
|
+
// Missing/NaN total → unknown size; refetch so invalidated cache is not treated as complete.
|
|
561
|
+
if (!Number.isFinite(expectedTotal) || files.length < expectedTotal && files.length < page * pageSize) {
|
|
328
562
|
loadContent(path).then(function (data) {
|
|
329
563
|
const loadedFiles = _.filter(data.children, function (item) {
|
|
330
564
|
return !!item.uri;
|
|
331
565
|
});
|
|
566
|
+
const node = getByExactPath(tree, path);
|
|
567
|
+
if (node && !Number.isFinite(expectedTotal)) {
|
|
568
|
+
node.children = _.filter(node.children, function (item) {
|
|
569
|
+
return !item.uri;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
332
572
|
setToPath(tree, path, {
|
|
333
573
|
children: loadedFiles,
|
|
334
574
|
total: data.total,
|
|
335
575
|
childrenLimit: data.childrenLimit
|
|
336
576
|
});
|
|
337
|
-
content = getByPath(tree, path);
|
|
577
|
+
content = getByExactPath(tree, path) || getByPath(tree, path);
|
|
578
|
+
cb(content);
|
|
579
|
+
}).catch(function () {
|
|
338
580
|
cb(content);
|
|
339
581
|
});
|
|
340
582
|
} else {
|
|
@@ -384,6 +626,29 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
384
626
|
return match;
|
|
385
627
|
}
|
|
386
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Get a subtree by exact path equality.
|
|
631
|
+
* @param {Object} tree
|
|
632
|
+
* @param {String} path
|
|
633
|
+
* @returns {Object|undefined}
|
|
634
|
+
*/
|
|
635
|
+
function getByExactPath(tree, path) {
|
|
636
|
+
let match;
|
|
637
|
+
if (tree) {
|
|
638
|
+
if (tree.path === path) {
|
|
639
|
+
match = tree;
|
|
640
|
+
} else if (tree.children) {
|
|
641
|
+
_.forEach(tree.children, function (child) {
|
|
642
|
+
match = getByExactPath(child, path);
|
|
643
|
+
if (match) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return match;
|
|
650
|
+
}
|
|
651
|
+
|
|
387
652
|
/**
|
|
388
653
|
* Merge data into at into the subtree
|
|
389
654
|
* @param {Object} tree - the tree model
|
|
@@ -427,8 +692,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
427
692
|
return child.path === path || child.name && tree.path + child.name === path || child.uri === path;
|
|
428
693
|
});
|
|
429
694
|
done = removed.length > 0;
|
|
430
|
-
|
|
431
|
-
|
|
695
|
+
if (done) {
|
|
696
|
+
if (Number.isFinite(Number(tree.total))) {
|
|
697
|
+
tree.total = Math.max(0, Number(tree.total) - removed.length);
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
432
700
|
_.forEach(tree.children, function (child) {
|
|
433
701
|
done = removeFromPath(child, path);
|
|
434
702
|
if (done) {
|
|
@@ -440,31 +708,116 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
440
708
|
return done;
|
|
441
709
|
}
|
|
442
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Drop cached file rows for a folder so the next load hits the service
|
|
713
|
+
* with the current sort. Nested folder nodes are kept.
|
|
714
|
+
* @param {String} path
|
|
715
|
+
*/
|
|
716
|
+
function invalidateFolderFiles(path) {
|
|
717
|
+
const content = getByExactPath(fileTree, path);
|
|
718
|
+
if (content && Array.isArray(content.children)) {
|
|
719
|
+
content.children = content.children.filter(function (child) {
|
|
720
|
+
return child.path && !child.uri;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Replace a folder node with a freshly loaded payload (files + folders).
|
|
727
|
+
* @param {String} path
|
|
728
|
+
* @param {Object} data
|
|
729
|
+
*/
|
|
730
|
+
function replaceFolderContent(path, data) {
|
|
731
|
+
const content = getByExactPath(fileTree, path);
|
|
732
|
+
if (!content || !data) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (Object.prototype.hasOwnProperty.call(data, 'children')) {
|
|
736
|
+
content.children = data.children;
|
|
737
|
+
} else {
|
|
738
|
+
content.children = [];
|
|
739
|
+
content.empty = true;
|
|
740
|
+
content.total = 0;
|
|
741
|
+
}
|
|
742
|
+
if (Object.prototype.hasOwnProperty.call(data, 'total')) {
|
|
743
|
+
content.total = data.total;
|
|
744
|
+
}
|
|
745
|
+
if (Object.prototype.hasOwnProperty.call(data, 'childrenLimit')) {
|
|
746
|
+
content.childrenLimit = data.childrenLimit;
|
|
747
|
+
}
|
|
748
|
+
if (data.label) {
|
|
749
|
+
content.label = data.label;
|
|
750
|
+
}
|
|
751
|
+
if (data.path) {
|
|
752
|
+
content.path = data.path;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
443
756
|
/**
|
|
444
757
|
* Get the content of a folder
|
|
445
758
|
* @param {String} path - the folder path
|
|
446
|
-
* @returns {
|
|
759
|
+
* @returns {Promise} resolves with folder content
|
|
447
760
|
*/
|
|
448
761
|
function loadContent(path) {
|
|
449
762
|
const parameters = {};
|
|
450
|
-
parameters[options.pathParam] = path;
|
|
451
|
-
return
|
|
763
|
+
parameters[options.pathParam || 'path'] = path;
|
|
764
|
+
return Promise.resolve($$1.ajax({
|
|
452
765
|
url: options.browseUrl,
|
|
453
766
|
method: 'GET',
|
|
454
767
|
dataType: 'json',
|
|
768
|
+
timeout: ajaxTimeoutMs,
|
|
455
769
|
data: _.merge(parameters, options.params, {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
770
|
+
// depth=2 so each rendered child folder includes its own dir children,
|
|
771
|
+
// allowing leaf folders to hide the expand chevron without an extra click.
|
|
772
|
+
depth: 2,
|
|
773
|
+
childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
774
|
+
sortBy: sort.field,
|
|
775
|
+
sortDir: sort.direction
|
|
776
|
+
})
|
|
777
|
+
})).then(function (response) {
|
|
778
|
+
if (response && response.success === false) {
|
|
779
|
+
return Promise.reject(response);
|
|
463
780
|
}
|
|
464
|
-
|
|
781
|
+
let data = response && response.data ? response.data : response;
|
|
782
|
+
data = updatePermissions(data);
|
|
783
|
+
if (data.children && data.children.length > 0) {
|
|
784
|
+
data.children.map(responseChildren => updatePermissions(responseChildren));
|
|
785
|
+
}
|
|
786
|
+
return data;
|
|
465
787
|
});
|
|
466
788
|
}
|
|
467
789
|
|
|
790
|
+
/**
|
|
791
|
+
* Whether a folder node has nested directory children.
|
|
792
|
+
* @param {Object} node
|
|
793
|
+
* @returns {boolean|null} true/false when known; null when children were not loaded yet
|
|
794
|
+
*/
|
|
795
|
+
function hasNestedFolderChildren(node) {
|
|
796
|
+
if (!node || node.empty === true) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
if (!Array.isArray(node.children)) {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
return Boolean(_.find(node.children, 'path'));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Show or hide the expand chevron on a folder anchor (keep spacer width).
|
|
807
|
+
* @param {jQuery} $anchor
|
|
808
|
+
* @param {boolean} hasNested
|
|
809
|
+
*/
|
|
810
|
+
function setFolderToggleState($anchor, hasNested) {
|
|
811
|
+
const $toggle = $anchor.children('.tree-toggle');
|
|
812
|
+
if (hasNested) {
|
|
813
|
+
$toggle.addClass('icon-right').removeClass('is-leaf');
|
|
814
|
+
$anchor.removeClass('is-leaf');
|
|
815
|
+
} else {
|
|
816
|
+
$toggle.removeClass('icon-right').addClass('is-leaf');
|
|
817
|
+
$anchor.removeClass('opened').addClass('is-leaf');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
468
821
|
/**
|
|
469
822
|
* Update the HTML Tree
|
|
470
823
|
* @param {Object} data - the tree data
|
|
@@ -476,7 +829,10 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
476
829
|
if (typeof data.relPath === 'undefined') {
|
|
477
830
|
data.relPath = data.path;
|
|
478
831
|
}
|
|
832
|
+
// Unknown nested state (lazy depth) keeps the chevron until the folder is opened.
|
|
833
|
+
data.showToggle = hasNestedFolderChildren(data) !== false;
|
|
479
834
|
$parent.append(folderTpl(data));
|
|
835
|
+
return;
|
|
480
836
|
}
|
|
481
837
|
if (data && data.children && _.isArray(data.children) && !data.empty) {
|
|
482
838
|
_.forEach(data.children, function (child) {
|
|
@@ -505,7 +861,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
505
861
|
* Render pagination
|
|
506
862
|
*/
|
|
507
863
|
function renderPagination() {
|
|
508
|
-
|
|
864
|
+
if (searchMode) {
|
|
865
|
+
// Drop browse pagination controls/handlers when search owns the table.
|
|
866
|
+
$paginationContainer.empty();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
509
869
|
const total = Number(selectedClass.total);
|
|
510
870
|
const childrenLimit = Number(selectedClass.childrenLimit);
|
|
511
871
|
$paginationContainer.empty();
|
|
@@ -527,18 +887,35 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
527
887
|
}).render($paginationContainer);
|
|
528
888
|
}
|
|
529
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Re-fetch the current folder with the active sort and publish files.
|
|
892
|
+
*/
|
|
893
|
+
function reloadSortedFolder() {
|
|
894
|
+
const path = selectedClass.path;
|
|
895
|
+
loadContent(path).then(function (data) {
|
|
896
|
+
if (!data) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
replaceFolderContent(path, data);
|
|
900
|
+
const content = getByExactPath(fileTree, path) || data;
|
|
901
|
+
selectFolder(content, content.path || path);
|
|
902
|
+
}).catch(function (error) {
|
|
903
|
+
LOGGER.error(error);
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
530
907
|
/**
|
|
531
908
|
* Load page
|
|
532
909
|
*/
|
|
533
910
|
function loadPage() {
|
|
534
|
-
const subTree = getByPath(fileTree, selectedClass.path);
|
|
911
|
+
const subTree = getByPath(fileTree, selectedClass.path) || fileTree;
|
|
535
912
|
|
|
536
913
|
//get the folder content
|
|
537
914
|
getFolderContent(subTree, selectedClass.path, function (content) {
|
|
538
915
|
indexTree(fileTree);
|
|
539
916
|
if (content) {
|
|
540
917
|
//internal event to set the file-selector content
|
|
541
|
-
$container.trigger(`folderselect.${
|
|
918
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getPage(content.children), content.path, content]);
|
|
542
919
|
}
|
|
543
920
|
});
|
|
544
921
|
}
|