vue-girder-slicer-cli-ui 0.0.8

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.
Files changed (58) hide show
  1. package/README.md +102 -0
  2. package/dist/api/girderRest.d.ts +36 -0
  3. package/dist/api/girderSlicerApi.d.ts +32 -0
  4. package/dist/components/FileBrowser/DataBrowser.tw.vue.d.ts +177 -0
  5. package/dist/components/FileBrowser/Modal.tw.vue.d.ts +27 -0
  6. package/dist/components/FileBrowser/RootSelection.tw.vue.d.ts +41 -0
  7. package/dist/components/FileBrowser/utils.d.ts +9 -0
  8. package/dist/components/GirderControlsPanel.tw.vue.d.ts +29 -0
  9. package/dist/components/GirderSlicerParameter.tw.vue.d.ts +50 -0
  10. package/dist/components/GirderSlicerTaskButton.tw.vue.d.ts +42 -0
  11. package/dist/components/GirderSlicerTaskCard.tw.vue.d.ts +44 -0
  12. package/dist/components/GirderSlicerTaskMenu.tw.vue.d.ts +49 -0
  13. package/dist/components/GlicerSlicerTaskMenuModal.tw.vue.d.ts +34 -0
  14. package/dist/components/GlicerSlicerTaskMenuModalButton.tw.vue.d.ts +38 -0
  15. package/dist/components/GriderSlicerTasksIntegrated.tw.vue.d.ts +74 -0
  16. package/dist/components/Parameters/BaseParameter.tw.vue.d.ts +21 -0
  17. package/dist/components/Parameters/BooleanParameter.tw.vue.d.ts +18 -0
  18. package/dist/components/Parameters/ColorParameter.tw.vue.d.ts +19 -0
  19. package/dist/components/Parameters/EnumerationParameters.tw.vue.d.ts +19 -0
  20. package/dist/components/Parameters/FileParameter.tw.vue.d.ts +32 -0
  21. package/dist/components/Parameters/RangeParameter.tw.vue.d.ts +19 -0
  22. package/dist/favicon.ico +0 -0
  23. package/dist/girderTypes.d.ts +21 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/parser/constraints.d.ts +4 -0
  26. package/dist/parser/convert.d.ts +7 -0
  27. package/dist/parser/defaultValue.d.ts +12 -0
  28. package/dist/parser/group.d.ts +9 -0
  29. package/dist/parser/index.d.ts +8 -0
  30. package/dist/parser/panel.d.ts +11 -0
  31. package/dist/parser/param.d.ts +6 -0
  32. package/dist/parser/parse.d.ts +20 -0
  33. package/dist/parser/parserTypes.d.ts +58 -0
  34. package/dist/parser/widget.d.ts +6 -0
  35. package/dist/style.css +1 -0
  36. package/dist/tailwind.css +1370 -0
  37. package/dist/utils.d.ts +5 -0
  38. package/dist/vue-girder-slicer-cli-ui.js +12987 -0
  39. package/dist/vue-girder-slicer-cli-ui.umd.cjs +59 -0
  40. package/package.json +135 -0
  41. package/src/components/FileBrowser/DataBrowser.tw.vue +842 -0
  42. package/src/components/FileBrowser/Modal.tw.vue +82 -0
  43. package/src/components/FileBrowser/RootSelection.tw.vue +83 -0
  44. package/src/components/FileBrowser/utils.ts +48 -0
  45. package/src/components/GirderControlsPanel.tw.vue +94 -0
  46. package/src/components/GirderSlicerParameter.tw.vue +84 -0
  47. package/src/components/GirderSlicerTaskButton.tw.vue +208 -0
  48. package/src/components/GirderSlicerTaskCard.tw.vue +184 -0
  49. package/src/components/GirderSlicerTaskMenu.tw.vue +217 -0
  50. package/src/components/GlicerSlicerTaskMenuModal.tw.vue +77 -0
  51. package/src/components/GlicerSlicerTaskMenuModalButton.tw.vue +106 -0
  52. package/src/components/GriderSlicerTasksIntegrated.tw.vue +73 -0
  53. package/src/components/Parameters/BaseParameter.tw.vue +76 -0
  54. package/src/components/Parameters/BooleanParameter.tw.vue +54 -0
  55. package/src/components/Parameters/ColorParameter.tw.vue +58 -0
  56. package/src/components/Parameters/EnumerationParameters.tw.vue +66 -0
  57. package/src/components/Parameters/FileParameter.tw.vue +172 -0
  58. package/src/components/Parameters/RangeParameter.tw.vue +74 -0
