@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
|
@@ -11,65 +11,194 @@
|
|
|
11
11
|
*
|
|
12
12
|
* You should have received a copy of the GNU General Public License
|
|
13
13
|
* along with this program; if not, write to the Free Software
|
|
14
|
-
* Foundation, Inc.,
|
|
14
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
15
15
|
*
|
|
16
|
-
* Copyright (c) 2018-
|
|
16
|
+
* Copyright (c) 2018-2026 (original work) Open Assessment Technologies SA;
|
|
17
17
|
*/
|
|
18
18
|
import $ from 'jquery';
|
|
19
19
|
import _ from 'lodash';
|
|
20
|
-
import request from 'core/request';
|
|
21
20
|
import paginationComponent from 'ui/pagination';
|
|
22
21
|
import rootFolderTpl from 'ui/resourcemgr/tpl/rootFolder';
|
|
23
22
|
import folderTpl from 'ui/resourcemgr/tpl/folder';
|
|
23
|
+
import loggerFactory from 'core/logger';
|
|
24
24
|
import updatePermissions from './util/updatePermissions';
|
|
25
|
+
import { DEFAULT_SORT, sortAssetItems } from 'ui/resourcemgr/assetSearchContract';
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
+
const NS = 'resourcemgr';
|
|
28
|
+
const LOGGER = loggerFactory(`ui/${NS}`);
|
|
29
|
+
const DEFAULT_AJAX_TIMEOUT_MS = 30000;
|
|
30
|
+
const TREE_LOADING_REF_KEY = `${NS}TreeLoadingRefCount`;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {Object} data
|
|
34
|
+
* @returns {Array}
|
|
35
|
+
*/
|
|
36
|
+
function folderChildrenOnly(data) {
|
|
37
|
+
return _.filter(data && data.children, function (child) {
|
|
38
|
+
return child && child.path && !child.uri;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
27
41
|
|
|
28
42
|
export default function (options) {
|
|
43
|
+
if (!options.browseUrl && options.url) {
|
|
44
|
+
options.browseUrl = options.url;
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
const root = options.root || 'local';
|
|
30
48
|
const rootPath = options.path || '/';
|
|
49
|
+
const initialPath = options.initialPath || rootPath;
|
|
31
50
|
const $container = options.$target;
|
|
32
51
|
const $fileBrowser = $('.file-browser .file-browser-wrapper', $container);
|
|
33
52
|
const $divContainer = $(`.${root}`, $fileBrowser);
|
|
34
53
|
const $folderContainer = $('.folders', $divContainer);
|
|
54
|
+
const $paginationContainer = $('.pagination-bottom', $container);
|
|
35
55
|
const fileTree = {};
|
|
36
56
|
// for pagination
|
|
37
57
|
let selectedClass = {
|
|
38
58
|
path: rootPath,
|
|
39
|
-
childrenLimit:
|
|
59
|
+
childrenLimit: 11,
|
|
40
60
|
total: 0,
|
|
41
61
|
page: 1
|
|
42
62
|
};
|
|
63
|
+
let searchMode = false;
|
|
64
|
+
let sort = Object.assign({}, DEFAULT_SORT);
|
|
65
|
+
const splitBrowse = options.splitBrowse !== false;
|
|
66
|
+
const ajaxTimeoutMs = Number.isFinite(Number(options.ajaxTimeoutMs)) && Number(options.ajaxTimeoutMs) > 0
|
|
67
|
+
? Number(options.ajaxTimeoutMs)
|
|
68
|
+
: DEFAULT_AJAX_TIMEOUT_MS;
|
|
69
|
+
const $treeLoadingOverlay = $('.file-browser-tree-loading', $fileBrowser);
|
|
70
|
+
const $treeLoadingStatus = $('.file-browser-tree-loading-status', $treeLoadingOverlay);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Ref-count in-flight browse requests and toggle the tree loading overlay.
|
|
74
|
+
* @param {Boolean} increment - true when a load starts, false when it finishes
|
|
75
|
+
*/
|
|
76
|
+
function setTreeLoading(increment) {
|
|
77
|
+
let treeLoadingRefCount = Number($fileBrowser.data(TREE_LOADING_REF_KEY)) || 0;
|
|
78
|
+
treeLoadingRefCount += increment ? 1 : -1;
|
|
79
|
+
if (treeLoadingRefCount < 0) {
|
|
80
|
+
treeLoadingRefCount = 0;
|
|
81
|
+
}
|
|
82
|
+
$fileBrowser.data(TREE_LOADING_REF_KEY, treeLoadingRefCount);
|
|
83
|
+
const loading = treeLoadingRefCount > 0;
|
|
84
|
+
$fileBrowser.toggleClass('is-tree-loading', loading);
|
|
85
|
+
$fileBrowser.attr('aria-busy', loading ? 'true' : 'false');
|
|
86
|
+
$treeLoadingOverlay.toggleClass('hidden', !loading).attr('aria-hidden', loading ? 'false' : 'true');
|
|
87
|
+
$treeLoadingStatus.attr('aria-busy', loading ? 'true' : 'false');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Show or hide the asset table loading overlay (browse mode only).
|
|
92
|
+
* @param {Boolean} loading
|
|
93
|
+
*/
|
|
94
|
+
function setFilesLoading(loading) {
|
|
95
|
+
if (searchMode) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
$container.trigger(`filesloading.${NS}`, [!!loading]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
$container.on(`searchmode.${NS}`, function (e, enabled) {
|
|
102
|
+
searchMode = !!enabled;
|
|
103
|
+
if (searchMode) {
|
|
104
|
+
$container.trigger(`filesloading.${NS}`, [false]);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
$container.on(`sortchange.${NS}`, function (e, nextSort) {
|
|
109
|
+
sort = Object.assign({}, DEFAULT_SORT, nextSort || {});
|
|
110
|
+
selectedClass.page = 1;
|
|
111
|
+
invalidateFolderFiles(selectedClass.path);
|
|
112
|
+
if (searchMode || !isActiveBrowser()) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
reloadSortedFolder();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
$container.on(`searchclear.${NS}`, function (e, path) {
|
|
119
|
+
const targetPath = path || selectedClass.path;
|
|
120
|
+
selectedClass.page = 1;
|
|
121
|
+
invalidateFolderFiles(targetPath);
|
|
122
|
+
if (!isActiveBrowser()) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath) || fileTree;
|
|
126
|
+
if (!searchMode) {
|
|
127
|
+
setFilesLoading(true);
|
|
128
|
+
}
|
|
129
|
+
getFolderContent(subTree, targetPath, function (content) {
|
|
130
|
+
if (content) {
|
|
131
|
+
selectFolder(content, targetPath);
|
|
132
|
+
} else {
|
|
133
|
+
setFilesLoading(false);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Reopen with resolved parent (AC3 edit/change): leave search, open folder again.
|
|
139
|
+
$container.on(`applycontext.${NS}`, function (e, ctx) {
|
|
140
|
+
const path = (ctx && ctx.path) || rootPath;
|
|
141
|
+
const pathIsTaomedia = path && String(path).indexOf('taomedia://') === 0;
|
|
142
|
+
if (pathIsTaomedia && root === 'local') {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!pathIsTaomedia && root !== 'local') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
$container.data('activeFileBrowserRoot', root);
|
|
149
|
+
if (searchMode) {
|
|
150
|
+
$container.trigger(`requestexitsearch.${NS}`);
|
|
151
|
+
}
|
|
152
|
+
selectedClass.page = 1;
|
|
153
|
+
openInitialPath(path);
|
|
154
|
+
});
|
|
43
155
|
|
|
44
156
|
//load the content of the ROOT
|
|
157
|
+
const showInitialListLoading = !searchMode && root === 'local';
|
|
158
|
+
if (showInitialListLoading) {
|
|
159
|
+
setFilesLoading(true);
|
|
160
|
+
}
|
|
45
161
|
getFolderContent(fileTree, rootPath, function (content) {
|
|
162
|
+
if (!content) {
|
|
163
|
+
if (showInitialListLoading) {
|
|
164
|
+
setFilesLoading(false);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
46
168
|
indexTree(content);
|
|
47
169
|
|
|
48
170
|
//create the tree node for the ROOT folder by default once the initial content loaded
|
|
49
|
-
$folderContainer.append(
|
|
171
|
+
$folderContainer.append(
|
|
172
|
+
rootFolderTpl(
|
|
173
|
+
Object.assign({}, content, {
|
|
174
|
+
showToggle: hasNestedFolderChildren(content) !== false
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
);
|
|
50
178
|
|
|
51
179
|
const $rootNode = $('.root-folder', $folderContainer);
|
|
52
180
|
//create an inner list and append found elements
|
|
53
181
|
const $innerList = $('.root ul', $folderContainer);
|
|
54
|
-
if (content
|
|
182
|
+
if (hasNestedFolderChildren(content)) {
|
|
55
183
|
$rootNode.addClass('opened');
|
|
184
|
+
} else if (hasNestedFolderChildren(content) === false) {
|
|
185
|
+
setFolderToggleState($rootNode, false);
|
|
56
186
|
}
|
|
57
187
|
updateFolders(content, $innerList);
|
|
58
188
|
|
|
59
|
-
if (content.permissions.read && !options.hasAlreadySelected) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
content.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
content
|
|
67
|
-
]);
|
|
68
|
-
renderPagination();
|
|
189
|
+
if (content.permissions && content.permissions.read && !options.hasAlreadySelected) {
|
|
190
|
+
if (initialPath && initialPath !== rootPath) {
|
|
191
|
+
openInitialPath(initialPath);
|
|
192
|
+
} else {
|
|
193
|
+
selectFolder(content, content.path);
|
|
194
|
+
syncTreeActiveFolder(content.path || rootPath);
|
|
195
|
+
}
|
|
69
196
|
|
|
70
197
|
if (root !== 'local') {
|
|
71
198
|
options.hasAlreadySelected = true;
|
|
72
199
|
}
|
|
200
|
+
} else if (showInitialListLoading) {
|
|
201
|
+
setFilesLoading(false);
|
|
73
202
|
}
|
|
74
203
|
});
|
|
75
204
|
|
|
@@ -77,9 +206,17 @@ export default function (options) {
|
|
|
77
206
|
$divContainer.off('click', '.folders a').on('click', '.folders a', function (e) {
|
|
78
207
|
e.preventDefault();
|
|
79
208
|
const $selected = $(this);
|
|
80
|
-
const $folders = $('.folders li', $fileBrowser);
|
|
81
209
|
const fullPath = $selected.data('path');
|
|
82
|
-
const subTree =
|
|
210
|
+
const subTree = getByExactPath(fileTree, fullPath);
|
|
211
|
+
const openingFolder = !searchMode && fullPath !== selectedClass.path;
|
|
212
|
+
|
|
213
|
+
if (openingFolder) {
|
|
214
|
+
selectedClass.page = 1;
|
|
215
|
+
}
|
|
216
|
+
if (!searchMode) {
|
|
217
|
+
setFilesLoading(true);
|
|
218
|
+
}
|
|
219
|
+
invalidateFolderFiles(fullPath);
|
|
83
220
|
|
|
84
221
|
//get the folder content
|
|
85
222
|
getFolderContent(subTree, fullPath, function (content) {
|
|
@@ -88,10 +225,12 @@ export default function (options) {
|
|
|
88
225
|
if (content) {
|
|
89
226
|
//either create the inner list of the content is new or just show it
|
|
90
227
|
let $innerList = $selected.siblings('ul');
|
|
91
|
-
|
|
228
|
+
const nested = hasNestedFolderChildren(content);
|
|
229
|
+
if (!$innerList.length && nested) {
|
|
92
230
|
$innerList = $('<ul></ul>').insertAfter($selected);
|
|
93
231
|
updateFolders(content, $innerList);
|
|
94
232
|
$selected.addClass('opened');
|
|
233
|
+
setFolderToggleState($selected, true);
|
|
95
234
|
} else if ($innerList.length) {
|
|
96
235
|
if ($innerList.css('display') === 'none') {
|
|
97
236
|
$innerList.show();
|
|
@@ -100,68 +239,222 @@ export default function (options) {
|
|
|
100
239
|
$innerList.hide();
|
|
101
240
|
$selected.removeClass('opened');
|
|
102
241
|
}
|
|
242
|
+
} else if (nested === false) {
|
|
243
|
+
// Leaf folder: keep alignment spacer, hide expand chevron.
|
|
244
|
+
setFolderToggleState($selected, false);
|
|
103
245
|
}
|
|
104
246
|
|
|
105
|
-
|
|
106
|
-
$folders.removeClass('active');
|
|
107
|
-
$selected.parent('li').addClass('active');
|
|
247
|
+
syncTreeActiveFolder(fullPath, $selected);
|
|
108
248
|
|
|
109
249
|
//internal event to set the file-selector content
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
getPage(content.children),
|
|
114
|
-
content.path,
|
|
115
|
-
content
|
|
116
|
-
]);
|
|
117
|
-
renderPagination();
|
|
250
|
+
selectFolder(content, fullPath);
|
|
251
|
+
} else if (!searchMode) {
|
|
252
|
+
setFilesLoading(false);
|
|
118
253
|
}
|
|
119
254
|
});
|
|
120
255
|
});
|
|
121
256
|
|
|
122
|
-
$container.on(`filenew.${
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
if (root !== 'local' || !_.find(subTree.children, { name: file.name })) {
|
|
129
|
-
updatePermissions(file);
|
|
130
|
-
const childrenFilesOnly = _.filter(subTree.children, function (child) {
|
|
131
|
-
// Only file object has link property
|
|
132
|
-
return Object.prototype.hasOwnProperty.call(child, 'link');
|
|
133
|
-
});
|
|
257
|
+
$container.on(`filenew.${NS}`, function (e, file, path) {
|
|
258
|
+
const targetPath = path || selectedClass.path;
|
|
259
|
+
if (!targetPath || searchMode) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
134
262
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
$container.trigger(`folderselect.${ns}`, [subTree.label, getPage(subTree.children), path, subTree]);
|
|
145
|
-
renderPagination();
|
|
146
|
-
}
|
|
263
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
264
|
+
if (activeRoot && activeRoot !== root) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath);
|
|
269
|
+
if (subTree && root === 'local' && file && file.name && _.find(subTree.children, { name: file.name })) {
|
|
270
|
+
return;
|
|
147
271
|
}
|
|
272
|
+
|
|
273
|
+
if (targetPath !== selectedClass.path) {
|
|
274
|
+
selectedClass.path = targetPath;
|
|
275
|
+
}
|
|
276
|
+
selectedClass.page = 1;
|
|
277
|
+
|
|
278
|
+
invalidateFolderFiles(targetPath);
|
|
279
|
+
const pendingUpload =
|
|
280
|
+
file && !file.error && (file.uri || file.name) ? file : null;
|
|
281
|
+
reloadSortedFolder(pendingUpload ? [pendingUpload] : []);
|
|
148
282
|
});
|
|
149
283
|
|
|
150
|
-
$container.on(`filedelete.${
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
loadPage();
|
|
284
|
+
$container.on(`filedelete.${NS}`, function (e, path) {
|
|
285
|
+
if (searchMode) {
|
|
286
|
+
return;
|
|
154
287
|
}
|
|
288
|
+
|
|
289
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
290
|
+
if (activeRoot && activeRoot !== root) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (path) {
|
|
295
|
+
removeFromPath(fileTree, path);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
invalidateFolderFiles(selectedClass.path);
|
|
299
|
+
reloadSortedFolder();
|
|
155
300
|
});
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Open and select an initial folder path after the root tree is available.
|
|
304
|
+
* @param {String} path
|
|
305
|
+
*/
|
|
306
|
+
function findFolderLink(path) {
|
|
307
|
+
return $folderContainer.find('a').filter(function () {
|
|
308
|
+
return $(this).data('path') === path;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Render directory children under an expanded folder anchor (replace / reopen).
|
|
314
|
+
* @param {String} parentPath
|
|
315
|
+
* @param {Object} parentContent
|
|
316
|
+
*/
|
|
317
|
+
function mountFolderBranch(parentPath, parentContent) {
|
|
318
|
+
const $parentLink = findFolderLink(parentPath);
|
|
319
|
+
if (!$parentLink.length || !parentContent) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
let $innerList = $parentLink.siblings('ul');
|
|
323
|
+
if (!$innerList.length) {
|
|
324
|
+
$innerList = $('<ul></ul>').insertAfter($parentLink);
|
|
325
|
+
}
|
|
326
|
+
updateFolders(parentContent, $innerList);
|
|
327
|
+
$parentLink.addClass('opened');
|
|
328
|
+
if ($innerList.css('display') === 'none') {
|
|
329
|
+
$innerList.show();
|
|
330
|
+
}
|
|
331
|
+
setFolderToggleState($parentLink, hasNestedFolderChildren(parentContent) !== false);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function markFolderActive(path) {
|
|
335
|
+
const $targetLink = findFolderLink(path);
|
|
336
|
+
if ($targetLink.length) {
|
|
337
|
+
$targetLink.parents('li').each(function () {
|
|
338
|
+
const $li = $(this);
|
|
339
|
+
const $anchor = $li.children('a');
|
|
340
|
+
const $list = $li.children('ul');
|
|
341
|
+
$anchor.addClass('opened');
|
|
342
|
+
if ($list.length) {
|
|
343
|
+
$list.show();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
syncTreeActiveFolder(path, $targetLink);
|
|
347
|
+
} else if (path === rootPath) {
|
|
348
|
+
syncTreeActiveFolder(rootPath);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function openInitialPath(path) {
|
|
353
|
+
getFolderContent(fileTree, path, function (content) {
|
|
354
|
+
indexTree(fileTree);
|
|
355
|
+
if (!content) {
|
|
356
|
+
const rootContent = getByPath(fileTree, rootPath) || fileTree;
|
|
357
|
+
const fallbackPath = rootContent.path || rootPath;
|
|
358
|
+
selectFolder(rootContent, fallbackPath);
|
|
359
|
+
syncTreeActiveFolder(fallbackPath);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function finishOpen() {
|
|
364
|
+
markFolderActive(path);
|
|
365
|
+
selectFolder(content, path);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (findFolderLink(path).length) {
|
|
369
|
+
finishOpen();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const parentFolderPath = content.parentFolderPath;
|
|
374
|
+
if (parentFolderPath && parentFolderPath !== path) {
|
|
375
|
+
getFolderContent(fileTree, parentFolderPath, function (parentContent) {
|
|
376
|
+
mountFolderBranch(parentFolderPath, parentContent);
|
|
377
|
+
finishOpen();
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
finishOpen();
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Mark the folder row that matches path as active (open folder icon for leaves).
|
|
388
|
+
* @param {String} path
|
|
389
|
+
* @param {jQuery} [$link]
|
|
390
|
+
*/
|
|
391
|
+
function syncTreeActiveFolder(path, $link) {
|
|
392
|
+
$('.folders li', $fileBrowser).removeClass('active');
|
|
393
|
+
const $target =
|
|
394
|
+
$link && $link.length
|
|
395
|
+
? $link
|
|
396
|
+
: $folderContainer.find('a').filter(function () {
|
|
397
|
+
return $(this).data('path') === path;
|
|
398
|
+
});
|
|
399
|
+
if ($target.length) {
|
|
400
|
+
$target.parent('li').addClass('active');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Whether this media source currently owns the file table.
|
|
406
|
+
* @returns {Boolean}
|
|
407
|
+
*/
|
|
408
|
+
function isActiveBrowser() {
|
|
409
|
+
return $container.data('activeFileBrowserRoot') === root;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Select a folder and publish its page of files to the selector.
|
|
414
|
+
* @param {Object} content
|
|
415
|
+
* @param {String} path
|
|
416
|
+
*/
|
|
417
|
+
function selectFolder(content, path) {
|
|
418
|
+
if (!content) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
$container.data('activeFileBrowserRoot', root);
|
|
422
|
+
if (searchMode) {
|
|
423
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
424
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
425
|
+
content.label,
|
|
426
|
+
getFilesForDisplay(content),
|
|
427
|
+
path,
|
|
428
|
+
content
|
|
429
|
+
]);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
updateSelectedClass(path, content.total, content.childrenLimit);
|
|
433
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
434
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
435
|
+
content.label,
|
|
436
|
+
getFilesForDisplay(content),
|
|
437
|
+
path,
|
|
438
|
+
content
|
|
439
|
+
]);
|
|
440
|
+
renderPagination();
|
|
441
|
+
if (!searchMode) {
|
|
442
|
+
setFilesLoading(false);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
156
446
|
/**
|
|
157
447
|
* Get files for page
|
|
158
448
|
* @param {Array} children
|
|
159
449
|
* @returns {Array} files for this page
|
|
160
450
|
*/
|
|
161
451
|
function getPage(children) {
|
|
162
|
-
const files =
|
|
163
|
-
|
|
164
|
-
|
|
452
|
+
const files = sortAssetItems(
|
|
453
|
+
_.filter(children, function (item) {
|
|
454
|
+
return !!item.uri;
|
|
455
|
+
}),
|
|
456
|
+
sort
|
|
457
|
+
);
|
|
165
458
|
if (selectedClass.childrenLimit) {
|
|
166
459
|
return files.slice(
|
|
167
460
|
(selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
@@ -170,45 +463,295 @@ export default function (options) {
|
|
|
170
463
|
}
|
|
171
464
|
return files;
|
|
172
465
|
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Files for the table (split list payload or legacy children slice).
|
|
469
|
+
* @param {Object} content
|
|
470
|
+
* @returns {Array}
|
|
471
|
+
*/
|
|
472
|
+
function getFilesForDisplay(content) {
|
|
473
|
+
if (!content) {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
if (splitBrowse) {
|
|
477
|
+
return content.listItems || [];
|
|
478
|
+
}
|
|
479
|
+
return getPage(content.children || []);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function listCacheKey() {
|
|
483
|
+
return `${sort.field}:${sort.direction}:${selectedClass.page}`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Snapshot list request identity (folder + page/sort) when a browse starts.
|
|
488
|
+
* @returns {{path: String, cacheKey: String}}
|
|
489
|
+
*/
|
|
490
|
+
function captureListRequestContext() {
|
|
491
|
+
return {
|
|
492
|
+
path: selectedClass.path,
|
|
493
|
+
cacheKey: listCacheKey()
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Whether navigation changed before an in-flight list response should apply.
|
|
499
|
+
* @param {{path: String, cacheKey: String}} captured
|
|
500
|
+
* @returns {Boolean}
|
|
501
|
+
*/
|
|
502
|
+
function isListRequestStale(captured) {
|
|
503
|
+
return (
|
|
504
|
+
!captured ||
|
|
505
|
+
captured.path !== selectedClass.path ||
|
|
506
|
+
captured.cacheKey !== listCacheKey()
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* @param {Object} response
|
|
512
|
+
* @returns {Object}
|
|
513
|
+
*/
|
|
514
|
+
function unwrapBrowseResponse(response) {
|
|
515
|
+
if (response && response.success === false) {
|
|
516
|
+
return Promise.reject(response);
|
|
517
|
+
}
|
|
518
|
+
let data = response && response.data ? response.data : response;
|
|
519
|
+
data = updatePermissions(data);
|
|
520
|
+
if (data.children && data.children.length > 0) {
|
|
521
|
+
data.children = data.children.map(function (child) {
|
|
522
|
+
return updatePermissions(child);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (data.items && data.items.length > 0) {
|
|
526
|
+
data.items = data.items.map(function (item) {
|
|
527
|
+
return updatePermissions(item);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return data;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* @param {String} path
|
|
535
|
+
* @param {Object} extraData
|
|
536
|
+
* @returns {Promise}
|
|
537
|
+
*/
|
|
538
|
+
function browseRequest(path, extraData) {
|
|
539
|
+
const parameters = {};
|
|
540
|
+
parameters[options.pathParam || 'path'] = path;
|
|
541
|
+
return Promise.resolve(
|
|
542
|
+
$.ajax({
|
|
543
|
+
url: options.browseUrl,
|
|
544
|
+
method: 'GET',
|
|
545
|
+
dataType: 'json',
|
|
546
|
+
timeout: ajaxTimeoutMs,
|
|
547
|
+
data: _.merge(parameters, options.params, extraData)
|
|
548
|
+
})
|
|
549
|
+
).then(unwrapBrowseResponse);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* @param {Object} tree
|
|
554
|
+
* @param {String} path
|
|
555
|
+
* @param {Object} data
|
|
556
|
+
*/
|
|
557
|
+
function applyTreePayload(tree, path, data) {
|
|
558
|
+
const folderData = Object.assign({}, data, { children: folderChildrenOnly(data) });
|
|
559
|
+
if (!tree.path) {
|
|
560
|
+
_.merge(tree, folderData);
|
|
561
|
+
} else if (folderData.children) {
|
|
562
|
+
setToPath(tree, path, folderData);
|
|
563
|
+
if (!_.find(folderData.children, 'path')) {
|
|
564
|
+
markFolderEmptyAtPath(tree, path);
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
setToPath(tree, path, folderData);
|
|
568
|
+
markFolderEmptyAtPath(tree, path);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* @param {Object} tree
|
|
574
|
+
* @param {String} path
|
|
575
|
+
* @param {Object} listData
|
|
576
|
+
* @param {String} cacheKey
|
|
577
|
+
*/
|
|
578
|
+
function applyListPayload(tree, path, listData, cacheKey) {
|
|
579
|
+
const node = getByExactPath(tree, path) || getByPath(tree, path);
|
|
580
|
+
if (!node || !listData) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
node.listItems = listData.items || [];
|
|
584
|
+
node.total = listData.total;
|
|
585
|
+
node.childrenLimit = listData.pageSize || listData.childrenLimit || selectedClass.childrenLimit;
|
|
586
|
+
node.listPage = listData.page;
|
|
587
|
+
node.totalIsApproximate = listData.totalIsApproximate;
|
|
588
|
+
node.listCacheKey = cacheKey;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Ensure upload responses appear in the table before Elasticsearch catches up.
|
|
593
|
+
* @param {String} path
|
|
594
|
+
* @param {Array<Object>} uploads
|
|
595
|
+
*/
|
|
596
|
+
function mergePendingUploads(path, uploads) {
|
|
597
|
+
if (!uploads || !uploads.length) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const node = getByExactPath(fileTree, path) || getByPath(fileTree, path);
|
|
601
|
+
if (!node) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (!Array.isArray(node.listItems)) {
|
|
605
|
+
node.listItems = [];
|
|
606
|
+
}
|
|
607
|
+
let added = 0;
|
|
608
|
+
uploads.forEach(function (uploaded) {
|
|
609
|
+
if (!uploaded || uploaded.error) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const normalized = updatePermissions(uploaded);
|
|
613
|
+
if (!normalized.uri && !normalized.name) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const exists = _.some(node.listItems, function (item) {
|
|
617
|
+
return (
|
|
618
|
+
(normalized.uri && item.uri === normalized.uri) ||
|
|
619
|
+
(normalized.name && item.name === normalized.name)
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
if (exists) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
node.listItems.push(normalized);
|
|
626
|
+
added++;
|
|
627
|
+
});
|
|
628
|
+
if (!added) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (splitBrowse) {
|
|
632
|
+
node.listItems = sortAssetItems(node.listItems, sort);
|
|
633
|
+
}
|
|
634
|
+
const total = Number(node.total);
|
|
635
|
+
if (Number.isFinite(total) && total >= 0) {
|
|
636
|
+
node.total = total + added;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
173
640
|
/**
|
|
174
641
|
* Get the content of a folder, either in the model or load it
|
|
175
642
|
* @param {Object} tree - the tree model
|
|
176
643
|
* @param {String} path - the folder path (relative to the root)
|
|
177
644
|
* @param {Function} cb - called back with the content in 1st parameter
|
|
178
645
|
*/
|
|
646
|
+
function markFolderEmptyAtPath(tree, path) {
|
|
647
|
+
const node = getByExactPath(tree, path);
|
|
648
|
+
if (node) {
|
|
649
|
+
node.empty = true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
179
653
|
function getFolderContent(tree, path, cb) {
|
|
654
|
+
if (splitBrowse) {
|
|
655
|
+
const existing = getByExactPath(tree, path) || getByPath(tree, path);
|
|
656
|
+
const needTree = !existing || (!existing.children && !existing.empty);
|
|
657
|
+
const cacheKey = listCacheKey();
|
|
658
|
+
const needList = !existing || existing.listCacheKey !== cacheKey;
|
|
659
|
+
|
|
660
|
+
const treePromise = needTree
|
|
661
|
+
? (setTreeLoading(true),
|
|
662
|
+
browseRequest(path, {
|
|
663
|
+
part: 'tree',
|
|
664
|
+
depth: 1,
|
|
665
|
+
sortBy: sort.field,
|
|
666
|
+
sortDir: sort.direction
|
|
667
|
+
}).then(
|
|
668
|
+
function (data) {
|
|
669
|
+
setTreeLoading(false);
|
|
670
|
+
return data;
|
|
671
|
+
},
|
|
672
|
+
function (error) {
|
|
673
|
+
setTreeLoading(false);
|
|
674
|
+
return Promise.reject(error);
|
|
675
|
+
}
|
|
676
|
+
))
|
|
677
|
+
: Promise.resolve(null);
|
|
678
|
+
|
|
679
|
+
const listPromise = needList
|
|
680
|
+
? browseRequest(path, {
|
|
681
|
+
part: 'list',
|
|
682
|
+
page: selectedClass.page,
|
|
683
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
684
|
+
sortBy: sort.field,
|
|
685
|
+
sortDir: sort.direction
|
|
686
|
+
})
|
|
687
|
+
: Promise.resolve(null);
|
|
688
|
+
|
|
689
|
+
Promise.all([treePromise, listPromise])
|
|
690
|
+
.then(function ([treeData, listData]) {
|
|
691
|
+
if (treeData) {
|
|
692
|
+
applyTreePayload(tree, path, treeData);
|
|
693
|
+
}
|
|
694
|
+
if (listData) {
|
|
695
|
+
applyListPayload(tree, path, listData, cacheKey);
|
|
696
|
+
}
|
|
697
|
+
cb(getByExactPath(tree, path) || getByPath(tree, path));
|
|
698
|
+
})
|
|
699
|
+
.catch(function () {
|
|
700
|
+
cb(null);
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
180
705
|
let content = getByPath(tree, path);
|
|
181
706
|
if (!content || (!content.children && !content.empty)) {
|
|
182
707
|
loadContent(path).then(function (data) {
|
|
183
708
|
if (!tree.path) {
|
|
184
709
|
tree = _.merge(tree, data);
|
|
185
710
|
} else if (data.children) {
|
|
711
|
+
setToPath(tree, path, data);
|
|
186
712
|
if (!_.find(data.children, 'path')) {
|
|
187
713
|
// no subfolders inside folder
|
|
188
|
-
tree
|
|
714
|
+
markFolderEmptyAtPath(tree, path);
|
|
189
715
|
}
|
|
190
|
-
setToPath(tree, path, data);
|
|
191
716
|
} else {
|
|
192
|
-
tree
|
|
717
|
+
setToPath(tree, path, data);
|
|
718
|
+
markFolderEmptyAtPath(tree, path);
|
|
193
719
|
}
|
|
194
720
|
cb(data);
|
|
721
|
+
}).catch(function () {
|
|
722
|
+
cb(null);
|
|
195
723
|
});
|
|
196
724
|
} else if (content.children) {
|
|
197
725
|
const files = _.filter(content.children, function (item) {
|
|
198
726
|
return !!item.uri;
|
|
199
727
|
});
|
|
200
|
-
//
|
|
201
|
-
|
|
728
|
+
// Use folder total (not selectedClass): openInitialPath runs before selectFolder.
|
|
729
|
+
const expectedTotal = Number(content.total);
|
|
730
|
+
const pageSize = Number(content.childrenLimit) || selectedClass.childrenLimit || 11;
|
|
731
|
+
const page = selectedClass.page || 1;
|
|
732
|
+
// Missing/NaN total → unknown size; refetch so invalidated cache is not treated as complete.
|
|
733
|
+
if (
|
|
734
|
+
!Number.isFinite(expectedTotal) ||
|
|
735
|
+
(files.length < expectedTotal && files.length < page * pageSize)
|
|
736
|
+
) {
|
|
202
737
|
loadContent(path).then(function (data) {
|
|
203
738
|
const loadedFiles = _.filter(data.children, function (item) {
|
|
204
739
|
return !!item.uri;
|
|
205
740
|
});
|
|
741
|
+
const node = getByExactPath(tree, path);
|
|
742
|
+
if (node && !Number.isFinite(expectedTotal)) {
|
|
743
|
+
node.children = _.filter(node.children, function (item) {
|
|
744
|
+
return !item.uri;
|
|
745
|
+
});
|
|
746
|
+
}
|
|
206
747
|
setToPath(tree, path, {
|
|
207
748
|
children: loadedFiles,
|
|
208
749
|
total: data.total,
|
|
209
750
|
childrenLimit: data.childrenLimit
|
|
210
751
|
});
|
|
211
|
-
content = getByPath(tree, path);
|
|
752
|
+
content = getByExactPath(tree, path) || getByPath(tree, path);
|
|
753
|
+
cb(content);
|
|
754
|
+
}).catch(function () {
|
|
212
755
|
cb(content);
|
|
213
756
|
});
|
|
214
757
|
} else {
|
|
@@ -257,6 +800,29 @@ export default function (options) {
|
|
|
257
800
|
return match;
|
|
258
801
|
}
|
|
259
802
|
|
|
803
|
+
/**
|
|
804
|
+
* Get a subtree by exact path equality.
|
|
805
|
+
* @param {Object} tree
|
|
806
|
+
* @param {String} path
|
|
807
|
+
* @returns {Object|undefined}
|
|
808
|
+
*/
|
|
809
|
+
function getByExactPath(tree, path) {
|
|
810
|
+
let match;
|
|
811
|
+
if (tree) {
|
|
812
|
+
if (tree.path === path) {
|
|
813
|
+
match = tree;
|
|
814
|
+
} else if (tree.children) {
|
|
815
|
+
_.forEach(tree.children, function (child) {
|
|
816
|
+
match = getByExactPath(child, path);
|
|
817
|
+
if (match) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return match;
|
|
824
|
+
}
|
|
825
|
+
|
|
260
826
|
/**
|
|
261
827
|
* Merge data into at into the subtree
|
|
262
828
|
* @param {Object} tree - the tree model
|
|
@@ -300,8 +866,11 @@ export default function (options) {
|
|
|
300
866
|
return child.path === path || (child.name && tree.path + child.name === path) || child.uri === path;
|
|
301
867
|
});
|
|
302
868
|
done = removed.length > 0;
|
|
303
|
-
|
|
304
|
-
|
|
869
|
+
if (done) {
|
|
870
|
+
if (Number.isFinite(Number(tree.total))) {
|
|
871
|
+
tree.total = Math.max(0, Number(tree.total) - removed.length);
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
305
874
|
_.forEach(tree.children, function (child) {
|
|
306
875
|
done = removeFromPath(child, path);
|
|
307
876
|
if (done) {
|
|
@@ -313,31 +882,141 @@ export default function (options) {
|
|
|
313
882
|
return done;
|
|
314
883
|
}
|
|
315
884
|
|
|
885
|
+
/**
|
|
886
|
+
* Drop cached file rows for a folder so the next load hits the service
|
|
887
|
+
* with the current sort. Nested folder nodes are kept.
|
|
888
|
+
* @param {String} path
|
|
889
|
+
*/
|
|
890
|
+
function invalidateFolderFiles(path) {
|
|
891
|
+
const content = getByExactPath(fileTree, path);
|
|
892
|
+
if (!content) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (splitBrowse) {
|
|
896
|
+
delete content.listItems;
|
|
897
|
+
delete content.listCacheKey;
|
|
898
|
+
delete content.listPage;
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (Array.isArray(content.children)) {
|
|
902
|
+
content.children = content.children.filter(function (child) {
|
|
903
|
+
return child.path && !child.uri;
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Replace a folder node with a freshly loaded payload (files + folders).
|
|
910
|
+
* @param {String} path
|
|
911
|
+
* @param {Object} data
|
|
912
|
+
*/
|
|
913
|
+
function replaceFolderContent(path, data, cacheKey) {
|
|
914
|
+
const content = getByExactPath(fileTree, path);
|
|
915
|
+
if (!content || !data) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (splitBrowse && Object.prototype.hasOwnProperty.call(data, 'items')) {
|
|
919
|
+
applyListPayload(fileTree, path, data, cacheKey || listCacheKey());
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (Object.prototype.hasOwnProperty.call(data, 'children')) {
|
|
923
|
+
content.children = data.children;
|
|
924
|
+
} else {
|
|
925
|
+
content.children = [];
|
|
926
|
+
content.empty = true;
|
|
927
|
+
content.total = 0;
|
|
928
|
+
}
|
|
929
|
+
if (Object.prototype.hasOwnProperty.call(data, 'total')) {
|
|
930
|
+
content.total = data.total;
|
|
931
|
+
}
|
|
932
|
+
if (Object.prototype.hasOwnProperty.call(data, 'childrenLimit')) {
|
|
933
|
+
content.childrenLimit = data.childrenLimit;
|
|
934
|
+
}
|
|
935
|
+
if (data.label) {
|
|
936
|
+
content.label = data.label;
|
|
937
|
+
}
|
|
938
|
+
if (data.path) {
|
|
939
|
+
content.path = data.path;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
316
943
|
/**
|
|
317
944
|
* Get the content of a folder
|
|
318
945
|
* @param {String} path - the folder path
|
|
319
|
-
* @returns {
|
|
946
|
+
* @returns {Promise} resolves with folder content
|
|
320
947
|
*/
|
|
321
948
|
function loadContent(path) {
|
|
322
949
|
const parameters = {};
|
|
323
|
-
parameters[options.pathParam] = path;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
950
|
+
parameters[options.pathParam || 'path'] = path;
|
|
951
|
+
setTreeLoading(true);
|
|
952
|
+
return Promise.resolve(
|
|
953
|
+
$.ajax({
|
|
954
|
+
url: options.browseUrl,
|
|
955
|
+
method: 'GET',
|
|
956
|
+
dataType: 'json',
|
|
957
|
+
timeout: ajaxTimeoutMs,
|
|
958
|
+
data: _.merge(parameters, options.params, {
|
|
959
|
+
// depth=2 so each rendered child folder includes its own dir children,
|
|
960
|
+
// allowing leaf folders to hide the expand chevron without an extra click.
|
|
961
|
+
depth: 2,
|
|
962
|
+
childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
963
|
+
sortBy: sort.field,
|
|
964
|
+
sortDir: sort.direction
|
|
965
|
+
})
|
|
966
|
+
})
|
|
967
|
+
)
|
|
968
|
+
.then(function (response) {
|
|
969
|
+
if (response && response.success === false) {
|
|
970
|
+
return Promise.reject(response);
|
|
338
971
|
}
|
|
339
|
-
|
|
340
|
-
|
|
972
|
+
let data = response && response.data ? response.data : response;
|
|
973
|
+
data = updatePermissions(data);
|
|
974
|
+
if (data.children && data.children.length > 0) {
|
|
975
|
+
data.children.map(responseChildren => updatePermissions(responseChildren));
|
|
976
|
+
}
|
|
977
|
+
return data;
|
|
978
|
+
})
|
|
979
|
+
.then(
|
|
980
|
+
function (data) {
|
|
981
|
+
setTreeLoading(false);
|
|
982
|
+
return data;
|
|
983
|
+
},
|
|
984
|
+
function (error) {
|
|
985
|
+
setTreeLoading(false);
|
|
986
|
+
return Promise.reject(error);
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Whether a folder node has nested directory children.
|
|
993
|
+
* @param {Object} node
|
|
994
|
+
* @returns {boolean|null} true/false when known; null when children were not loaded yet
|
|
995
|
+
*/
|
|
996
|
+
function hasNestedFolderChildren(node) {
|
|
997
|
+
if (!node || node.empty === true) {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
if (!Array.isArray(node.children)) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
return Boolean(_.find(node.children, 'path'));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Show or hide the expand chevron on a folder anchor (keep spacer width).
|
|
1008
|
+
* @param {jQuery} $anchor
|
|
1009
|
+
* @param {boolean} hasNested
|
|
1010
|
+
*/
|
|
1011
|
+
function setFolderToggleState($anchor, hasNested) {
|
|
1012
|
+
const $toggle = $anchor.children('.tree-toggle');
|
|
1013
|
+
if (hasNested) {
|
|
1014
|
+
$toggle.addClass('icon-right').removeClass('is-leaf');
|
|
1015
|
+
$anchor.removeClass('is-leaf');
|
|
1016
|
+
} else {
|
|
1017
|
+
$toggle.removeClass('icon-right').addClass('is-leaf');
|
|
1018
|
+
$anchor.removeClass('opened').addClass('is-leaf');
|
|
1019
|
+
}
|
|
341
1020
|
}
|
|
342
1021
|
|
|
343
1022
|
/**
|
|
@@ -351,7 +1030,10 @@ export default function (options) {
|
|
|
351
1030
|
if (typeof data.relPath === 'undefined') {
|
|
352
1031
|
data.relPath = data.path;
|
|
353
1032
|
}
|
|
1033
|
+
// Unknown nested state (lazy depth) keeps the chevron until the folder is opened.
|
|
1034
|
+
data.showToggle = hasNestedFolderChildren(data) !== false;
|
|
354
1035
|
$parent.append(folderTpl(data));
|
|
1036
|
+
return;
|
|
355
1037
|
}
|
|
356
1038
|
if (data && data.children && _.isArray(data.children) && !data.empty) {
|
|
357
1039
|
_.forEach(data.children, function (child) {
|
|
@@ -376,7 +1058,7 @@ export default function (options) {
|
|
|
376
1058
|
childrenLimit:
|
|
377
1059
|
Number.isFinite(normalizedChildrenLimit) && normalizedChildrenLimit > 0
|
|
378
1060
|
? normalizedChildrenLimit
|
|
379
|
-
: selectedClass.childrenLimit ||
|
|
1061
|
+
: selectedClass.childrenLimit || 11,
|
|
380
1062
|
page: 1
|
|
381
1063
|
};
|
|
382
1064
|
}
|
|
@@ -384,7 +1066,11 @@ export default function (options) {
|
|
|
384
1066
|
* Render pagination
|
|
385
1067
|
*/
|
|
386
1068
|
function renderPagination() {
|
|
387
|
-
|
|
1069
|
+
if (searchMode) {
|
|
1070
|
+
// Drop browse pagination controls/handlers when search owns the table.
|
|
1071
|
+
$paginationContainer.empty();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
388
1074
|
const total = Number(selectedClass.total);
|
|
389
1075
|
const childrenLimit = Number(selectedClass.childrenLimit);
|
|
390
1076
|
|
|
@@ -413,19 +1099,113 @@ export default function (options) {
|
|
|
413
1099
|
.render($paginationContainer);
|
|
414
1100
|
}
|
|
415
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Re-fetch the current folder with the active sort and publish files.
|
|
1104
|
+
*/
|
|
1105
|
+
function reloadSortedFolder(pendingUploads) {
|
|
1106
|
+
const listRequest = captureListRequestContext();
|
|
1107
|
+
const path = listRequest.path;
|
|
1108
|
+
if (!searchMode && isActiveBrowser()) {
|
|
1109
|
+
setFilesLoading(true);
|
|
1110
|
+
}
|
|
1111
|
+
const loadPromise = splitBrowse
|
|
1112
|
+
? browseRequest(path, {
|
|
1113
|
+
part: 'list',
|
|
1114
|
+
page: selectedClass.page,
|
|
1115
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
1116
|
+
sortBy: sort.field,
|
|
1117
|
+
sortDir: sort.direction
|
|
1118
|
+
})
|
|
1119
|
+
: loadContent(path);
|
|
1120
|
+
|
|
1121
|
+
loadPromise
|
|
1122
|
+
.then(function (data) {
|
|
1123
|
+
if (!data) {
|
|
1124
|
+
setFilesLoading(false);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (isListRequestStale(listRequest)) {
|
|
1128
|
+
setFilesLoading(false);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
replaceFolderContent(path, data, listRequest.cacheKey);
|
|
1132
|
+
mergePendingUploads(path, pendingUploads);
|
|
1133
|
+
const content = getByExactPath(fileTree, path) || data;
|
|
1134
|
+
selectFolder(content, content.path || path);
|
|
1135
|
+
})
|
|
1136
|
+
.catch(function (error) {
|
|
1137
|
+
LOGGER.error(error);
|
|
1138
|
+
setFilesLoading(false);
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
|
|
416
1142
|
/**
|
|
417
1143
|
* Load page
|
|
418
1144
|
*/
|
|
419
1145
|
function loadPage() {
|
|
420
|
-
const
|
|
1146
|
+
const listRequest = captureListRequestContext();
|
|
1147
|
+
|
|
1148
|
+
if (!searchMode && isActiveBrowser()) {
|
|
1149
|
+
setFilesLoading(true);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (splitBrowse) {
|
|
1153
|
+
const path = listRequest.path;
|
|
1154
|
+
browseRequest(path, {
|
|
1155
|
+
part: 'list',
|
|
1156
|
+
page: selectedClass.page,
|
|
1157
|
+
pageSize: selectedClass.childrenLimit || 11,
|
|
1158
|
+
sortBy: sort.field,
|
|
1159
|
+
sortDir: sort.direction
|
|
1160
|
+
})
|
|
1161
|
+
.then(function (data) {
|
|
1162
|
+
if (isListRequestStale(listRequest)) {
|
|
1163
|
+
setFilesLoading(false);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
applyListPayload(fileTree, path, data, listRequest.cacheKey);
|
|
1167
|
+
const content = getByExactPath(fileTree, path);
|
|
1168
|
+
if (content) {
|
|
1169
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
1170
|
+
content.label,
|
|
1171
|
+
getFilesForDisplay(content),
|
|
1172
|
+
content.path,
|
|
1173
|
+
content
|
|
1174
|
+
]);
|
|
1175
|
+
renderPagination();
|
|
1176
|
+
}
|
|
1177
|
+
setFilesLoading(false);
|
|
1178
|
+
})
|
|
1179
|
+
.catch(function (error) {
|
|
1180
|
+
LOGGER.error(error);
|
|
1181
|
+
setFilesLoading(false);
|
|
1182
|
+
});
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const subTree = getByPath(fileTree, selectedClass.path) || fileTree;
|
|
421
1187
|
|
|
422
1188
|
//get the folder content
|
|
423
1189
|
getFolderContent(subTree, selectedClass.path, function (content) {
|
|
1190
|
+
if (isListRequestStale(listRequest)) {
|
|
1191
|
+
if (!searchMode) {
|
|
1192
|
+
setFilesLoading(false);
|
|
1193
|
+
}
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
424
1196
|
indexTree(fileTree);
|
|
425
1197
|
|
|
426
1198
|
if (content) {
|
|
427
1199
|
//internal event to set the file-selector content
|
|
428
|
-
$container.trigger(`folderselect.${
|
|
1200
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
1201
|
+
content.label,
|
|
1202
|
+
getFilesForDisplay(content),
|
|
1203
|
+
content.path,
|
|
1204
|
+
content
|
|
1205
|
+
]);
|
|
1206
|
+
}
|
|
1207
|
+
if (!searchMode) {
|
|
1208
|
+
setFilesLoading(false);
|
|
429
1209
|
}
|
|
430
1210
|
});
|
|
431
1211
|
}
|