@oat-sa/tao-core-ui 3.23.0 → 3.24.1
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 +1230 -97
- package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
- package/dist/resourcemgr/fileBrowser.js +815 -83
- package/dist/resourcemgr/filePreview.js +82 -7
- package/dist/resourcemgr/fileSelector.js +377 -92
- package/dist/resourcemgr.js +314 -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 +1230 -97
- package/src/resourcemgr/css/resourcemgr.css.map +1 -1
- package/src/resourcemgr/fileBrowser.js +872 -92
- package/src/resourcemgr/filePreview.js +89 -8
- package/src/resourcemgr/fileSelector.js +356 -46
- package/src/resourcemgr/scss/resourcemgr.scss +1450 -89
- 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 +125 -54
- package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
- package/src/resourcemgr.js +294 -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,48 +198,171 @@ 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 = 30000;
|
|
208
|
+
const TREE_LOADING_REF_KEY = `${NS}TreeLoadingRefCount`;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {Object} data
|
|
212
|
+
* @returns {Array}
|
|
213
|
+
*/
|
|
214
|
+
function folderChildrenOnly(data) {
|
|
215
|
+
return _.filter(data && data.children, function (child) {
|
|
216
|
+
return child && child.path && !child.uri;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
172
219
|
function fileBrowser (options) {
|
|
220
|
+
if (!options.browseUrl && options.url) {
|
|
221
|
+
options.browseUrl = options.url;
|
|
222
|
+
}
|
|
173
223
|
const root = options.root || 'local';
|
|
174
224
|
const rootPath = options.path || '/';
|
|
225
|
+
const initialPath = options.initialPath || rootPath;
|
|
175
226
|
const $container = options.$target;
|
|
176
227
|
const $fileBrowser = $$1('.file-browser .file-browser-wrapper', $container);
|
|
177
228
|
const $divContainer = $$1(`.${root}`, $fileBrowser);
|
|
178
229
|
const $folderContainer = $$1('.folders', $divContainer);
|
|
230
|
+
const $paginationContainer = $$1('.pagination-bottom', $container);
|
|
179
231
|
const fileTree = {};
|
|
180
232
|
// for pagination
|
|
181
233
|
let selectedClass = {
|
|
182
234
|
path: rootPath,
|
|
183
|
-
childrenLimit:
|
|
235
|
+
childrenLimit: 11,
|
|
184
236
|
total: 0,
|
|
185
237
|
page: 1
|
|
186
238
|
};
|
|
239
|
+
let searchMode = false;
|
|
240
|
+
let sort = Object.assign({}, assetSearchContract.DEFAULT_SORT);
|
|
241
|
+
const splitBrowse = options.splitBrowse !== false;
|
|
242
|
+
const ajaxTimeoutMs = Number.isFinite(Number(options.ajaxTimeoutMs)) && Number(options.ajaxTimeoutMs) > 0 ? Number(options.ajaxTimeoutMs) : DEFAULT_AJAX_TIMEOUT_MS;
|
|
243
|
+
const $treeLoadingOverlay = $$1('.file-browser-tree-loading', $fileBrowser);
|
|
244
|
+
const $treeLoadingStatus = $$1('.file-browser-tree-loading-status', $treeLoadingOverlay);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Ref-count in-flight browse requests and toggle the tree loading overlay.
|
|
248
|
+
* @param {Boolean} increment - true when a load starts, false when it finishes
|
|
249
|
+
*/
|
|
250
|
+
function setTreeLoading(increment) {
|
|
251
|
+
let treeLoadingRefCount = Number($fileBrowser.data(TREE_LOADING_REF_KEY)) || 0;
|
|
252
|
+
treeLoadingRefCount += increment ? 1 : -1;
|
|
253
|
+
if (treeLoadingRefCount < 0) {
|
|
254
|
+
treeLoadingRefCount = 0;
|
|
255
|
+
}
|
|
256
|
+
$fileBrowser.data(TREE_LOADING_REF_KEY, treeLoadingRefCount);
|
|
257
|
+
const loading = treeLoadingRefCount > 0;
|
|
258
|
+
$fileBrowser.toggleClass('is-tree-loading', loading);
|
|
259
|
+
$fileBrowser.attr('aria-busy', loading ? 'true' : 'false');
|
|
260
|
+
$treeLoadingOverlay.toggleClass('hidden', !loading).attr('aria-hidden', loading ? 'false' : 'true');
|
|
261
|
+
$treeLoadingStatus.attr('aria-busy', loading ? 'true' : 'false');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Show or hide the asset table loading overlay (browse mode only).
|
|
266
|
+
* @param {Boolean} loading
|
|
267
|
+
*/
|
|
268
|
+
function setFilesLoading(loading) {
|
|
269
|
+
if (searchMode) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
$container.trigger(`filesloading.${NS}`, [!!loading]);
|
|
273
|
+
}
|
|
274
|
+
$container.on(`searchmode.${NS}`, function (e, enabled) {
|
|
275
|
+
searchMode = !!enabled;
|
|
276
|
+
if (searchMode) {
|
|
277
|
+
$container.trigger(`filesloading.${NS}`, [false]);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
$container.on(`sortchange.${NS}`, function (e, nextSort) {
|
|
281
|
+
sort = Object.assign({}, assetSearchContract.DEFAULT_SORT, nextSort || {});
|
|
282
|
+
selectedClass.page = 1;
|
|
283
|
+
invalidateFolderFiles(selectedClass.path);
|
|
284
|
+
if (searchMode || !isActiveBrowser()) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
reloadSortedFolder();
|
|
288
|
+
});
|
|
289
|
+
$container.on(`searchclear.${NS}`, function (e, path) {
|
|
290
|
+
const targetPath = path || selectedClass.path;
|
|
291
|
+
selectedClass.page = 1;
|
|
292
|
+
invalidateFolderFiles(targetPath);
|
|
293
|
+
if (!isActiveBrowser()) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath) || fileTree;
|
|
297
|
+
if (!searchMode) {
|
|
298
|
+
setFilesLoading(true);
|
|
299
|
+
}
|
|
300
|
+
getFolderContent(subTree, targetPath, function (content) {
|
|
301
|
+
if (content) {
|
|
302
|
+
selectFolder(content, targetPath);
|
|
303
|
+
} else {
|
|
304
|
+
setFilesLoading(false);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Reopen with resolved parent (AC3 edit/change): leave search, open folder again.
|
|
310
|
+
$container.on(`applycontext.${NS}`, function (e, ctx) {
|
|
311
|
+
const path = ctx && ctx.path || rootPath;
|
|
312
|
+
const pathIsTaomedia = path && String(path).indexOf('taomedia://') === 0;
|
|
313
|
+
if (pathIsTaomedia && root === 'local') {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!pathIsTaomedia && root !== 'local') {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
$container.data('activeFileBrowserRoot', root);
|
|
320
|
+
if (searchMode) {
|
|
321
|
+
$container.trigger(`requestexitsearch.${NS}`);
|
|
322
|
+
}
|
|
323
|
+
selectedClass.page = 1;
|
|
324
|
+
openInitialPath(path);
|
|
325
|
+
});
|
|
187
326
|
|
|
188
327
|
//load the content of the ROOT
|
|
328
|
+
const showInitialListLoading = !searchMode && root === 'local';
|
|
329
|
+
if (showInitialListLoading) {
|
|
330
|
+
setFilesLoading(true);
|
|
331
|
+
}
|
|
189
332
|
getFolderContent(fileTree, rootPath, function (content) {
|
|
333
|
+
if (!content) {
|
|
334
|
+
if (showInitialListLoading) {
|
|
335
|
+
setFilesLoading(false);
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
190
339
|
indexTree(content);
|
|
191
340
|
|
|
192
341
|
//create the tree node for the ROOT folder by default once the initial content loaded
|
|
193
|
-
$folderContainer.append(rootFolderTpl(content
|
|
342
|
+
$folderContainer.append(rootFolderTpl(Object.assign({}, content, {
|
|
343
|
+
showToggle: hasNestedFolderChildren(content) !== false
|
|
344
|
+
})));
|
|
194
345
|
const $rootNode = $$1('.root-folder', $folderContainer);
|
|
195
346
|
//create an inner list and append found elements
|
|
196
347
|
const $innerList = $$1('.root ul', $folderContainer);
|
|
197
|
-
if (content
|
|
348
|
+
if (hasNestedFolderChildren(content)) {
|
|
198
349
|
$rootNode.addClass('opened');
|
|
350
|
+
} else if (hasNestedFolderChildren(content) === false) {
|
|
351
|
+
setFolderToggleState($rootNode, false);
|
|
199
352
|
}
|
|
200
353
|
updateFolders(content, $innerList);
|
|
201
|
-
if (content.permissions.read && !options.hasAlreadySelected) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
354
|
+
if (content.permissions && content.permissions.read && !options.hasAlreadySelected) {
|
|
355
|
+
if (initialPath && initialPath !== rootPath) {
|
|
356
|
+
openInitialPath(initialPath);
|
|
357
|
+
} else {
|
|
358
|
+
selectFolder(content, content.path);
|
|
359
|
+
syncTreeActiveFolder(content.path || rootPath);
|
|
360
|
+
}
|
|
206
361
|
if (root !== 'local') {
|
|
207
362
|
options.hasAlreadySelected = true;
|
|
208
363
|
}
|
|
364
|
+
} else if (showInitialListLoading) {
|
|
365
|
+
setFilesLoading(false);
|
|
209
366
|
}
|
|
210
367
|
});
|
|
211
368
|
|
|
@@ -213,9 +370,16 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
213
370
|
$divContainer.off('click', '.folders a').on('click', '.folders a', function (e) {
|
|
214
371
|
e.preventDefault();
|
|
215
372
|
const $selected = $$1(this);
|
|
216
|
-
const $folders = $$1('.folders li', $fileBrowser);
|
|
217
373
|
const fullPath = $selected.data('path');
|
|
218
|
-
const subTree =
|
|
374
|
+
const subTree = getByExactPath(fileTree, fullPath);
|
|
375
|
+
const openingFolder = !searchMode && fullPath !== selectedClass.path;
|
|
376
|
+
if (openingFolder) {
|
|
377
|
+
selectedClass.page = 1;
|
|
378
|
+
}
|
|
379
|
+
if (!searchMode) {
|
|
380
|
+
setFilesLoading(true);
|
|
381
|
+
}
|
|
382
|
+
invalidateFolderFiles(fullPath);
|
|
219
383
|
|
|
220
384
|
//get the folder content
|
|
221
385
|
getFolderContent(subTree, fullPath, function (content) {
|
|
@@ -223,10 +387,12 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
223
387
|
if (content) {
|
|
224
388
|
//either create the inner list of the content is new or just show it
|
|
225
389
|
let $innerList = $selected.siblings('ul');
|
|
226
|
-
|
|
390
|
+
const nested = hasNestedFolderChildren(content);
|
|
391
|
+
if (!$innerList.length && nested) {
|
|
227
392
|
$innerList = $$1('<ul></ul>').insertAfter($selected);
|
|
228
393
|
updateFolders(content, $innerList);
|
|
229
394
|
$selected.addClass('opened');
|
|
395
|
+
setFolderToggleState($selected, true);
|
|
230
396
|
} else if ($innerList.length) {
|
|
231
397
|
if ($innerList.css('display') === 'none') {
|
|
232
398
|
$innerList.show();
|
|
@@ -235,106 +401,460 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
235
401
|
$innerList.hide();
|
|
236
402
|
$selected.removeClass('opened');
|
|
237
403
|
}
|
|
404
|
+
} else if (nested === false) {
|
|
405
|
+
// Leaf folder: keep alignment spacer, hide expand chevron.
|
|
406
|
+
setFolderToggleState($selected, false);
|
|
238
407
|
}
|
|
239
|
-
|
|
240
|
-
//toggle active element
|
|
241
|
-
$folders.removeClass('active');
|
|
242
|
-
$selected.parent('li').addClass('active');
|
|
408
|
+
syncTreeActiveFolder(fullPath, $selected);
|
|
243
409
|
|
|
244
410
|
//internal event to set the file-selector content
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
411
|
+
selectFolder(content, fullPath);
|
|
412
|
+
} else if (!searchMode) {
|
|
413
|
+
setFilesLoading(false);
|
|
248
414
|
}
|
|
249
415
|
});
|
|
250
416
|
});
|
|
251
|
-
$container.on(`filenew.${
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
subTree.children = [];
|
|
256
|
-
}
|
|
257
|
-
if (root !== 'local' || !_.find(subTree.children, {
|
|
258
|
-
name: file.name
|
|
259
|
-
})) {
|
|
260
|
-
updatePermissions(file);
|
|
261
|
-
const childrenFilesOnly = _.filter(subTree.children, function (child) {
|
|
262
|
-
// Only file object has link property
|
|
263
|
-
return Object.prototype.hasOwnProperty.call(child, 'link');
|
|
264
|
-
});
|
|
265
|
-
if (childrenFilesOnly.length === subTree.total) {
|
|
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();
|
|
276
|
-
}
|
|
417
|
+
$container.on(`filenew.${NS}`, function (e, file, path) {
|
|
418
|
+
const targetPath = path || selectedClass.path;
|
|
419
|
+
if (!targetPath || searchMode) {
|
|
420
|
+
return;
|
|
277
421
|
}
|
|
422
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
423
|
+
if (activeRoot && activeRoot !== root) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath);
|
|
427
|
+
if (subTree && root === 'local' && file && file.name && _.find(subTree.children, {
|
|
428
|
+
name: file.name
|
|
429
|
+
})) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (targetPath !== selectedClass.path) {
|
|
433
|
+
selectedClass.path = targetPath;
|
|
434
|
+
}
|
|
435
|
+
selectedClass.page = 1;
|
|
436
|
+
invalidateFolderFiles(targetPath);
|
|
437
|
+
const pendingUpload = file && !file.error && (file.uri || file.name) ? file : null;
|
|
438
|
+
reloadSortedFolder(pendingUpload ? [pendingUpload] : []);
|
|
278
439
|
});
|
|
279
|
-
$container.on(`filedelete.${
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
440
|
+
$container.on(`filedelete.${NS}`, function (e, path) {
|
|
441
|
+
if (searchMode) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
445
|
+
if (activeRoot && activeRoot !== root) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (path) {
|
|
449
|
+
removeFromPath(fileTree, path);
|
|
283
450
|
}
|
|
451
|
+
invalidateFolderFiles(selectedClass.path);
|
|
452
|
+
reloadSortedFolder();
|
|
284
453
|
});
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Open and select an initial folder path after the root tree is available.
|
|
457
|
+
* @param {String} path
|
|
458
|
+
*/
|
|
459
|
+
function findFolderLink(path) {
|
|
460
|
+
return $folderContainer.find('a').filter(function () {
|
|
461
|
+
return $$1(this).data('path') === path;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Render directory children under an expanded folder anchor (replace / reopen).
|
|
467
|
+
* @param {String} parentPath
|
|
468
|
+
* @param {Object} parentContent
|
|
469
|
+
*/
|
|
470
|
+
function mountFolderBranch(parentPath, parentContent) {
|
|
471
|
+
const $parentLink = findFolderLink(parentPath);
|
|
472
|
+
if (!$parentLink.length || !parentContent) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
let $innerList = $parentLink.siblings('ul');
|
|
476
|
+
if (!$innerList.length) {
|
|
477
|
+
$innerList = $$1('<ul></ul>').insertAfter($parentLink);
|
|
478
|
+
}
|
|
479
|
+
updateFolders(parentContent, $innerList);
|
|
480
|
+
$parentLink.addClass('opened');
|
|
481
|
+
if ($innerList.css('display') === 'none') {
|
|
482
|
+
$innerList.show();
|
|
483
|
+
}
|
|
484
|
+
setFolderToggleState($parentLink, hasNestedFolderChildren(parentContent) !== false);
|
|
485
|
+
}
|
|
486
|
+
function markFolderActive(path) {
|
|
487
|
+
const $targetLink = findFolderLink(path);
|
|
488
|
+
if ($targetLink.length) {
|
|
489
|
+
$targetLink.parents('li').each(function () {
|
|
490
|
+
const $li = $$1(this);
|
|
491
|
+
const $anchor = $li.children('a');
|
|
492
|
+
const $list = $li.children('ul');
|
|
493
|
+
$anchor.addClass('opened');
|
|
494
|
+
if ($list.length) {
|
|
495
|
+
$list.show();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
syncTreeActiveFolder(path, $targetLink);
|
|
499
|
+
} else if (path === rootPath) {
|
|
500
|
+
syncTreeActiveFolder(rootPath);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function openInitialPath(path) {
|
|
504
|
+
getFolderContent(fileTree, path, function (content) {
|
|
505
|
+
indexTree(fileTree);
|
|
506
|
+
if (!content) {
|
|
507
|
+
const rootContent = getByPath(fileTree, rootPath) || fileTree;
|
|
508
|
+
const fallbackPath = rootContent.path || rootPath;
|
|
509
|
+
selectFolder(rootContent, fallbackPath);
|
|
510
|
+
syncTreeActiveFolder(fallbackPath);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
function finishOpen() {
|
|
514
|
+
markFolderActive(path);
|
|
515
|
+
selectFolder(content, path);
|
|
516
|
+
}
|
|
517
|
+
if (findFolderLink(path).length) {
|
|
518
|
+
finishOpen();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const parentFolderPath = content.parentFolderPath;
|
|
522
|
+
if (parentFolderPath && parentFolderPath !== path) {
|
|
523
|
+
getFolderContent(fileTree, parentFolderPath, function (parentContent) {
|
|
524
|
+
mountFolderBranch(parentFolderPath, parentContent);
|
|
525
|
+
finishOpen();
|
|
526
|
+
});
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
finishOpen();
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Mark the folder row that matches path as active (open folder icon for leaves).
|
|
535
|
+
* @param {String} path
|
|
536
|
+
* @param {jQuery} [$link]
|
|
537
|
+
*/
|
|
538
|
+
function syncTreeActiveFolder(path, $link) {
|
|
539
|
+
$$1('.folders li', $fileBrowser).removeClass('active');
|
|
540
|
+
const $target = $link && $link.length ? $link : $folderContainer.find('a').filter(function () {
|
|
541
|
+
return $$1(this).data('path') === path;
|
|
542
|
+
});
|
|
543
|
+
if ($target.length) {
|
|
544
|
+
$target.parent('li').addClass('active');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Whether this media source currently owns the file table.
|
|
550
|
+
* @returns {Boolean}
|
|
551
|
+
*/
|
|
552
|
+
function isActiveBrowser() {
|
|
553
|
+
return $container.data('activeFileBrowserRoot') === root;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Select a folder and publish its page of files to the selector.
|
|
558
|
+
* @param {Object} content
|
|
559
|
+
* @param {String} path
|
|
560
|
+
*/
|
|
561
|
+
function selectFolder(content, path) {
|
|
562
|
+
if (!content) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
$container.data('activeFileBrowserRoot', root);
|
|
566
|
+
if (searchMode) {
|
|
567
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
568
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), path, content]);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
updateSelectedClass(path, content.total, content.childrenLimit);
|
|
572
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
573
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), path, content]);
|
|
574
|
+
renderPagination();
|
|
575
|
+
if (!searchMode) {
|
|
576
|
+
setFilesLoading(false);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
285
580
|
/**
|
|
286
581
|
* Get files for page
|
|
287
582
|
* @param {Array} children
|
|
288
583
|
* @returns {Array} files for this page
|
|
289
584
|
*/
|
|
290
585
|
function getPage(children) {
|
|
291
|
-
const files = _.filter(children, function (item) {
|
|
586
|
+
const files = assetSearchContract.sortAssetItems(_.filter(children, function (item) {
|
|
292
587
|
return !!item.uri;
|
|
293
|
-
});
|
|
588
|
+
}), sort);
|
|
294
589
|
if (selectedClass.childrenLimit) {
|
|
295
590
|
return files.slice((selectedClass.page - 1) * selectedClass.childrenLimit, selectedClass.page * selectedClass.childrenLimit);
|
|
296
591
|
}
|
|
297
592
|
return files;
|
|
298
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Files for the table (split list payload or legacy children slice).
|
|
597
|
+
* @param {Object} content
|
|
598
|
+
* @returns {Array}
|
|
599
|
+
*/
|
|
600
|
+
function getFilesForDisplay(content) {
|
|
601
|
+
if (!content) {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
if (splitBrowse) {
|
|
605
|
+
return content.listItems || [];
|
|
606
|
+
}
|
|
607
|
+
return getPage(content.children || []);
|
|
608
|
+
}
|
|
609
|
+
function listCacheKey() {
|
|
610
|
+
return `${sort.field}:${sort.direction}:${selectedClass.page}`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Snapshot list request identity (folder + page/sort) when a browse starts.
|
|
615
|
+
* @returns {{path: String, cacheKey: String}}
|
|
616
|
+
*/
|
|
617
|
+
function captureListRequestContext() {
|
|
618
|
+
return {
|
|
619
|
+
path: selectedClass.path,
|
|
620
|
+
cacheKey: listCacheKey()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Whether navigation changed before an in-flight list response should apply.
|
|
626
|
+
* @param {{path: String, cacheKey: String}} captured
|
|
627
|
+
* @returns {Boolean}
|
|
628
|
+
*/
|
|
629
|
+
function isListRequestStale(captured) {
|
|
630
|
+
return !captured || captured.path !== selectedClass.path || captured.cacheKey !== listCacheKey();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* @param {Object} response
|
|
635
|
+
* @returns {Object}
|
|
636
|
+
*/
|
|
637
|
+
function unwrapBrowseResponse(response) {
|
|
638
|
+
if (response && response.success === false) {
|
|
639
|
+
return Promise.reject(response);
|
|
640
|
+
}
|
|
641
|
+
let data = response && response.data ? response.data : response;
|
|
642
|
+
data = updatePermissions(data);
|
|
643
|
+
if (data.children && data.children.length > 0) {
|
|
644
|
+
data.children = data.children.map(function (child) {
|
|
645
|
+
return updatePermissions(child);
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (data.items && data.items.length > 0) {
|
|
649
|
+
data.items = data.items.map(function (item) {
|
|
650
|
+
return updatePermissions(item);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return data;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* @param {String} path
|
|
658
|
+
* @param {Object} extraData
|
|
659
|
+
* @returns {Promise}
|
|
660
|
+
*/
|
|
661
|
+
function browseRequest(path, extraData) {
|
|
662
|
+
const parameters = {};
|
|
663
|
+
parameters[options.pathParam || 'path'] = path;
|
|
664
|
+
return Promise.resolve($$1.ajax({
|
|
665
|
+
url: options.browseUrl,
|
|
666
|
+
method: 'GET',
|
|
667
|
+
dataType: 'json',
|
|
668
|
+
timeout: ajaxTimeoutMs,
|
|
669
|
+
data: _.merge(parameters, options.params, extraData)
|
|
670
|
+
})).then(unwrapBrowseResponse);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* @param {Object} tree
|
|
675
|
+
* @param {String} path
|
|
676
|
+
* @param {Object} data
|
|
677
|
+
*/
|
|
678
|
+
function applyTreePayload(tree, path, data) {
|
|
679
|
+
const folderData = Object.assign({}, data, {
|
|
680
|
+
children: folderChildrenOnly(data)
|
|
681
|
+
});
|
|
682
|
+
if (!tree.path) {
|
|
683
|
+
_.merge(tree, folderData);
|
|
684
|
+
} else if (folderData.children) {
|
|
685
|
+
setToPath(tree, path, folderData);
|
|
686
|
+
if (!_.find(folderData.children, 'path')) {
|
|
687
|
+
markFolderEmptyAtPath(tree, path);
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
setToPath(tree, path, folderData);
|
|
691
|
+
markFolderEmptyAtPath(tree, path);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @param {Object} tree
|
|
697
|
+
* @param {String} path
|
|
698
|
+
* @param {Object} listData
|
|
699
|
+
* @param {String} cacheKey
|
|
700
|
+
*/
|
|
701
|
+
function applyListPayload(tree, path, listData, cacheKey) {
|
|
702
|
+
const node = getByExactPath(tree, path) || getByPath(tree, path);
|
|
703
|
+
if (!node || !listData) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
node.listItems = listData.items || [];
|
|
707
|
+
node.total = listData.total;
|
|
708
|
+
node.childrenLimit = listData.pageSize || listData.childrenLimit || selectedClass.childrenLimit;
|
|
709
|
+
node.listPage = listData.page;
|
|
710
|
+
node.totalIsApproximate = listData.totalIsApproximate;
|
|
711
|
+
node.listCacheKey = cacheKey;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Ensure upload responses appear in the table before Elasticsearch catches up.
|
|
716
|
+
* @param {String} path
|
|
717
|
+
* @param {Array<Object>} uploads
|
|
718
|
+
*/
|
|
719
|
+
function mergePendingUploads(path, uploads) {
|
|
720
|
+
if (!uploads || !uploads.length) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const node = getByExactPath(fileTree, path) || getByPath(fileTree, path);
|
|
724
|
+
if (!node) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (!Array.isArray(node.listItems)) {
|
|
728
|
+
node.listItems = [];
|
|
729
|
+
}
|
|
730
|
+
let added = 0;
|
|
731
|
+
uploads.forEach(function (uploaded) {
|
|
732
|
+
if (!uploaded || uploaded.error) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const normalized = updatePermissions(uploaded);
|
|
736
|
+
if (!normalized.uri && !normalized.name) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const exists = _.some(node.listItems, function (item) {
|
|
740
|
+
return normalized.uri && item.uri === normalized.uri || normalized.name && item.name === normalized.name;
|
|
741
|
+
});
|
|
742
|
+
if (exists) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
node.listItems.push(normalized);
|
|
746
|
+
added++;
|
|
747
|
+
});
|
|
748
|
+
if (!added) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (splitBrowse) {
|
|
752
|
+
node.listItems = assetSearchContract.sortAssetItems(node.listItems, sort);
|
|
753
|
+
}
|
|
754
|
+
const total = Number(node.total);
|
|
755
|
+
if (Number.isFinite(total) && total >= 0) {
|
|
756
|
+
node.total = total + added;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
299
760
|
/**
|
|
300
761
|
* Get the content of a folder, either in the model or load it
|
|
301
762
|
* @param {Object} tree - the tree model
|
|
302
763
|
* @param {String} path - the folder path (relative to the root)
|
|
303
764
|
* @param {Function} cb - called back with the content in 1st parameter
|
|
304
765
|
*/
|
|
766
|
+
function markFolderEmptyAtPath(tree, path) {
|
|
767
|
+
const node = getByExactPath(tree, path);
|
|
768
|
+
if (node) {
|
|
769
|
+
node.empty = true;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
305
772
|
function getFolderContent(tree, path, cb) {
|
|
773
|
+
if (splitBrowse) {
|
|
774
|
+
const existing = getByExactPath(tree, path) || getByPath(tree, path);
|
|
775
|
+
const needTree = !existing || !existing.children && !existing.empty;
|
|
776
|
+
const cacheKey = listCacheKey();
|
|
777
|
+
const needList = !existing || existing.listCacheKey !== cacheKey;
|
|
778
|
+
const treePromise = needTree ? (setTreeLoading(true), browseRequest(path, {
|
|
779
|
+
part: 'tree',
|
|
780
|
+
depth: 1,
|
|
781
|
+
sortBy: sort.field,
|
|
782
|
+
sortDir: sort.direction
|
|
783
|
+
}).then(function (data) {
|
|
784
|
+
setTreeLoading(false);
|
|
785
|
+
return data;
|
|
786
|
+
}, function (error) {
|
|
787
|
+
setTreeLoading(false);
|
|
788
|
+
return Promise.reject(error);
|
|
789
|
+
})) : Promise.resolve(null);
|
|
790
|
+
const listPromise = needList ? browseRequest(path, {
|
|
791
|
+
part: 'list',
|
|
792
|
+
page: selectedClass.page,
|
|
793
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
794
|
+
sortBy: sort.field,
|
|
795
|
+
sortDir: sort.direction
|
|
796
|
+
}) : Promise.resolve(null);
|
|
797
|
+
Promise.all([treePromise, listPromise]).then(function (_ref) {
|
|
798
|
+
let [treeData, listData] = _ref;
|
|
799
|
+
if (treeData) {
|
|
800
|
+
applyTreePayload(tree, path, treeData);
|
|
801
|
+
}
|
|
802
|
+
if (listData) {
|
|
803
|
+
applyListPayload(tree, path, listData, cacheKey);
|
|
804
|
+
}
|
|
805
|
+
cb(getByExactPath(tree, path) || getByPath(tree, path));
|
|
806
|
+
}).catch(function () {
|
|
807
|
+
cb(null);
|
|
808
|
+
});
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
306
811
|
let content = getByPath(tree, path);
|
|
307
812
|
if (!content || !content.children && !content.empty) {
|
|
308
813
|
loadContent(path).then(function (data) {
|
|
309
814
|
if (!tree.path) {
|
|
310
815
|
tree = _.merge(tree, data);
|
|
311
816
|
} else if (data.children) {
|
|
817
|
+
setToPath(tree, path, data);
|
|
312
818
|
if (!_.find(data.children, 'path')) {
|
|
313
819
|
// no subfolders inside folder
|
|
314
|
-
tree
|
|
820
|
+
markFolderEmptyAtPath(tree, path);
|
|
315
821
|
}
|
|
316
|
-
setToPath(tree, path, data);
|
|
317
822
|
} else {
|
|
318
|
-
tree
|
|
823
|
+
setToPath(tree, path, data);
|
|
824
|
+
markFolderEmptyAtPath(tree, path);
|
|
319
825
|
}
|
|
320
826
|
cb(data);
|
|
827
|
+
}).catch(function () {
|
|
828
|
+
cb(null);
|
|
321
829
|
});
|
|
322
830
|
} else if (content.children) {
|
|
323
831
|
const files = _.filter(content.children, function (item) {
|
|
324
832
|
return !!item.uri;
|
|
325
833
|
});
|
|
326
|
-
//
|
|
327
|
-
|
|
834
|
+
// Use folder total (not selectedClass): openInitialPath runs before selectFolder.
|
|
835
|
+
const expectedTotal = Number(content.total);
|
|
836
|
+
const pageSize = Number(content.childrenLimit) || selectedClass.childrenLimit || 11;
|
|
837
|
+
const page = selectedClass.page || 1;
|
|
838
|
+
// Missing/NaN total → unknown size; refetch so invalidated cache is not treated as complete.
|
|
839
|
+
if (!Number.isFinite(expectedTotal) || files.length < expectedTotal && files.length < page * pageSize) {
|
|
328
840
|
loadContent(path).then(function (data) {
|
|
329
841
|
const loadedFiles = _.filter(data.children, function (item) {
|
|
330
842
|
return !!item.uri;
|
|
331
843
|
});
|
|
844
|
+
const node = getByExactPath(tree, path);
|
|
845
|
+
if (node && !Number.isFinite(expectedTotal)) {
|
|
846
|
+
node.children = _.filter(node.children, function (item) {
|
|
847
|
+
return !item.uri;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
332
850
|
setToPath(tree, path, {
|
|
333
851
|
children: loadedFiles,
|
|
334
852
|
total: data.total,
|
|
335
853
|
childrenLimit: data.childrenLimit
|
|
336
854
|
});
|
|
337
|
-
content = getByPath(tree, path);
|
|
855
|
+
content = getByExactPath(tree, path) || getByPath(tree, path);
|
|
856
|
+
cb(content);
|
|
857
|
+
}).catch(function () {
|
|
338
858
|
cb(content);
|
|
339
859
|
});
|
|
340
860
|
} else {
|
|
@@ -384,6 +904,29 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
384
904
|
return match;
|
|
385
905
|
}
|
|
386
906
|
|
|
907
|
+
/**
|
|
908
|
+
* Get a subtree by exact path equality.
|
|
909
|
+
* @param {Object} tree
|
|
910
|
+
* @param {String} path
|
|
911
|
+
* @returns {Object|undefined}
|
|
912
|
+
*/
|
|
913
|
+
function getByExactPath(tree, path) {
|
|
914
|
+
let match;
|
|
915
|
+
if (tree) {
|
|
916
|
+
if (tree.path === path) {
|
|
917
|
+
match = tree;
|
|
918
|
+
} else if (tree.children) {
|
|
919
|
+
_.forEach(tree.children, function (child) {
|
|
920
|
+
match = getByExactPath(child, path);
|
|
921
|
+
if (match) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return match;
|
|
928
|
+
}
|
|
929
|
+
|
|
387
930
|
/**
|
|
388
931
|
* Merge data into at into the subtree
|
|
389
932
|
* @param {Object} tree - the tree model
|
|
@@ -427,8 +970,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
427
970
|
return child.path === path || child.name && tree.path + child.name === path || child.uri === path;
|
|
428
971
|
});
|
|
429
972
|
done = removed.length > 0;
|
|
430
|
-
|
|
431
|
-
|
|
973
|
+
if (done) {
|
|
974
|
+
if (Number.isFinite(Number(tree.total))) {
|
|
975
|
+
tree.total = Math.max(0, Number(tree.total) - removed.length);
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
432
978
|
_.forEach(tree.children, function (child) {
|
|
433
979
|
done = removeFromPath(child, path);
|
|
434
980
|
if (done) {
|
|
@@ -440,31 +986,136 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
440
986
|
return done;
|
|
441
987
|
}
|
|
442
988
|
|
|
989
|
+
/**
|
|
990
|
+
* Drop cached file rows for a folder so the next load hits the service
|
|
991
|
+
* with the current sort. Nested folder nodes are kept.
|
|
992
|
+
* @param {String} path
|
|
993
|
+
*/
|
|
994
|
+
function invalidateFolderFiles(path) {
|
|
995
|
+
const content = getByExactPath(fileTree, path);
|
|
996
|
+
if (!content) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (splitBrowse) {
|
|
1000
|
+
delete content.listItems;
|
|
1001
|
+
delete content.listCacheKey;
|
|
1002
|
+
delete content.listPage;
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (Array.isArray(content.children)) {
|
|
1006
|
+
content.children = content.children.filter(function (child) {
|
|
1007
|
+
return child.path && !child.uri;
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Replace a folder node with a freshly loaded payload (files + folders).
|
|
1014
|
+
* @param {String} path
|
|
1015
|
+
* @param {Object} data
|
|
1016
|
+
*/
|
|
1017
|
+
function replaceFolderContent(path, data, cacheKey) {
|
|
1018
|
+
const content = getByExactPath(fileTree, path);
|
|
1019
|
+
if (!content || !data) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (splitBrowse && Object.prototype.hasOwnProperty.call(data, 'items')) {
|
|
1023
|
+
applyListPayload(fileTree, path, data, cacheKey || listCacheKey());
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (Object.prototype.hasOwnProperty.call(data, 'children')) {
|
|
1027
|
+
content.children = data.children;
|
|
1028
|
+
} else {
|
|
1029
|
+
content.children = [];
|
|
1030
|
+
content.empty = true;
|
|
1031
|
+
content.total = 0;
|
|
1032
|
+
}
|
|
1033
|
+
if (Object.prototype.hasOwnProperty.call(data, 'total')) {
|
|
1034
|
+
content.total = data.total;
|
|
1035
|
+
}
|
|
1036
|
+
if (Object.prototype.hasOwnProperty.call(data, 'childrenLimit')) {
|
|
1037
|
+
content.childrenLimit = data.childrenLimit;
|
|
1038
|
+
}
|
|
1039
|
+
if (data.label) {
|
|
1040
|
+
content.label = data.label;
|
|
1041
|
+
}
|
|
1042
|
+
if (data.path) {
|
|
1043
|
+
content.path = data.path;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
443
1047
|
/**
|
|
444
1048
|
* Get the content of a folder
|
|
445
1049
|
* @param {String} path - the folder path
|
|
446
|
-
* @returns {
|
|
1050
|
+
* @returns {Promise} resolves with folder content
|
|
447
1051
|
*/
|
|
448
1052
|
function loadContent(path) {
|
|
449
1053
|
const parameters = {};
|
|
450
|
-
parameters[options.pathParam] = path;
|
|
451
|
-
|
|
1054
|
+
parameters[options.pathParam || 'path'] = path;
|
|
1055
|
+
setTreeLoading(true);
|
|
1056
|
+
return Promise.resolve($$1.ajax({
|
|
452
1057
|
url: options.browseUrl,
|
|
453
1058
|
method: 'GET',
|
|
454
1059
|
dataType: 'json',
|
|
1060
|
+
timeout: ajaxTimeoutMs,
|
|
455
1061
|
data: _.merge(parameters, options.params, {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1062
|
+
// depth=2 so each rendered child folder includes its own dir children,
|
|
1063
|
+
// allowing leaf folders to hide the expand chevron without an extra click.
|
|
1064
|
+
depth: 2,
|
|
1065
|
+
childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
1066
|
+
sortBy: sort.field,
|
|
1067
|
+
sortDir: sort.direction
|
|
1068
|
+
})
|
|
1069
|
+
})).then(function (response) {
|
|
1070
|
+
if (response && response.success === false) {
|
|
1071
|
+
return Promise.reject(response);
|
|
1072
|
+
}
|
|
1073
|
+
let data = response && response.data ? response.data : response;
|
|
1074
|
+
data = updatePermissions(data);
|
|
1075
|
+
if (data.children && data.children.length > 0) {
|
|
1076
|
+
data.children.map(responseChildren => updatePermissions(responseChildren));
|
|
1077
|
+
}
|
|
1078
|
+
return data;
|
|
1079
|
+
}).then(function (data) {
|
|
1080
|
+
setTreeLoading(false);
|
|
1081
|
+
return data;
|
|
1082
|
+
}, function (error) {
|
|
1083
|
+
setTreeLoading(false);
|
|
1084
|
+
return Promise.reject(error);
|
|
465
1085
|
});
|
|
466
1086
|
}
|
|
467
1087
|
|
|
1088
|
+
/**
|
|
1089
|
+
* Whether a folder node has nested directory children.
|
|
1090
|
+
* @param {Object} node
|
|
1091
|
+
* @returns {boolean|null} true/false when known; null when children were not loaded yet
|
|
1092
|
+
*/
|
|
1093
|
+
function hasNestedFolderChildren(node) {
|
|
1094
|
+
if (!node || node.empty === true) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
if (!Array.isArray(node.children)) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
return Boolean(_.find(node.children, 'path'));
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Show or hide the expand chevron on a folder anchor (keep spacer width).
|
|
1105
|
+
* @param {jQuery} $anchor
|
|
1106
|
+
* @param {boolean} hasNested
|
|
1107
|
+
*/
|
|
1108
|
+
function setFolderToggleState($anchor, hasNested) {
|
|
1109
|
+
const $toggle = $anchor.children('.tree-toggle');
|
|
1110
|
+
if (hasNested) {
|
|
1111
|
+
$toggle.addClass('icon-right').removeClass('is-leaf');
|
|
1112
|
+
$anchor.removeClass('is-leaf');
|
|
1113
|
+
} else {
|
|
1114
|
+
$toggle.removeClass('icon-right').addClass('is-leaf');
|
|
1115
|
+
$anchor.removeClass('opened').addClass('is-leaf');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
468
1119
|
/**
|
|
469
1120
|
* Update the HTML Tree
|
|
470
1121
|
* @param {Object} data - the tree data
|
|
@@ -476,7 +1127,10 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
476
1127
|
if (typeof data.relPath === 'undefined') {
|
|
477
1128
|
data.relPath = data.path;
|
|
478
1129
|
}
|
|
1130
|
+
// Unknown nested state (lazy depth) keeps the chevron until the folder is opened.
|
|
1131
|
+
data.showToggle = hasNestedFolderChildren(data) !== false;
|
|
479
1132
|
$parent.append(folderTpl(data));
|
|
1133
|
+
return;
|
|
480
1134
|
}
|
|
481
1135
|
if (data && data.children && _.isArray(data.children) && !data.empty) {
|
|
482
1136
|
_.forEach(data.children, function (child) {
|
|
@@ -497,7 +1151,7 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
497
1151
|
selectedClass = {
|
|
498
1152
|
path,
|
|
499
1153
|
total: Number.isFinite(normalizedTotal) && normalizedTotal >= 0 ? normalizedTotal : 0,
|
|
500
|
-
childrenLimit: Number.isFinite(normalizedChildrenLimit) && normalizedChildrenLimit > 0 ? normalizedChildrenLimit : selectedClass.childrenLimit ||
|
|
1154
|
+
childrenLimit: Number.isFinite(normalizedChildrenLimit) && normalizedChildrenLimit > 0 ? normalizedChildrenLimit : selectedClass.childrenLimit || 11,
|
|
501
1155
|
page: 1
|
|
502
1156
|
};
|
|
503
1157
|
}
|
|
@@ -505,7 +1159,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
505
1159
|
* Render pagination
|
|
506
1160
|
*/
|
|
507
1161
|
function renderPagination() {
|
|
508
|
-
|
|
1162
|
+
if (searchMode) {
|
|
1163
|
+
// Drop browse pagination controls/handlers when search owns the table.
|
|
1164
|
+
$paginationContainer.empty();
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
509
1167
|
const total = Number(selectedClass.total);
|
|
510
1168
|
const childrenLimit = Number(selectedClass.childrenLimit);
|
|
511
1169
|
$paginationContainer.empty();
|
|
@@ -527,18 +1185,92 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
|
|
|
527
1185
|
}).render($paginationContainer);
|
|
528
1186
|
}
|
|
529
1187
|
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Re-fetch the current folder with the active sort and publish files.
|
|
1190
|
+
*/
|
|
1191
|
+
function reloadSortedFolder(pendingUploads) {
|
|
1192
|
+
const listRequest = captureListRequestContext();
|
|
1193
|
+
const path = listRequest.path;
|
|
1194
|
+
if (!searchMode && isActiveBrowser()) {
|
|
1195
|
+
setFilesLoading(true);
|
|
1196
|
+
}
|
|
1197
|
+
const loadPromise = splitBrowse ? browseRequest(path, {
|
|
1198
|
+
part: 'list',
|
|
1199
|
+
page: selectedClass.page,
|
|
1200
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
1201
|
+
sortBy: sort.field,
|
|
1202
|
+
sortDir: sort.direction
|
|
1203
|
+
}) : loadContent(path);
|
|
1204
|
+
loadPromise.then(function (data) {
|
|
1205
|
+
if (!data) {
|
|
1206
|
+
setFilesLoading(false);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (isListRequestStale(listRequest)) {
|
|
1210
|
+
setFilesLoading(false);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
replaceFolderContent(path, data, listRequest.cacheKey);
|
|
1214
|
+
mergePendingUploads(path, pendingUploads);
|
|
1215
|
+
const content = getByExactPath(fileTree, path) || data;
|
|
1216
|
+
selectFolder(content, content.path || path);
|
|
1217
|
+
}).catch(function (error) {
|
|
1218
|
+
LOGGER.error(error);
|
|
1219
|
+
setFilesLoading(false);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
|
|
530
1223
|
/**
|
|
531
1224
|
* Load page
|
|
532
1225
|
*/
|
|
533
1226
|
function loadPage() {
|
|
534
|
-
const
|
|
1227
|
+
const listRequest = captureListRequestContext();
|
|
1228
|
+
if (!searchMode && isActiveBrowser()) {
|
|
1229
|
+
setFilesLoading(true);
|
|
1230
|
+
}
|
|
1231
|
+
if (splitBrowse) {
|
|
1232
|
+
const path = listRequest.path;
|
|
1233
|
+
browseRequest(path, {
|
|
1234
|
+
part: 'list',
|
|
1235
|
+
page: selectedClass.page,
|
|
1236
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
1237
|
+
sortBy: sort.field,
|
|
1238
|
+
sortDir: sort.direction
|
|
1239
|
+
}).then(function (data) {
|
|
1240
|
+
if (isListRequestStale(listRequest)) {
|
|
1241
|
+
setFilesLoading(false);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
applyListPayload(fileTree, path, data, listRequest.cacheKey);
|
|
1245
|
+
const content = getByExactPath(fileTree, path);
|
|
1246
|
+
if (content) {
|
|
1247
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), content.path, content]);
|
|
1248
|
+
renderPagination();
|
|
1249
|
+
}
|
|
1250
|
+
setFilesLoading(false);
|
|
1251
|
+
}).catch(function (error) {
|
|
1252
|
+
LOGGER.error(error);
|
|
1253
|
+
setFilesLoading(false);
|
|
1254
|
+
});
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const subTree = getByPath(fileTree, selectedClass.path) || fileTree;
|
|
535
1258
|
|
|
536
1259
|
//get the folder content
|
|
537
1260
|
getFolderContent(subTree, selectedClass.path, function (content) {
|
|
1261
|
+
if (isListRequestStale(listRequest)) {
|
|
1262
|
+
if (!searchMode) {
|
|
1263
|
+
setFilesLoading(false);
|
|
1264
|
+
}
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
538
1267
|
indexTree(fileTree);
|
|
539
1268
|
if (content) {
|
|
540
1269
|
//internal event to set the file-selector content
|
|
541
|
-
$container.trigger(`folderselect.${
|
|
1270
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), content.path, content]);
|
|
1271
|
+
}
|
|
1272
|
+
if (!searchMode) {
|
|
1273
|
+
setFilesLoading(false);
|
|
542
1274
|
}
|
|
543
1275
|
});
|
|
544
1276
|
}
|