@oat-sa/tao-core-ui 3.22.0 → 3.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/figure/FigureStateActive.js +64 -32
- package/dist/image/ImgStateActive/initHelper.js +64 -32
- package/dist/image/ImgStateActive.js +64 -32
- package/dist/mediaplayer/players/html5.js +44 -18
- package/dist/mediaplayer/utils/crossOrigin.js +64 -0
- package/dist/mediaplayer.js +7 -1
- package/dist/previewer.js +1 -1
- package/dist/resourcemgr/assetSearch.js +793 -0
- package/dist/resourcemgr/assetSearchContract.js +477 -0
- package/dist/resourcemgr/css/resourcemgr.css +1137 -97
- package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
- package/dist/resourcemgr/fileBrowser.js +456 -79
- package/dist/resourcemgr/filePreview.js +40 -4
- package/dist/resourcemgr/fileSelector.js +362 -92
- package/dist/resourcemgr.js +309 -57
- package/dist/searchModal/advancedSearch.js +233 -31
- package/package.json +15 -6
- package/src/image/ImgStateActive/initHelper.js +69 -31
- package/src/mediaplayer/players/html5.js +17 -7
- package/src/mediaplayer/readme.md +17 -0
- package/src/mediaplayer/tpl/audio.tpl +1 -1
- package/src/mediaplayer/tpl/video.tpl +1 -1
- package/src/mediaplayer/utils/crossOrigin.js +63 -0
- package/src/mediaplayer.js +10 -0
- package/src/previewer.js +1 -1
- package/src/resourcemgr/assetSearch.js +847 -0
- package/src/resourcemgr/assetSearchContract.js +489 -0
- package/src/resourcemgr/css/resourcemgr.css +1137 -97
- package/src/resourcemgr/css/resourcemgr.css.map +1 -1
- package/src/resourcemgr/fileBrowser.js +470 -91
- package/src/resourcemgr/filePreview.js +46 -4
- package/src/resourcemgr/fileSelector.js +338 -46
- package/src/resourcemgr/scss/resourcemgr.scss +1351 -88
- package/src/resourcemgr/tpl/advanced-search.tpl +10 -0
- package/src/resourcemgr/tpl/fileSelect.tpl +25 -28
- package/src/resourcemgr/tpl/folder.tpl +4 -1
- package/src/resourcemgr/tpl/layout.tpl +113 -54
- package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
- package/src/resourcemgr.js +289 -47
- package/src/searchModal/advancedSearch.js +242 -31
|
@@ -11,27 +11,36 @@
|
|
|
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 = 10000;
|
|
27
30
|
|
|
28
31
|
export default function (options) {
|
|
32
|
+
if (!options.browseUrl && options.url) {
|
|
33
|
+
options.browseUrl = options.url;
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
const root = options.root || 'local';
|
|
30
37
|
const rootPath = options.path || '/';
|
|
38
|
+
const initialPath = options.initialPath || rootPath;
|
|
31
39
|
const $container = options.$target;
|
|
32
40
|
const $fileBrowser = $('.file-browser .file-browser-wrapper', $container);
|
|
33
41
|
const $divContainer = $(`.${root}`, $fileBrowser);
|
|
34
42
|
const $folderContainer = $('.folders', $divContainer);
|
|
43
|
+
const $paginationContainer = $('.pagination-bottom', $container);
|
|
35
44
|
const fileTree = {};
|
|
36
45
|
// for pagination
|
|
37
46
|
let selectedClass = {
|
|
@@ -40,32 +49,92 @@ export default function (options) {
|
|
|
40
49
|
total: 0,
|
|
41
50
|
page: 1
|
|
42
51
|
};
|
|
52
|
+
let searchMode = false;
|
|
53
|
+
let sort = Object.assign({}, DEFAULT_SORT);
|
|
54
|
+
const ajaxTimeoutMs = Number.isFinite(Number(options.ajaxTimeoutMs)) && Number(options.ajaxTimeoutMs) > 0
|
|
55
|
+
? Number(options.ajaxTimeoutMs)
|
|
56
|
+
: DEFAULT_AJAX_TIMEOUT_MS;
|
|
57
|
+
|
|
58
|
+
$container.on(`searchmode.${NS}`, function (e, enabled) {
|
|
59
|
+
searchMode = !!enabled;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
$container.on(`sortchange.${NS}`, function (e, nextSort) {
|
|
63
|
+
sort = Object.assign({}, DEFAULT_SORT, nextSort || {});
|
|
64
|
+
selectedClass.page = 1;
|
|
65
|
+
invalidateFolderFiles(selectedClass.path);
|
|
66
|
+
if (searchMode || !isActiveBrowser()) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
reloadSortedFolder();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
$container.on(`searchclear.${NS}`, function (e, path) {
|
|
73
|
+
const targetPath = path || selectedClass.path;
|
|
74
|
+
selectedClass.page = 1;
|
|
75
|
+
invalidateFolderFiles(targetPath);
|
|
76
|
+
if (!isActiveBrowser()) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath) || fileTree;
|
|
80
|
+
getFolderContent(subTree, targetPath, function (content) {
|
|
81
|
+
if (content) {
|
|
82
|
+
selectFolder(content, targetPath);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Reopen with resolved parent (AC3 edit/change): leave search, open folder again.
|
|
88
|
+
$container.on(`applycontext.${NS}`, function (e, ctx) {
|
|
89
|
+
const path = (ctx && ctx.path) || rootPath;
|
|
90
|
+
const pathIsTaomedia = path && String(path).indexOf('taomedia://') === 0;
|
|
91
|
+
if (pathIsTaomedia && root === 'local') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!pathIsTaomedia && root !== 'local') {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
$container.data('activeFileBrowserRoot', root);
|
|
98
|
+
if (searchMode) {
|
|
99
|
+
$container.trigger(`requestexitsearch.${NS}`);
|
|
100
|
+
}
|
|
101
|
+
selectedClass.page = 1;
|
|
102
|
+
openInitialPath(path);
|
|
103
|
+
});
|
|
43
104
|
|
|
44
105
|
//load the content of the ROOT
|
|
45
106
|
getFolderContent(fileTree, rootPath, function (content) {
|
|
107
|
+
if (!content) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
46
110
|
indexTree(content);
|
|
47
111
|
|
|
48
112
|
//create the tree node for the ROOT folder by default once the initial content loaded
|
|
49
|
-
$folderContainer.append(
|
|
113
|
+
$folderContainer.append(
|
|
114
|
+
rootFolderTpl(
|
|
115
|
+
Object.assign({}, content, {
|
|
116
|
+
showToggle: hasNestedFolderChildren(content) !== false
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
);
|
|
50
120
|
|
|
51
121
|
const $rootNode = $('.root-folder', $folderContainer);
|
|
52
122
|
//create an inner list and append found elements
|
|
53
123
|
const $innerList = $('.root ul', $folderContainer);
|
|
54
|
-
if (content
|
|
124
|
+
if (hasNestedFolderChildren(content)) {
|
|
55
125
|
$rootNode.addClass('opened');
|
|
126
|
+
} else if (hasNestedFolderChildren(content) === false) {
|
|
127
|
+
setFolderToggleState($rootNode, false);
|
|
56
128
|
}
|
|
57
129
|
updateFolders(content, $innerList);
|
|
58
130
|
|
|
59
|
-
if (content.permissions.read && !options.hasAlreadySelected) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
content.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
content
|
|
67
|
-
]);
|
|
68
|
-
renderPagination();
|
|
131
|
+
if (content.permissions && content.permissions.read && !options.hasAlreadySelected) {
|
|
132
|
+
if (initialPath && initialPath !== rootPath) {
|
|
133
|
+
openInitialPath(initialPath);
|
|
134
|
+
} else {
|
|
135
|
+
selectFolder(content, content.path);
|
|
136
|
+
syncTreeActiveFolder(content.path || rootPath);
|
|
137
|
+
}
|
|
69
138
|
|
|
70
139
|
if (root !== 'local') {
|
|
71
140
|
options.hasAlreadySelected = true;
|
|
@@ -77,9 +146,8 @@ export default function (options) {
|
|
|
77
146
|
$divContainer.off('click', '.folders a').on('click', '.folders a', function (e) {
|
|
78
147
|
e.preventDefault();
|
|
79
148
|
const $selected = $(this);
|
|
80
|
-
const $folders = $('.folders li', $fileBrowser);
|
|
81
149
|
const fullPath = $selected.data('path');
|
|
82
|
-
const subTree =
|
|
150
|
+
const subTree = getByExactPath(fileTree, fullPath);
|
|
83
151
|
|
|
84
152
|
//get the folder content
|
|
85
153
|
getFolderContent(subTree, fullPath, function (content) {
|
|
@@ -88,10 +156,12 @@ export default function (options) {
|
|
|
88
156
|
if (content) {
|
|
89
157
|
//either create the inner list of the content is new or just show it
|
|
90
158
|
let $innerList = $selected.siblings('ul');
|
|
91
|
-
|
|
159
|
+
const nested = hasNestedFolderChildren(content);
|
|
160
|
+
if (!$innerList.length && nested) {
|
|
92
161
|
$innerList = $('<ul></ul>').insertAfter($selected);
|
|
93
162
|
updateFolders(content, $innerList);
|
|
94
163
|
$selected.addClass('opened');
|
|
164
|
+
setFolderToggleState($selected, true);
|
|
95
165
|
} else if ($innerList.length) {
|
|
96
166
|
if ($innerList.css('display') === 'none') {
|
|
97
167
|
$innerList.show();
|
|
@@ -100,68 +170,215 @@ export default function (options) {
|
|
|
100
170
|
$innerList.hide();
|
|
101
171
|
$selected.removeClass('opened');
|
|
102
172
|
}
|
|
173
|
+
} else if (nested === false) {
|
|
174
|
+
// Leaf folder: keep alignment spacer, hide expand chevron.
|
|
175
|
+
setFolderToggleState($selected, false);
|
|
103
176
|
}
|
|
104
177
|
|
|
105
|
-
|
|
106
|
-
$folders.removeClass('active');
|
|
107
|
-
$selected.parent('li').addClass('active');
|
|
178
|
+
syncTreeActiveFolder(fullPath, $selected);
|
|
108
179
|
|
|
109
180
|
//internal event to set the file-selector content
|
|
110
|
-
|
|
111
|
-
$container.trigger(`folderselect.${ns}`, [
|
|
112
|
-
content.label,
|
|
113
|
-
getPage(content.children),
|
|
114
|
-
content.path,
|
|
115
|
-
content
|
|
116
|
-
]);
|
|
117
|
-
renderPagination();
|
|
181
|
+
selectFolder(content, fullPath);
|
|
118
182
|
}
|
|
119
183
|
});
|
|
120
184
|
});
|
|
121
185
|
|
|
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
|
-
});
|
|
186
|
+
$container.on(`filenew.${NS}`, function (e, file, path) {
|
|
187
|
+
const targetPath = path || selectedClass.path;
|
|
188
|
+
if (!targetPath || searchMode) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
134
191
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
$container.trigger(`folderselect.${ns}`, [subTree.label, getPage(subTree.children), path, subTree]);
|
|
145
|
-
renderPagination();
|
|
146
|
-
}
|
|
192
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
193
|
+
if (activeRoot && activeRoot !== root) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath);
|
|
198
|
+
if (subTree && root === 'local' && file && file.name && _.find(subTree.children, { name: file.name })) {
|
|
199
|
+
return;
|
|
147
200
|
}
|
|
201
|
+
|
|
202
|
+
if (targetPath !== selectedClass.path) {
|
|
203
|
+
selectedClass.path = targetPath;
|
|
204
|
+
selectedClass.page = 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
invalidateFolderFiles(targetPath);
|
|
208
|
+
reloadSortedFolder();
|
|
148
209
|
});
|
|
149
210
|
|
|
150
|
-
$container.on(`filedelete.${
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
211
|
+
$container.on(`filedelete.${NS}`, function (e, path) {
|
|
212
|
+
if (searchMode) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const activeRoot = $container.data('activeFileBrowserRoot');
|
|
217
|
+
if (activeRoot && activeRoot !== root) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (path) {
|
|
222
|
+
removeFromPath(fileTree, path);
|
|
154
223
|
}
|
|
224
|
+
|
|
225
|
+
invalidateFolderFiles(selectedClass.path);
|
|
226
|
+
reloadSortedFolder();
|
|
155
227
|
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Open and select an initial folder path after the root tree is available.
|
|
231
|
+
* @param {String} path
|
|
232
|
+
*/
|
|
233
|
+
function findFolderLink(path) {
|
|
234
|
+
return $folderContainer.find('a').filter(function () {
|
|
235
|
+
return $(this).data('path') === path;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Render directory children under an expanded folder anchor (replace / reopen).
|
|
241
|
+
* @param {String} parentPath
|
|
242
|
+
* @param {Object} parentContent
|
|
243
|
+
*/
|
|
244
|
+
function mountFolderBranch(parentPath, parentContent) {
|
|
245
|
+
const $parentLink = findFolderLink(parentPath);
|
|
246
|
+
if (!$parentLink.length || !parentContent) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
let $innerList = $parentLink.siblings('ul');
|
|
250
|
+
if (!$innerList.length) {
|
|
251
|
+
$innerList = $('<ul></ul>').insertAfter($parentLink);
|
|
252
|
+
}
|
|
253
|
+
updateFolders(parentContent, $innerList);
|
|
254
|
+
$parentLink.addClass('opened');
|
|
255
|
+
if ($innerList.css('display') === 'none') {
|
|
256
|
+
$innerList.show();
|
|
257
|
+
}
|
|
258
|
+
setFolderToggleState($parentLink, hasNestedFolderChildren(parentContent) !== false);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function markFolderActive(path) {
|
|
262
|
+
const $targetLink = findFolderLink(path);
|
|
263
|
+
if ($targetLink.length) {
|
|
264
|
+
$targetLink.parents('li').each(function () {
|
|
265
|
+
const $li = $(this);
|
|
266
|
+
const $anchor = $li.children('a');
|
|
267
|
+
const $list = $li.children('ul');
|
|
268
|
+
$anchor.addClass('opened');
|
|
269
|
+
if ($list.length) {
|
|
270
|
+
$list.show();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
syncTreeActiveFolder(path, $targetLink);
|
|
274
|
+
} else if (path === rootPath) {
|
|
275
|
+
syncTreeActiveFolder(rootPath);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function openInitialPath(path) {
|
|
280
|
+
getFolderContent(fileTree, path, function (content) {
|
|
281
|
+
indexTree(fileTree);
|
|
282
|
+
if (!content) {
|
|
283
|
+
const rootContent = getByPath(fileTree, rootPath) || fileTree;
|
|
284
|
+
const fallbackPath = rootContent.path || rootPath;
|
|
285
|
+
selectFolder(rootContent, fallbackPath);
|
|
286
|
+
syncTreeActiveFolder(fallbackPath);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function finishOpen() {
|
|
291
|
+
markFolderActive(path);
|
|
292
|
+
selectFolder(content, path);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (findFolderLink(path).length) {
|
|
296
|
+
finishOpen();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const parentFolderPath = content.parentFolderPath;
|
|
301
|
+
if (parentFolderPath && parentFolderPath !== path) {
|
|
302
|
+
getFolderContent(fileTree, parentFolderPath, function (parentContent) {
|
|
303
|
+
mountFolderBranch(parentFolderPath, parentContent);
|
|
304
|
+
finishOpen();
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
finishOpen();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Mark the folder row that matches path as active (open folder icon for leaves).
|
|
315
|
+
* @param {String} path
|
|
316
|
+
* @param {jQuery} [$link]
|
|
317
|
+
*/
|
|
318
|
+
function syncTreeActiveFolder(path, $link) {
|
|
319
|
+
$('.folders li', $fileBrowser).removeClass('active');
|
|
320
|
+
const $target =
|
|
321
|
+
$link && $link.length
|
|
322
|
+
? $link
|
|
323
|
+
: $folderContainer.find('a').filter(function () {
|
|
324
|
+
return $(this).data('path') === path;
|
|
325
|
+
});
|
|
326
|
+
if ($target.length) {
|
|
327
|
+
$target.parent('li').addClass('active');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Whether this media source currently owns the file table.
|
|
333
|
+
* @returns {Boolean}
|
|
334
|
+
*/
|
|
335
|
+
function isActiveBrowser() {
|
|
336
|
+
return $container.data('activeFileBrowserRoot') === root;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Select a folder and publish its page of files to the selector.
|
|
341
|
+
* @param {Object} content
|
|
342
|
+
* @param {String} path
|
|
343
|
+
*/
|
|
344
|
+
function selectFolder(content, path) {
|
|
345
|
+
if (!content) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
$container.data('activeFileBrowserRoot', root);
|
|
349
|
+
if (searchMode) {
|
|
350
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
351
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
352
|
+
content.label,
|
|
353
|
+
getPage(content.children || []),
|
|
354
|
+
path,
|
|
355
|
+
content
|
|
356
|
+
]);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
updateSelectedClass(path, content.total, content.childrenLimit);
|
|
360
|
+
$container.trigger(`folderpath.${NS}`, [path, content.label]);
|
|
361
|
+
$container.trigger(`folderselect.${NS}`, [
|
|
362
|
+
content.label,
|
|
363
|
+
getPage(content.children || []),
|
|
364
|
+
path,
|
|
365
|
+
content
|
|
366
|
+
]);
|
|
367
|
+
renderPagination();
|
|
368
|
+
}
|
|
369
|
+
|
|
156
370
|
/**
|
|
157
371
|
* Get files for page
|
|
158
372
|
* @param {Array} children
|
|
159
373
|
* @returns {Array} files for this page
|
|
160
374
|
*/
|
|
161
375
|
function getPage(children) {
|
|
162
|
-
const files =
|
|
163
|
-
|
|
164
|
-
|
|
376
|
+
const files = sortAssetItems(
|
|
377
|
+
_.filter(children, function (item) {
|
|
378
|
+
return !!item.uri;
|
|
379
|
+
}),
|
|
380
|
+
sort
|
|
381
|
+
);
|
|
165
382
|
if (selectedClass.childrenLimit) {
|
|
166
383
|
return files.slice(
|
|
167
384
|
(selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
@@ -176,6 +393,13 @@ export default function (options) {
|
|
|
176
393
|
* @param {String} path - the folder path (relative to the root)
|
|
177
394
|
* @param {Function} cb - called back with the content in 1st parameter
|
|
178
395
|
*/
|
|
396
|
+
function markFolderEmptyAtPath(tree, path) {
|
|
397
|
+
const node = getByExactPath(tree, path);
|
|
398
|
+
if (node) {
|
|
399
|
+
node.empty = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
179
403
|
function getFolderContent(tree, path, cb) {
|
|
180
404
|
let content = getByPath(tree, path);
|
|
181
405
|
if (!content || (!content.children && !content.empty)) {
|
|
@@ -183,32 +407,50 @@ export default function (options) {
|
|
|
183
407
|
if (!tree.path) {
|
|
184
408
|
tree = _.merge(tree, data);
|
|
185
409
|
} else if (data.children) {
|
|
410
|
+
setToPath(tree, path, data);
|
|
186
411
|
if (!_.find(data.children, 'path')) {
|
|
187
412
|
// no subfolders inside folder
|
|
188
|
-
tree
|
|
413
|
+
markFolderEmptyAtPath(tree, path);
|
|
189
414
|
}
|
|
190
|
-
setToPath(tree, path, data);
|
|
191
415
|
} else {
|
|
192
|
-
tree
|
|
416
|
+
setToPath(tree, path, data);
|
|
417
|
+
markFolderEmptyAtPath(tree, path);
|
|
193
418
|
}
|
|
194
419
|
cb(data);
|
|
420
|
+
}).catch(function () {
|
|
421
|
+
cb(null);
|
|
195
422
|
});
|
|
196
423
|
} else if (content.children) {
|
|
197
424
|
const files = _.filter(content.children, function (item) {
|
|
198
425
|
return !!item.uri;
|
|
199
426
|
});
|
|
200
|
-
//
|
|
201
|
-
|
|
427
|
+
// Use folder total (not selectedClass): openInitialPath runs before selectFolder.
|
|
428
|
+
const expectedTotal = Number(content.total);
|
|
429
|
+
const pageSize = Number(content.childrenLimit) || selectedClass.childrenLimit || 10;
|
|
430
|
+
const page = selectedClass.page || 1;
|
|
431
|
+
// Missing/NaN total → unknown size; refetch so invalidated cache is not treated as complete.
|
|
432
|
+
if (
|
|
433
|
+
!Number.isFinite(expectedTotal) ||
|
|
434
|
+
(files.length < expectedTotal && files.length < page * pageSize)
|
|
435
|
+
) {
|
|
202
436
|
loadContent(path).then(function (data) {
|
|
203
437
|
const loadedFiles = _.filter(data.children, function (item) {
|
|
204
438
|
return !!item.uri;
|
|
205
439
|
});
|
|
440
|
+
const node = getByExactPath(tree, path);
|
|
441
|
+
if (node && !Number.isFinite(expectedTotal)) {
|
|
442
|
+
node.children = _.filter(node.children, function (item) {
|
|
443
|
+
return !item.uri;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
206
446
|
setToPath(tree, path, {
|
|
207
447
|
children: loadedFiles,
|
|
208
448
|
total: data.total,
|
|
209
449
|
childrenLimit: data.childrenLimit
|
|
210
450
|
});
|
|
211
|
-
content = getByPath(tree, path);
|
|
451
|
+
content = getByExactPath(tree, path) || getByPath(tree, path);
|
|
452
|
+
cb(content);
|
|
453
|
+
}).catch(function () {
|
|
212
454
|
cb(content);
|
|
213
455
|
});
|
|
214
456
|
} else {
|
|
@@ -257,6 +499,29 @@ export default function (options) {
|
|
|
257
499
|
return match;
|
|
258
500
|
}
|
|
259
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Get a subtree by exact path equality.
|
|
504
|
+
* @param {Object} tree
|
|
505
|
+
* @param {String} path
|
|
506
|
+
* @returns {Object|undefined}
|
|
507
|
+
*/
|
|
508
|
+
function getByExactPath(tree, path) {
|
|
509
|
+
let match;
|
|
510
|
+
if (tree) {
|
|
511
|
+
if (tree.path === path) {
|
|
512
|
+
match = tree;
|
|
513
|
+
} else if (tree.children) {
|
|
514
|
+
_.forEach(tree.children, function (child) {
|
|
515
|
+
match = getByExactPath(child, path);
|
|
516
|
+
if (match) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return match;
|
|
523
|
+
}
|
|
524
|
+
|
|
260
525
|
/**
|
|
261
526
|
* Merge data into at into the subtree
|
|
262
527
|
* @param {Object} tree - the tree model
|
|
@@ -300,8 +565,11 @@ export default function (options) {
|
|
|
300
565
|
return child.path === path || (child.name && tree.path + child.name === path) || child.uri === path;
|
|
301
566
|
});
|
|
302
567
|
done = removed.length > 0;
|
|
303
|
-
|
|
304
|
-
|
|
568
|
+
if (done) {
|
|
569
|
+
if (Number.isFinite(Number(tree.total))) {
|
|
570
|
+
tree.total = Math.max(0, Number(tree.total) - removed.length);
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
305
573
|
_.forEach(tree.children, function (child) {
|
|
306
574
|
done = removeFromPath(child, path);
|
|
307
575
|
if (done) {
|
|
@@ -313,31 +581,116 @@ export default function (options) {
|
|
|
313
581
|
return done;
|
|
314
582
|
}
|
|
315
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Drop cached file rows for a folder so the next load hits the service
|
|
586
|
+
* with the current sort. Nested folder nodes are kept.
|
|
587
|
+
* @param {String} path
|
|
588
|
+
*/
|
|
589
|
+
function invalidateFolderFiles(path) {
|
|
590
|
+
const content = getByExactPath(fileTree, path);
|
|
591
|
+
if (content && Array.isArray(content.children)) {
|
|
592
|
+
content.children = content.children.filter(function (child) {
|
|
593
|
+
return child.path && !child.uri;
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Replace a folder node with a freshly loaded payload (files + folders).
|
|
600
|
+
* @param {String} path
|
|
601
|
+
* @param {Object} data
|
|
602
|
+
*/
|
|
603
|
+
function replaceFolderContent(path, data) {
|
|
604
|
+
const content = getByExactPath(fileTree, path);
|
|
605
|
+
if (!content || !data) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (Object.prototype.hasOwnProperty.call(data, 'children')) {
|
|
609
|
+
content.children = data.children;
|
|
610
|
+
} else {
|
|
611
|
+
content.children = [];
|
|
612
|
+
content.empty = true;
|
|
613
|
+
content.total = 0;
|
|
614
|
+
}
|
|
615
|
+
if (Object.prototype.hasOwnProperty.call(data, 'total')) {
|
|
616
|
+
content.total = data.total;
|
|
617
|
+
}
|
|
618
|
+
if (Object.prototype.hasOwnProperty.call(data, 'childrenLimit')) {
|
|
619
|
+
content.childrenLimit = data.childrenLimit;
|
|
620
|
+
}
|
|
621
|
+
if (data.label) {
|
|
622
|
+
content.label = data.label;
|
|
623
|
+
}
|
|
624
|
+
if (data.path) {
|
|
625
|
+
content.path = data.path;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
316
629
|
/**
|
|
317
630
|
* Get the content of a folder
|
|
318
631
|
* @param {String} path - the folder path
|
|
319
|
-
* @returns {
|
|
632
|
+
* @returns {Promise} resolves with folder content
|
|
320
633
|
*/
|
|
321
634
|
function loadContent(path) {
|
|
322
635
|
const parameters = {};
|
|
323
|
-
parameters[options.pathParam] = path;
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
636
|
+
parameters[options.pathParam || 'path'] = path;
|
|
637
|
+
return Promise.resolve(
|
|
638
|
+
$.ajax({
|
|
639
|
+
url: options.browseUrl,
|
|
640
|
+
method: 'GET',
|
|
641
|
+
dataType: 'json',
|
|
642
|
+
timeout: ajaxTimeoutMs,
|
|
643
|
+
data: _.merge(parameters, options.params, {
|
|
644
|
+
// depth=2 so each rendered child folder includes its own dir children,
|
|
645
|
+
// allowing leaf folders to hide the expand chevron without an extra click.
|
|
646
|
+
depth: 2,
|
|
647
|
+
childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit,
|
|
648
|
+
sortBy: sort.field,
|
|
649
|
+
sortDir: sort.direction
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
).then(function (response) {
|
|
653
|
+
if (response && response.success === false) {
|
|
654
|
+
return Promise.reject(response);
|
|
655
|
+
}
|
|
656
|
+
let data = response && response.data ? response.data : response;
|
|
657
|
+
data = updatePermissions(data);
|
|
658
|
+
if (data.children && data.children.length > 0) {
|
|
659
|
+
data.children.map(responseChildren => updatePermissions(responseChildren));
|
|
660
|
+
}
|
|
661
|
+
return data;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Whether a folder node has nested directory children.
|
|
667
|
+
* @param {Object} node
|
|
668
|
+
* @returns {boolean|null} true/false when known; null when children were not loaded yet
|
|
669
|
+
*/
|
|
670
|
+
function hasNestedFolderChildren(node) {
|
|
671
|
+
if (!node || node.empty === true) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
if (!Array.isArray(node.children)) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
return Boolean(_.find(node.children, 'path'));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Show or hide the expand chevron on a folder anchor (keep spacer width).
|
|
682
|
+
* @param {jQuery} $anchor
|
|
683
|
+
* @param {boolean} hasNested
|
|
684
|
+
*/
|
|
685
|
+
function setFolderToggleState($anchor, hasNested) {
|
|
686
|
+
const $toggle = $anchor.children('.tree-toggle');
|
|
687
|
+
if (hasNested) {
|
|
688
|
+
$toggle.addClass('icon-right').removeClass('is-leaf');
|
|
689
|
+
$anchor.removeClass('is-leaf');
|
|
690
|
+
} else {
|
|
691
|
+
$toggle.removeClass('icon-right').addClass('is-leaf');
|
|
692
|
+
$anchor.removeClass('opened').addClass('is-leaf');
|
|
693
|
+
}
|
|
341
694
|
}
|
|
342
695
|
|
|
343
696
|
/**
|
|
@@ -351,7 +704,10 @@ export default function (options) {
|
|
|
351
704
|
if (typeof data.relPath === 'undefined') {
|
|
352
705
|
data.relPath = data.path;
|
|
353
706
|
}
|
|
707
|
+
// Unknown nested state (lazy depth) keeps the chevron until the folder is opened.
|
|
708
|
+
data.showToggle = hasNestedFolderChildren(data) !== false;
|
|
354
709
|
$parent.append(folderTpl(data));
|
|
710
|
+
return;
|
|
355
711
|
}
|
|
356
712
|
if (data && data.children && _.isArray(data.children) && !data.empty) {
|
|
357
713
|
_.forEach(data.children, function (child) {
|
|
@@ -384,7 +740,11 @@ export default function (options) {
|
|
|
384
740
|
* Render pagination
|
|
385
741
|
*/
|
|
386
742
|
function renderPagination() {
|
|
387
|
-
|
|
743
|
+
if (searchMode) {
|
|
744
|
+
// Drop browse pagination controls/handlers when search owns the table.
|
|
745
|
+
$paginationContainer.empty();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
388
748
|
const total = Number(selectedClass.total);
|
|
389
749
|
const childrenLimit = Number(selectedClass.childrenLimit);
|
|
390
750
|
|
|
@@ -413,11 +773,30 @@ export default function (options) {
|
|
|
413
773
|
.render($paginationContainer);
|
|
414
774
|
}
|
|
415
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Re-fetch the current folder with the active sort and publish files.
|
|
778
|
+
*/
|
|
779
|
+
function reloadSortedFolder() {
|
|
780
|
+
const path = selectedClass.path;
|
|
781
|
+
loadContent(path)
|
|
782
|
+
.then(function (data) {
|
|
783
|
+
if (!data) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
replaceFolderContent(path, data);
|
|
787
|
+
const content = getByExactPath(fileTree, path) || data;
|
|
788
|
+
selectFolder(content, content.path || path);
|
|
789
|
+
})
|
|
790
|
+
.catch(function (error) {
|
|
791
|
+
LOGGER.error(error);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
416
795
|
/**
|
|
417
796
|
* Load page
|
|
418
797
|
*/
|
|
419
798
|
function loadPage() {
|
|
420
|
-
const subTree = getByPath(fileTree, selectedClass.path);
|
|
799
|
+
const subTree = getByPath(fileTree, selectedClass.path) || fileTree;
|
|
421
800
|
|
|
422
801
|
//get the folder content
|
|
423
802
|
getFolderContent(subTree, selectedClass.path, function (content) {
|
|
@@ -425,7 +804,7 @@ export default function (options) {
|
|
|
425
804
|
|
|
426
805
|
if (content) {
|
|
427
806
|
//internal event to set the file-selector content
|
|
428
|
-
$container.trigger(`folderselect.${
|
|
807
|
+
$container.trigger(`folderselect.${NS}`, [content.label, getPage(content.children), content.path, content]);
|
|
429
808
|
}
|
|
430
809
|
});
|
|
431
810
|
}
|