@@ -0,0 +1,842 @@
1
+ <script lang="ts">
2
+ import { PropType, Ref, defineComponent, onMounted, ref, watch } from 'vue'
3
+ import RestClient from '../../api/girderRest';
4
+ import { GirderModel, GirderModelType } from '../../girderTypes';
5
+ import { mdiAccount, mdiArrowUpRightBold, mdiChevronDoubleLeft, mdiChevronDoubleRight, mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp,
6
+ mdiClose, mdiEarth, mdiFile, mdiFolder, mdiLock, mdiSitemap } from '@mdi/js';
7
+ import SvgIcon from '@jamescoyle/vue-icon';
8
+ import { convertInputNumber, convertInputString, countFormatter, isValidRegex, sizeFormatter } from './utils'
9
+ import RootSelection from './RootSelection.tw.vue';
10
+ export default defineComponent({
11
+ components: {
12
+ SvgIcon,
13
+ RootSelection
14
+ },
15
+ props: {
16
+ apiUrl: {
17
+ type: String,
18
+ default: 'api/v1'
19
+ },
20
+ multi: {
21
+ type: Boolean,
22
+ default: false,
23
+ },
24
+ type: {
25
+ type: String,
26
+ default: 'file',
27
+ },
28
+ limit: {
29
+ type: Number,
30
+ default: 100,
31
+ },
32
+ output: {
33
+ type: Boolean,
34
+ required: false,
35
+ },
36
+ validation: {
37
+ type: Function as PropType<(id: GirderModel) => ({ valid: boolean, msg?: string})>,
38
+ default: () => ({valid: true})
39
+ },
40
+ parentId: {
41
+ type: String,
42
+ required: false,
43
+ default: undefined
44
+ },
45
+ girderId: {
46
+ type: String,
47
+ required: false,
48
+ default: undefined,
49
+ },
50
+ name: {
51
+ type: String,
52
+ required: false,
53
+ default: undefined,
54
+ },
55
+ },
56
+ setup(props, { emit }) {
57
+ const errorMsg = ref('');
58
+ const submit = async () => {
59
+ if (selectedModel.value === null) {
60
+ selectedModel.value = await girderRest.get(`folder/${currentParentId.value}`);
61
+ }
62
+ if (selected.value !== null && selectedModel.value !== null)
63
+ {
64
+ errorMsg.value = '';
65
+ const result = props.validation(selectedModel.value);
66
+ if (result.valid) {
67
+ const data = {
68
+ girderId: selected.value.girderId,
69
+ name: selected.value.name,
70
+ parentId: currentParentId.value,
71
+ regExp: props.multi,
72
+ fileId: selected.value.fileId,
73
+ }
74
+ emit('submit', data);
75
+ } else if (result.msg) {
76
+ errorMsg.value = result.msg;
77
+ }
78
+ }
79
+ }
80
+ const girderRest = new RestClient({apiRoot: props.apiUrl, authenticateWithCredentials: true});
81
+ const iconMap: Ref<{user: string, collection:string, folder:string, item: string, file:string}> = ref({
82
+ 'user': mdiAccount,
83
+ 'collection' : mdiSitemap,
84
+ 'folder': mdiFolder,
85
+ 'item': mdiFile,
86
+ 'file': mdiFile,
87
+ })
88
+ const home: Ref<string | null> = ref(null);
89
+ const users: Ref<GirderModel[] | null> = ref(null);
90
+ const collections: Ref<GirderModel[] | null> = ref(null);
91
+ const user: Ref<GirderModel | null> = ref(null);
92
+ const rootFolders: Ref<GirderModel[] | null> = ref(null);
93
+ const folderCount: Ref<number> = ref(0);
94
+ const folderShow: Ref<boolean> = ref(true);
95
+ const itemShow: Ref<boolean> = ref(true);
96
+ const itemCount: Ref<number> = ref(0);
97
+ const rootItems: Ref<GirderModel[] | null> = ref(null);
98
+ const folderOffset: Ref<number> = ref(0);
99
+ const itemOffset: Ref<number> = ref(0);
100
+ const breadCrumb: Ref<{type: GirderModelType, path: {name:string, id: string}[]}> = ref({type: 'user' as GirderModelType, path:[]})
101
+ const currentParentId = ref('');
102
+ const currentParentType = ref('user');
103
+ const selected: Ref<null | {name: string, girderId: string, parentId?: string, fileId?: string}> = ref(null);
104
+ const selectedModel: Ref<null | GirderModel> = ref(null);
105
+ onMounted(async () => {
106
+ await getData();
107
+ if (props.parentId && props.girderId && props.name) {
108
+ currentParentId.value = props.parentId;
109
+ currentParentType.value = 'user';
110
+ const hierarchy = (await (girderRest.get(`folder/${props.parentId}/rootpath`))).data;
111
+ breadCrumb.value.type = hierarchy[0]['type'];
112
+ breadCrumb.value.path = [];
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ hierarchy.forEach((folder: any) => {
115
+ let name = folder['object']['name'];
116
+ if (!name && breadCrumb.value.type === 'user') {
117
+ name = `${folder['object']['firstName']} ${folder['object']['lastName']}`
118
+ }
119
+ breadCrumb.value.path.push({ name, id: folder['object']['_id'] });
120
+ });
121
+ const baseFolder = (await (girderRest.get(`folder/${props.parentId}`))).data;
122
+ breadCrumb.value.path.push({name: baseFolder['name'], id: baseFolder['_id']});
123
+ updateMainView(props.parentId, 'folder', '', true);
124
+ selected.value = {
125
+ name: props.name,
126
+ girderId: props.girderId,
127
+ parentId: props.parentId,
128
+ }
129
+ recalculatedSelected();
130
+ }
131
+ })
132
+ const updateFolders = async (parentId: string, parentType: string) => {
133
+ const responseFolders = await girderRest.get(`folder`, {
134
+ params: {
135
+ limit: props.limit,
136
+ offset: folderOffset.value * props.limit,
137
+ sort: 'name',
138
+ sortdir: 1,
139
+ parentType,
140
+ parentId,
141
+ },
142
+ }
143
+ );
144
+ folderCount.value = parseInt(responseFolders.headers['girder-total-count']);
145
+ rootFolders.value = responseFolders.data;
146
+ return responseFolders.data;
147
+ };
148
+ const updateItems = async (parentId: string) => {
149
+ const responseItems = await girderRest.get(`item`, {
150
+ params: {
151
+ limit: props.limit,
152
+ offset: itemOffset.value * props.limit,
153
+ sort: 'name',
154
+ sortdir: 1,
155
+ folderId: parentId,
156
+ },
157
+ }
158
+ );
159
+ itemCount.value = parseInt(responseItems.headers['girder-total-count']);
160
+ rootItems.value = responseItems.data;
161
+ return responseItems.data;
162
+ }
163
+ const updateMainView = async (parentId: string, parentType: string, name = '', resetShow=false) => {
164
+ if (props.type === 'directory' && parentType === 'folder') {
165
+ selected.value = {
166
+ name,
167
+ girderId: parentId,
168
+ };
169
+ } else if (parentType !== 'folder') {
170
+ selected.value = null;
171
+ }
172
+ currentParentId.value = parentId;
173
+ currentParentType.value = parentType;
174
+ if (resetShow) {
175
+ itemShow.value = false;
176
+ folderShow.value = false;
177
+ }
178
+ await updateFolders(parentId, parentType);
179
+ if (parentType === 'folder') {
180
+ await updateItems(parentId)
181
+ } else {
182
+ rootItems.value = [];
183
+ }
184
+ const foundBreadCrumbIndex = breadCrumb.value.path.findIndex((item) => item.id === parentId);
185
+ if (foundBreadCrumbIndex !== -1) {
186
+ breadCrumb.value = {
187
+ type: breadCrumb.value.type,
188
+ path: breadCrumb.value.path.slice(0, foundBreadCrumbIndex + 1)
189
+ }
190
+ } else {
191
+ breadCrumb.value = {
192
+ type: breadCrumb.value.type,
193
+ path: [ ...breadCrumb.value.path , { name, id: parentId }],
194
+ };
195
+ }
196
+ }
197
+ const setRoot = async ({ val, type, name}: {val: string, type: GirderModelType, name: string}) => {
198
+ home.value = val;
199
+ breadCrumb.value = { type, path: []};
200
+ updateMainView(val, type, name, true);
201
+ }
202
+ const upLevel = () => {
203
+ if (breadCrumb.value.path.length > 1) { // we can't go up if add root
204
+ const newPath = breadCrumb.value.path[breadCrumb.value.path.length - 2]
205
+ let newPathType = 'folder';
206
+ if( breadCrumb.value.path.length === 2) {
207
+ newPathType = breadCrumb.value.type;
208
+ }
209
+ updateMainView(newPath.id, newPathType, newPath.name, true)
210
+ }
211
+ }
212
+ // This gets the main data for the Root Hierarchy Tool
213
+ const getData = async () => {
214
+ const userResponse = (await girderRest.get('user/me')).data;
215
+ user.value = userResponse;
216
+ // REPLACE WITH A PROMISE ALL
217
+ const collectionsResponse = await girderRest.get('collection', {
218
+ params: {
219
+ limit: 1001,
220
+ offset: 0,
221
+ sort: 'name',
222
+ sortdir: 1,
223
+ },
224
+ });
225
+ const usersResponse = await girderRest.get('user', {
226
+ params: {
227
+ limit: 1001,
228
+ offset: 0,
229
+ sort: 'name',
230
+ sortdir: 1,
231
+ parentType: 'user',
232
+ },
233
+ });
234
+ const rootResponse = await girderRest.get('folder', {
235
+ params: {
236
+ limit: 1001,
237
+ offset: 0,
238
+ sort: 'name',
239
+ sortdir: 1,
240
+ parentType: 'user',
241
+ parentId: userResponse._id,
242
+ },
243
+ });
244
+ users.value = usersResponse.data;
245
+ collections.value = collectionsResponse.data;
246
+ rootFolders.value = rootResponse.data
247
+ home.value = userResponse._id;
248
+ breadCrumb.value = {
249
+ type: 'user',
250
+ path: [{
251
+ name:`${userResponse.firstName} ${userResponse.lastName}`,
252
+ id: userResponse._id
253
+ }]
254
+ }
255
+ }
256
+ watch(folderOffset, () => updateFolders(currentParentId.value, currentParentType.value));
257
+ watch(itemOffset, () => updateItems(currentParentId.value));
258
+ const updateOffset = (type: 'folder' | 'item', value: number) => {
259
+ if (value < 0 || Number.isNaN(value)) {
260
+ folderOffset.value = 0;
261
+ return;
262
+ }
263
+ if (type === 'folder' && value >= Math.ceil(folderCount.value/ props.limit)) {
264
+ folderOffset.value = Math.ceil(folderCount.value/ props.limit)-1;
265
+ return;
266
+ }
267
+ if (type === 'item' && value >= Math.ceil(itemCount.value/ props.limit)) {
268
+ itemOffset.value = Math.ceil(itemCount.value/ props.limit)-1;
269
+ return;
270
+ }
271
+ if (type ==='folder') {
272
+ folderOffset.value = value;
273
+ }
274
+ if (type === 'item') {
275
+ itemOffset.value = value;
276
+ }
277
+ }
278
+ const selecting = async (item: GirderModel, type: 'folder' | 'file') => {
279
+ if (type === props.type) {
280
+ selected.value = {
281
+ name: item.name,
282
+ girderId: item._id,
283
+ parentId: type === 'file' ? item.parentId || undefined : undefined,
284
+ };
285
+ selectedModel.value = item;
286
+ }
287
+ if (type === 'file' && selected.value) {
288
+ const files = (await (girderRest.get(`item/${item._id}/files`))).data
289
+ if (files.length) {
290
+ selected.value.fileId = files[0]['_id'];
291
+ }
292
+ }
293
+ }
294
+ const selectedItems: Ref<Record<string, boolean>> = ref({});
295
+ const setSelectedName = async (data: string) => {
296
+ selected.value = {
297
+ name: data,
298
+ girderId: currentParentId.value,
299
+ parentId: currentParentId.value
300
+ };
301
+ recalculatedSelected();
302
+ }
303
+ const setRegularExpression = (data: string) => {
304
+ if (isValidRegex(data)) {
305
+ selected.value = {
306
+ name: data,
307
+ girderId: data,
308
+ parentId: currentParentId.value,
309
+ };
310
+ recalculatedSelected();
311
+ errorMsg.value = '';
312
+ } else {
313
+ errorMsg.value = 'Specify a valid Regular Expression';
314
+ }
315
+ }
316
+ const recalculatedSelected = () => {
317
+ selectedItems.value = {};
318
+ let reg: RegExp;
319
+ if (selected.value?.name) {
320
+ reg = new RegExp(selected.value.name);
321
+ } else {
322
+ return;
323
+ }
324
+ if (props.type !== 'directory' && rootItems.value) {
325
+ rootItems.value.forEach((item) => {
326
+ if (!props.multi && item.name === selected.value?.name) {
327
+ selectedItems.value[item.name] = true;
328
+ } else if (reg && reg.test(item.name)) {
329
+ selectedItems.value[item.name] = true;
330
+ }
331
+ })
332
+ }
333
+ if (props.type === 'directory' && rootFolders.value) {
334
+ rootFolders.value.forEach((item) => {
335
+ if (!props.multi && item.name === selected.value?.name) {
336
+ selectedItems.value[item.name] = true;
337
+ } else if (reg && reg.test(item.name)) {
338
+ selectedItems.value[item.name] = true;
339
+ }
340
+ })
341
+ }
342
+ }
343
+ return {
344
+ home,
345
+ collections,
346
+ user,
347
+ rootFolders,
348
+ folderCount,
349
+ folderShow,
350
+ folderOffset,
351
+ rootItems,
352
+ itemCount,
353
+ itemShow,
354
+ itemOffset,
355
+ iconMap,
356
+ breadCrumb,
357
+ selected,
358
+ selectedItems,
359
+ selectedModel,
360
+ errorMsg,
361
+ users,
362
+ currentParentType,
363
+ // icons
364
+ mdiAccount,
365
+ mdiArrowUpRightBold,
366
+ mdiChevronDoubleLeft,
367
+ mdiChevronDoubleRight,
368
+ mdiChevronDown, mdiChevronLeft,
369
+ mdiChevronRight,
370
+ mdiChevronUp,
371
+ mdiClose,
372
+ mdiEarth,
373
+ mdiFile,
374
+ mdiFolder,
375
+ mdiLock,
376
+ mdiSitemap,
377
+ updateOffset,
378
+ setRegularExpression,
379
+ setSelectedName,
380
+ updateMainView,
381
+ upLevel,
382
+ countFormatter,
383
+ setRoot,
384
+ convertInputString,
385
+ convertInputNumber,
386
+ selecting,
387
+ submit,
388
+ sizeFormatter,
389
+ }
390
+ },
391
+ });
392
+ </script>
393
+ <template>
394
+ <div>
395
+ <div class="fixed inset-0 z-50 flex justify-center items-center">
396
+ <div class="flex flex-col max-w-5xl rounded-lg shadow-lg bg-backgroundColor text-textColor">
397
+ <!-- Header -->
398
+ <div class="grid grid-cols-12">
399
+ <span class="col-span-10 text-xl my-2 ml-5">
400
+ File Browser
401
+ </span>
402
+ <span class="col-start-12 col-span-1 justify-self-end mr-5 my-2 " @click="$emit('close')">
403
+ <svg-icon
404
+ type="mdi"
405
+ :path="mdiClose"
406
+ :size="30"
407
+ class="gsu-icon gsu-clickable"
408
+ data-dismiss="modal"
409
+ aria-label="Close"
410
+ >
411
+ <span aria-hidden="true">&times;</span>
412
+ </svg-icon>
413
+ </span>
414
+ </div>
415
+
416
+ <!-- Body -->
417
+ <div class="py-2 px-6">
418
+ <div class="modal-body">
419
+ <div class="mx-auto mx-auto">
420
+ <div
421
+ class="flex flex-nowrap"
422
+ >
423
+ <root-selection
424
+ v-if="home && user && collections && users"
425
+ :home="user._id"
426
+ :value="home"
427
+ :collections="collections"
428
+ :users="users"
429
+ @change="setRoot($event)"
430
+ class="grow my-1"
431
+ />
432
+ </div>
433
+ <div class="gsu-breadcrumb mb-0">
434
+ <div class="grid grid-cols-7 rounded mb-0 ">
435
+ <div class="col-start-1 col-span-4 flex flex-row">
436
+ <div class="grow">
437
+ <span
438
+ v-for="(item, index) in breadCrumb.path"
439
+ :key="`breadCrumb_${item.id}`"
440
+ >
441
+ <span
442
+ v-if="index === 0"
443
+ @click="updateMainView(item.id, breadCrumb.type, item.name, true);"
444
+ >
445
+ <svg-icon
446
+ type="mdi"
447
+ :path="iconMap[breadCrumb.type]"
448
+ color="lightblue"
449
+ :size="30"
450
+ class="pb-2 gsu-icon gsu-clickable"
451
+ style="display:inline"
452
+ />
453
+ <span
454
+ class="gsu-clickable"
455
+ >
456
+ {{ item.name }}
457
+ </span>
458
+ </span>
459
+ <span
460
+ v-else-if="index !== breadCrumb.path. length - 1"
461
+ class="gsu-clickable"
462
+ @click="updateMainView(item.id, 'folder', item.name, true)"
463
+ >
464
+ {{ item.name }}
465
+ </span>
466
+ <span v-else>
467
+ {{ item.name }}
468
+ </span>
469
+ <span class="px-2">
470
+ /
471
+ </span>
472
+ </span>
473
+ </div>
474
+ </div>
475
+ <div class="col-start-7 col-span-3">
476
+ <div class="flex flex-row">
477
+ <div class="flex flex-row">
478
+ <svg-icon
479
+ type="mdi"
480
+ :path="iconMap['folder']"
481
+ color="gray"
482
+ :size="30"
483
+ class="pb-2 gsu-icon"
484
+ />
485
+ <span
486
+ class="number-badge"
487
+ >{{ countFormatter(folderCount) }}</span>
488
+ </div>
489
+ <div class="flex flex-row">
490
+ <svg-icon
491
+ type="mdi"
492
+ :path="iconMap['item']"
493
+ color="gray"
494
+ :size="30"
495
+ class="pb-2 gsu-icon"
496
+ />
497
+ <span
498
+ class="number-badge"
499
+ >{{ countFormatter(itemCount) }}</span>
500
+ </div>
501
+ <div class="px-2">
502
+ <svg-icon
503
+ type="mdi"
504
+ :path="mdiArrowUpRightBold"
505
+ color="blue"
506
+ :size="25"
507
+ class="pb-2 gsu-icon gsu-clickable"
508
+ @click="upLevel()"
509
+ />
510
+ </div>
511
+ </div>
512
+ </div>
513
+ </div>
514
+ </div>
515
+ </div>
516
+ <div class="data-list">
517
+ <div
518
+ v-if="(folderCount > limit || itemCount > limit) && folderCount"
519
+ class="folder-header"
520
+ >
521
+ <div class="flex flex-wrap">
522
+ <div
523
+ class="w-1/5"
524
+ @click="folderShow = !folderShow"
525
+ >
526
+ <svg-icon
527
+ type="mdi"
528
+ :path="folderShow ? mdiChevronDown : mdiChevronUp"
529
+ :size="30"
530
+ class="gsu-icon gsu-clickable header"
531
+ />
532
+ <span class="header">
533
+ Folders
534
+ </span>
535
+ </div>
536
+ <div class="w-2/3">
537
+ <div
538
+ v-if="folderCount > limit"
539
+ class="flex flex-wrap justify-content-center"
540
+ >
541
+ <div
542
+ class=""
543
+ @click="updateOffset('folder', 0)"
544
+ >
545
+ <svg-icon
546
+ type="mdi"
547
+ :path="mdiChevronDoubleLeft"
548
+ :size="30"
549
+ class="gsu-icon gsu-clickable"
550
+ :class="{'gsu-disabled-icon': folderOffset === 0}"
551
+ />
552
+ </div>
553
+ <div
554
+ class=""
555
+ @click="updateOffset('folder', folderOffset - 1)"
556
+ >
557
+ <svg-icon
558
+ type="mdi"
559
+ :path="mdiChevronLeft"
560
+ :size="30"
561
+ class="gsu-icon gsu-clickable"
562
+ :class="{'gsu-disabled-icon': folderOffset === 0}"
563
+ />
564
+ </div>
565
+ <div
566
+ class=""
567
+ >
568
+ <span class="header"> Page
569
+ <input
570
+ type="number"
571
+ :value="folderOffset + 1"
572
+ min="1"
573
+ Lmax=""
574
+ inputmode="numeric"
575
+ style="width:40px"
576
+ class="number-input"
577
+ @change="updateOffset('folder', convertInputNumber($event))"
578
+ >
579
+ of {{ Math.ceil(folderCount / limit) }}</span>
580
+ </div>
581
+ <div
582
+ class=""
583
+ @click="updateOffset('folder', folderOffset + 1)"
584
+ >
585
+ <svg-icon
586
+ type="mdi"
587
+ :path="mdiChevronRight"
588
+ :size="30"
589
+ class="gsu-icon gsu-clickable"
590
+ :class="{'gsu-disabled-icon': folderOffset >= Math.ceil(folderCount/limit)-1}"
591
+ />
592
+ </div>
593
+ <div
594
+ class=""
595
+ @click="updateOffset('folder', Math.ceil(folderCount/limit)-1)"
596
+ >
597
+ <svg-icon
598
+ type="mdi"
599
+ :path="mdiChevronDoubleRight"
600
+ :size="30"
601
+ :class="{'gsu-disabled-icon': folderOffset === Math.ceil(folderCount/limit)-1}"
602
+ class="gsu-icon gsu-clickable"
603
+ />
604
+ </div>
605
+ </div>
606
+ </div>
607
+ <div class="w-1/5" />
608
+ </div>
609
+ </div>
610
+ <span v-if="((itemCount > limit || folderCount > limit) && folderShow) || (itemCount <= limit && folderCount <= limit)">
611
+ <div
612
+ v-for="item in rootFolders"
613
+ :key="item._id"
614
+ :class="{'gsu-selected-items' :selectedItems[item.name] || (selected && selected.girderId === item.girderId)}"
615
+ class="grid grid-cols-8 justify-content-left g-0 gsu-item-row"
616
+ @click="selecting(item, 'folder'); updateMainView(item._id, item._modelType, item.name)"
617
+ >
618
+ <div class="col-start-1 col-span-7 flex-grow text-left">
619
+ <svg-icon
620
+ type="mdi"
621
+ :path="iconMap[item._modelType]"
622
+ color="lightblue"
623
+ :size="30"
624
+ class="pb-2 gsu-icon"
625
+ />
626
+ <span
627
+ class=" gsu-clickable"
628
+ >
629
+ {{ item.name }}
630
+ </span>
631
+ </div>
632
+ <div class="">
633
+ <svg-icon
634
+ type="mdi"
635
+ :path="item.public ? mdiEarth : mdiLock"
636
+ color="gray"
637
+ :size="30"
638
+ class="pb-2 gsu-icon"
639
+ />
640
+ <span class="gsu-row-info"> {{ item.public ? 'Public' : 'Private' }}</span>
641
+ </div>
642
+ </div>
643
+ </span>
644
+ <div
645
+ v-if="(itemCount > limit || folderCount > limit) && itemCount"
646
+ class="item-header"
647
+ >
648
+ <div
649
+ class="flex flex-wrap"
650
+ @click="itemShow = !itemShow"
651
+ >
652
+ <div class="w-1/5">
653
+ <svg-icon
654
+ type="mdi"
655
+ :path="itemShow ? mdiChevronDown : mdiChevronUp"
656
+ :size="30"
657
+ class="gsu-icon gsu-clickable"
658
+ />
659
+ <span class="header">
660
+ Items
661
+ </span>
662
+ </div>
663
+ <div class="w-2/3">
664
+ <div
665
+ v-if="itemCount > limit"
666
+ class="flex flex-wrap justify-content-center"
667
+ >
668
+ <div
669
+ class=""
670
+ @click="updateOffset('item', 0)"
671
+ >
672
+ <svg-icon
673
+ type="mdi"
674
+ :path="mdiChevronDoubleLeft"
675
+ :size="30"
676
+ class="icon gsu-clickable"
677
+ :class="{'gsu-disabled-icon': itemOffset === 0}"
678
+ />
679
+ </div>
680
+ <div
681
+ class=""
682
+ @click="updateOffset('item', itemOffset - 1)"
683
+ >
684
+ <svg-icon
685
+ type="mdi"
686
+ :path="mdiChevronLeft"
687
+ :size="30"
688
+ class="gsu-icon gsu-clickable"
689
+ :class="{'gsu-disabled-icon': itemOffset === 0}"
690
+ />
691
+ </div>
692
+ <div class="">
693
+ <span class="header"> Page
694
+ <input
695
+ type="number"
696
+ :value="itemOffset + 1"
697
+ min="1"
698
+ Lmax=""
699
+ inputmode="numeric"
700
+ style="width:40px"
701
+ class="number-input"
702
+ @change="updateOffset('item', convertInputNumber($event))"
703
+ >
704
+ of {{ Math.ceil(itemCount / limit) }}</span>
705
+ </div>
706
+ <div
707
+ class=""
708
+ @click="updateOffset('item', itemOffset + 1)"
709
+ >
710
+ <svg-icon
711
+ type="mdi"
712
+ :path="mdiChevronRight"
713
+ :size="30"
714
+ class="gsu-icon gsu-clickable"
715
+ :class="{'gsu-disabled-icon': itemOffset >= Math.ceil(itemCount/limit)-1}"
716
+ />
717
+ </div>
718
+ <div
719
+ class=""
720
+ @click="updateOffset('item', Math.ceil(folderCount/limit)-1)"
721
+ >
722
+ <svg-icon
723
+ type="mdi"
724
+ :path="mdiChevronDoubleRight"
725
+ :size="30"
726
+ :class="{'gsu-disabled-icon': itemOffset === Math.ceil(itemCount/limit)-1}"
727
+ class="gsu-icon gsu-clickable"
728
+ />
729
+ </div>
730
+ </div>
731
+ </div>
732
+ <div class="w-1/5" />
733
+ </div>
734
+ </div>
735
+ <span v-if="((itemCount > limit || folderCount > limit) && itemShow) || (itemCount <= limit && folderCount <= limit)">
736
+ <div
737
+ v-for="item in rootItems"
738
+ :key="item._id"
739
+ class="grid grid-cols-8 justify-content-left g-0 gsu-item-row"
740
+ :class="{'gsu-' :selectedItems[item.name] || (selected && selected.girderId === item._id)}"
741
+ @click="selecting(item, 'file')"
742
+ >
743
+ <div class="col-start-1 col-span-6 flex-grow text-left">
744
+ <svg-icon
745
+ type="mdi"
746
+ :path="iconMap[item._modelType]"
747
+ color="lightblue"
748
+ :size="30"
749
+ class="pb-2 gsu-icon"
750
+ />
751
+ <span>
752
+ {{ item.name }}
753
+ </span>
754
+ </div>
755
+ <div class="col-span-1">
756
+ <span class="gsu-row-info">{{ sizeFormatter(item.size) }}</span>
757
+ </div>
758
+ <div class="col-span-1">
759
+ <svg-icon
760
+ type="mdi"
761
+ :path="item.public ? mdiEarth : mdiLock"
762
+ color="gray"
763
+ :size="30"
764
+ class="pb-2 gsu-icon"
765
+ />
766
+ <span class="gsu-row-info"> {{ item.public ? 'Public' : 'Private' }}</span>
767
+ </div>
768
+ </div>
769
+ </span>
770
+ </div>
771
+ </div>
772
+ <div
773
+ v-if="['directory', 'file'].includes(type)"
774
+ class="mx-5 gsu-selection mb-2"
775
+ >
776
+ <div v-if="!multi">
777
+ <span> Selected {{ type === 'directory' ? 'folder' : 'file' }}:</span>
778
+ <input
779
+ v-if="type === 'directory'"
780
+ :value="selected ? selected.name : 'Select a folder'"
781
+ class="gsu-input block appearance-none w-full py-1 px-2 mb-1 text-base leading-normal rounded"
782
+ type="text"
783
+ placeholder="Select a folder…"
784
+ readonly
785
+ >
786
+ <input
787
+ v-if="type === 'file'"
788
+ :value="selected && selected.name"
789
+ class="gsu-input block appearance-none w-full py-1 px-2 mb-1 text-base leading-normal rounded"
790
+ type="text"
791
+ :placeholder="!output ? 'Select an item' : 'Output name:'"
792
+ :disabled="!output || currentParentType !== 'folder'"
793
+ @input="setSelectedName(convertInputString($event))"
794
+ >
795
+ </div>
796
+ <div v-else>
797
+ <span> {{ type === 'directory' ? 'Folder' : 'Item' }} Filter (Regular Expression)</span>
798
+ <input
799
+ :value="selected && selected.name"
800
+ class="gsu-input block appearance-none w-full py-1 px-2 mb-1 text-base leading-normal rounded"
801
+ type="text"
802
+ placeholder="Regular Expression"
803
+ @input="setRegularExpression(convertInputString($event))"
804
+ >
805
+ </div>
806
+ </div>
807
+ <div
808
+ v-if="errorMsg"
809
+ class="mx-5 error-msg"
810
+ >
811
+ {{ errorMsg }}
812
+ </div>
813
+ </div>
814
+
815
+ <!-- Footer -->
816
+ <div class="p-6 flex justify-end items-center">
817
+ <button
818
+ type="button"
819
+ class="gsu-btn inline-block align-middle text-center select-none border font-normal whitespace-no-wrap py-2 px-4 rounded text-base leading-normal no-underline mx-3"
820
+ @click="$emit('close')"
821
+ >
822
+ Cancel
823
+ </button>
824
+ <button
825
+ type="button"
826
+ class="gsu-btn-accept inline-block align-middle text-center select-none border font-normal whitespace-no-wrap py-2 px-4 rounded text-base leading-normal no-underline"
827
+ data-dismiss="modal"
828
+ :class="{'gsu-btn-disabled': selected === null || !!errorMsg || (multi && Object.values(selectedItems).length === 0)}"
829
+ :disabled="selected === null || !!errorMsg || (multi && Object.values(selectedItems).length === 0)"
830
+ @click="submit()"
831
+ >
832
+ Confirm
833
+ </button>
834
+ </div>
835
+ </div>
836
+ </div>
837
+ <div class="opacity-25 fixed inset-0 z-40 bg-black"></div>
838
+ </div>
839
+ </template>
840
+ <style scoped>
841
+
842
+ </style>