@hostlink/nuxt-light 1.21.2 → 1.21.4

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.
@@ -1,750 +0,0 @@
1
- <script setup>
2
- import { useI18n } from "vue-i18n";
3
- import { ref, watch, computed } from 'vue';
4
- import { useQuasar, format } from 'quasar';
5
- import { q, m, useLight, api } from '#imports';
6
- const { humanStorageSize } = format
7
-
8
- const light = useLight();
9
- const i18n = useI18n();
10
- const quasar = useQuasar();
11
- const $q = quasar;
12
- const emit = defineEmits(["input", "close"]);
13
-
14
- const props = defineProps({
15
- closeable: Boolean,
16
- height: {
17
- type: String,
18
- default: "700px",
19
- },
20
- defaultAction: {
21
- default: "preview",
22
- type: String,
23
- },
24
- multiple: {
25
- default: false,
26
- type: Boolean,
27
- },
28
- base: {
29
- default: "/",
30
- type: String,
31
- },
32
- });
33
-
34
- const loading = ref(false);
35
- const folderTree = ref(null);
36
-
37
- const pagination = {
38
- rowsPerPage: 0,
39
- "rows-per-page-options": [0],
40
- };
41
-
42
- const columns = ref([
43
- {
44
- name: "icon",
45
- label: "",
46
- field: "type",
47
- },
48
- {
49
- name: "name",
50
- label: i18n.t("Name"),
51
- field: "name",
52
- sortable: true,
53
- align: "left",
54
- },
55
- {
56
- name: "last_modified",
57
- label: i18n.t("Last Modified"),
58
- field: "lastModifiedHuman",
59
- align: "left",
60
- },
61
- {
62
- name: "size_display",
63
- label: i18n.t("Size"),
64
- field: "size",
65
- align: "right",
66
- format: (val) => {
67
- if (val == null) {
68
- return "";
69
- }
70
- return humanStorageSize(val)
71
- }
72
- },
73
- { name: "action" },
74
- ]);
75
-
76
- const localSearch = ref(null);
77
- const rightDrawerOpen = ref(false);
78
- const leftDrawerOpen = ref(false);
79
- const showDateOptions = ref(false);
80
- const exactPhrase = ref("");
81
- const hasWords = ref("");
82
- const excludeWords = ref("");
83
- const byWebsite = ref("");
84
- const byDate = ref("Any time");
85
-
86
- function onClear() {
87
- exactPhrase.value = "";
88
- hasWords.value = "";
89
- excludeWords.value = "";
90
- byWebsite.value = "";
91
- byDate.value = "Any time";
92
- }
93
-
94
- function changeDate(option) {
95
- byDate.value = option;
96
- showDateOptions.value = false;
97
- }
98
-
99
- function toggleLeftDrawer() {
100
- leftDrawerOpen.value = !leftDrawerOpen.value;
101
- }
102
-
103
- const path = ref(props.base);
104
-
105
- const onLazyLoad = async ({ node, key, done, fail }) => {
106
- const data = await api.fs.folders.list(node.path);
107
- data.map((item) => {
108
- item.lazy = true;
109
- return item;
110
- });
111
- done(data);
112
- }
113
-
114
- const items = ref([]);
115
-
116
- const loadItems = async () => {
117
- loading.value = true;
118
- items.value = [];
119
-
120
- let filesParams = { path: path.value };
121
-
122
- if (label.value) {
123
- filesParams.type = label.value;
124
- }
125
-
126
- if (localSearch.value) {
127
- filesParams.search = localSearch.value;
128
- }
129
-
130
- let files = await q("fsListFiles", filesParams, ["name", "size", "mime", "path", "canPreview", "imagePath", "lastModified", "lastModifiedHuman"]);
131
- files = files.map((item) => {
132
- item.type = "file";
133
- return item;
134
- });
135
-
136
- let folders = [];
137
- if (!label.value && !localSearch.value) {
138
- folders = await api.fs.folders.list(path.value);
139
- folders = folders.map((item) => {
140
- item.type = "folder";
141
- item.lazy = true;
142
- return item;
143
- });
144
- }
145
- items.value = folders.concat(files);
146
-
147
- loading.value = false;
148
-
149
- return {
150
- files,
151
- folders,
152
- }
153
- }
154
-
155
- watch(path, async (val) => {
156
- selected.value = [];
157
- const items = await loadItems();
158
- reloadTreeFolder(path.value, items.folders);
159
-
160
- });
161
-
162
- const label = ref(null);
163
-
164
- const initItems = await loadItems();
165
- const folders = ref(initItems.folders);
166
-
167
- watch(label, () => {
168
- loadItems();
169
- })
170
-
171
- const selected = ref([]);
172
-
173
- const breadcrumbs = computed(() => {
174
- let breadcrumbs = [
175
- {
176
- label: i18n.t("Storage"),
177
- path: props.base,
178
- },
179
- ];
180
-
181
- if (path.value.toString() == "") {
182
- return breadcrumbs;
183
- }
184
-
185
- let paths = path.value.split(props.base);
186
-
187
- let ps = [];
188
- for (let p of paths) {
189
- if (p) {
190
- ps.push(p);
191
- breadcrumbs.push({
192
- label: p,
193
- path: ps.join("/"),
194
- });
195
- }
196
- }
197
-
198
- return breadcrumbs;
199
- });
200
-
201
- const onClickRow = (evt, row, index) => {
202
- if (row.type == "folder") {
203
- preview.value = null;
204
- return;
205
- }
206
-
207
- preview.value = row;
208
- }
209
-
210
- const grid = ref(false);
211
-
212
- const onDblclickRow = (evt, row, index) => {
213
- if (row.type == "folder") {
214
- path.value = row.path;
215
- return;
216
- }
217
- if (row.type == "file") {
218
- emit("input", row.path);
219
- }
220
- }
221
-
222
-
223
- const findFolder = (path, folders) => {
224
- folders.value.forEach((item) => {
225
- if (item.path == path) {
226
- return item;
227
- }
228
- //find in children
229
- let found = findFolder(path, item.children);
230
- if (found) {
231
- return found;
232
- }
233
- });
234
- }
235
-
236
- const reloadTreeFolder = (path, newFolders) => {
237
-
238
- let node = folderTree.value.getNodeByKey(path);
239
- if (node) {
240
- node.lazy = false;
241
- node.children = newFolders;
242
- folderTree.value.setExpanded(path, true);
243
- } else {
244
-
245
- folders.value = newFolders;
246
- }
247
-
248
- }
249
-
250
- const onDeleteSelected = () => {
251
- quasar.dialog({
252
- title: "Delete",
253
- message: "Are you sure you want to delete this files or folders?",
254
- cancel: true,
255
- }).onOk(async () => {
256
- for (let row of selected.value) {
257
- if (row.type == "folder") {
258
- await api.fs.folders.delete(row.path)
259
- } else {
260
- await api.fs.files.delete(row.path)
261
- }
262
- }
263
- selected.value = [];
264
- const items = await loadItems();
265
- reloadTreeFolder(path.value, items.folders);
266
- });
267
-
268
- }
269
-
270
- const onNewFolder = () => {
271
- quasar.dialog({
272
- title: "New Folder",
273
- prompt: {
274
- label: "Name",
275
- },
276
- cancel: true,
277
- }).onOk(async (name) => {
278
- await api.fs.folders.create(path.value + "/" + name);
279
- const items = await loadItems();
280
- reloadTreeFolder(path.value, items.folders);
281
- });
282
- }
283
-
284
- const onDeleteRow = (row) => {
285
- if (row.type == "file") {
286
- quasar.dialog({
287
- title: "Delete",
288
- message: "Are you sure you want to delete this file?",
289
- cancel: true,
290
- }).onOk(async () => {
291
- await api.fs.files.delete(row.path);
292
- const items = await loadItems();
293
- reloadTreeFolder(path.value, items.folders);
294
- });
295
- } else if (row.type == "folder") {
296
- quasar.dialog({
297
- title: "Delete",
298
- message: "Are you sure you want to delete this folder?",
299
- cancel: true,
300
- }).onOk(async () => {
301
- await api.fs.folders.delete(row.path);
302
- const items = await loadItems();
303
- reloadTreeFolder(path.value, items.folders);
304
- });
305
- }
306
- }
307
-
308
- const onRenameRow = (row) => {
309
- quasar.dialog({
310
- title: "Rename " + row.type,
311
- prompt: {
312
- label: "Name",
313
- model: row.name,
314
- },
315
- cancel: true,
316
- }).onOk(async (name) => {
317
- try {
318
- if (row.type == "file") {
319
- await api.fs.files.rename(row.path, name)
320
- } else {
321
- await api.fs.folders.rename(row.path, name)
322
- }
323
- } catch (e) {
324
- quasar.dialog({
325
- title: "Error",
326
- message: e.message,
327
- });
328
-
329
- return;
330
- }
331
-
332
- const items = await loadItems();
333
- reloadTreeFolder(path.value, items.folders);
334
-
335
- });
336
- }
337
- const uploadFiles = ref([]);
338
- const showUploadFiles = ref(false);
339
-
340
-
341
- const onUploadFiles = async () => {
342
-
343
- $q.loading.show({
344
- message: "Uploading files...",
345
- });
346
-
347
- try {
348
- for (let file of uploadFiles.value) {
349
-
350
- await m("fsUploadFile", {
351
- path: path.value,
352
- file
353
- });
354
- }
355
- } catch (e) {
356
- $q.dialog({
357
- title: "Error",
358
- message: e.message,
359
- });
360
- $q.loading.hide();
361
- return;
362
- }
363
- uploadFiles.value = [];
364
-
365
- $q.loading.hide();
366
-
367
- showUploadFiles.value = false;
368
-
369
- const items = await loadItems();
370
- reloadTreeFolder(path.value, items.folders);
371
-
372
- }
373
-
374
- const preview = ref(null);
375
-
376
- const drive = computed(() => {
377
- return api.drive(props.driveIndex);
378
- })
379
-
380
- const moveToFolder = async (folder) => {
381
-
382
- for (let row of selected.value) {
383
- m("fsMove", { path: row.path, target: folder })
384
-
385
- }
386
-
387
- const items = await loadItems();
388
- reloadTreeFolder(path.value, items.folders);
389
- }
390
-
391
- const submitSearch = (e) => {
392
- localSearch.value = search.value;
393
- loadItems();
394
- }
395
-
396
- /** for grid view */
397
- const foldersGrid = computed(() => {
398
- return items.value.filter((item) => {
399
- return item.type == "folder";
400
- });
401
- });
402
-
403
- const filesGrid = computed(() => {
404
- return items.value.filter((item) => {
405
- return item.type == "file";
406
- });
407
- });
408
-
409
- const onDownloadRow = async (row) => {
410
-
411
- const resp = await q("fsFile", {
412
- path: row.path
413
- }, ["base64Content"]);
414
-
415
- const downloadLink = document.createElement("a");
416
- downloadLink.href = `data:application/octet-stream;base64,${resp.base64Content}`;
417
- downloadLink.download = row.name;
418
- downloadLink.click();
419
-
420
-
421
- }
422
-
423
- const search = ref(null);
424
-
425
- const reloadStorage = async () => {
426
- path.value = props.base;
427
-
428
- search.value = "";
429
- localSearch.value = "";
430
- label.value = null;
431
- const items = await loadItems();
432
- reloadTreeFolder(path.value, items.folders);
433
- }
434
-
435
- const permission = await api.auth.granted([
436
- 'fs.folder.create', 'fs.folder.delete', 'fs.folder.rename',
437
- 'fs.file.delete', 'fs.file.rename', 'fs.file.upload'
438
- ]);
439
-
440
- const canDeleteRow = (row) => {
441
- if (row.type == "folder" && permission.includes("fs.folder.delete")) {
442
- return true;
443
- }
444
-
445
- if (row.type == "file" && permission.includes("fs.file.delete")) {
446
- return true;
447
- }
448
-
449
- return false;
450
- }
451
-
452
- const canRenameRow = (row) => {
453
- if (row.type == "folder" && permission.includes("fs.folder.rename")) {
454
- return true;
455
- }
456
-
457
- if (row.type == "file" && permission.includes("fs.file.rename")) {
458
- return true;
459
- }
460
-
461
- return false;
462
- }
463
-
464
- /* const perm = await q("granted", {
465
- rights: ["fs.folder.create"]
466
- }) */
467
-
468
- const showPreviewImgDialog = ref(false);
469
- const previewImg = ref(null);
470
- const isDark = computed(() => light.isDarkMode());
471
-
472
- const onPreview = (row) => {
473
- showPreviewImgDialog.value = true;
474
- previewImg.value = row.imagePath;
475
- }
476
-
477
- const onPreviewPDF = async (row) => {
478
- const height = window.innerHeight - 200;
479
- quasar.dialog({
480
- autoClose: true,
481
- fullWidth: true,
482
- fullHeight: true,
483
- title: "Preview PDF",
484
- message: "<iframe src='" + row.imagePath + "' width='100%' height='" + height + "px'></iframe>",
485
- html: true,
486
-
487
- })
488
- }
489
-
490
- const onClickInfo = async (row) => {
491
- rightDrawerOpen.value = true;
492
-
493
- }
494
-
495
- </script>
496
- <template>
497
- <q-layout view="hHh lpR fFf" :class="isDark ? '' : 'bg-white'" container :style="{ 'min-height': height }">
498
- <q-header bordered :class="isDark ? '' : 'bg-white text-grey-8'" height-hint="64">
499
- <q-toolbar>
500
- <q-btn flat round @click="toggleLeftDrawer" aria-label="Menu" icon="menu" class="q-mr-sm" />
501
-
502
- <q-toolbar-title v-if="$q.screen.gt.xs" shrink class="row items-center no-wrap">
503
- <span class="q-ml-sm">{{ $t('File Manager') }}</span>
504
- </q-toolbar-title>
505
-
506
- <div>
507
- <q-input outlined :placeholder="$t('Search for file name')" dense @keyup.enter.native="submitSearch"
508
- v-model="search" :color="$light.color">
509
- <template v-slot:append>
510
- <q-btn flat dense round icon="sym_o_search"></q-btn>
511
- </template>
512
- </q-input>
513
- </div>
514
-
515
- <q-space></q-space>
516
- <q-btn v-if="closeable" icon="close" flat round @click="$emit('close')"></q-btn>
517
- </q-toolbar>
518
- </q-header>
519
-
520
- <q-drawer v-model="leftDrawerOpen" show-if-above bordered :width="257">
521
- <q-scroll-area class="fit">
522
- <q-list padding class="text-grey-8">
523
- <q-item>
524
- <q-item-section>
525
- <q-btn icon="add" outline rounded :color="light.color" :label="$t('New')">
526
- <q-menu>
527
- <q-list>
528
- <q-item clickable v-close-popup @click="onNewFolder" v-if="permission.includes('fs.folder.create')">
529
- <q-item-section avatar>
530
- <q-icon name="sym_o_create_new_folder"></q-icon>
531
- </q-item-section>
532
- <q-item-section>{{ $t('Folder') }}</q-item-section>
533
- </q-item>
534
- <q-separator />
535
- <q-item clickable v-close-popup @click="showUploadFiles = true"
536
- v-if="permission.includes('fs.file.upload')">
537
- <q-item-section avatar>
538
- <q-icon name="sym_o_upload_file"></q-icon>
539
- </q-item-section>
540
- <q-item-section>{{ $t('Upload file') }}</q-item-section>
541
- </q-item>
542
- </q-list>
543
- </q-menu>
544
- </q-btn>
545
- </q-item-section>
546
- </q-item>
547
-
548
- <q-list dense>
549
- <q-item>
550
- <q-item-section avatar>
551
- <q-icon name="sym_o_storage" />
552
- </q-item-section>
553
- <q-item-section>{{ $t('Storage') }}</q-item-section>
554
- <q-item-section avatar>
555
- <q-btn dense round flat icon="sym_o_refresh" @click="reloadStorage"></q-btn>
556
- </q-item-section>
557
- </q-item>
558
- <q-tree ref="folderTree" class="q-pl-md" :nodes="folders" node-key="path" label-key="name"
559
- @lazy-load="onLazyLoad" v-model:selected="path" no-selection-unset>
560
- </q-tree>
561
- </q-list>
562
- <!-- q-expansion-item :label="$t('Storage')" default-expand-all default-opened icon="sym_o_storage" selectable
563
- @click="path = '/'">
564
- <q-tree ref="folderTree" class="q-pl-md" :nodes="folders" node-key="path" label-key="name"
565
- @lazy-load="onLazyLoad" v-model:selected="path" no-selection-unset>
566
- </q-tree>
567
- </q-expansion-item-->
568
-
569
- <q-separator inset class="q-my-sm" />
570
-
571
- <l-file-manager-labels v-model="label" />
572
- </q-list>
573
- </q-scroll-area>
574
- </q-drawer>
575
-
576
- <q-drawer v-model="rightDrawerOpen" side="right" show-if-above bordered>
577
- <l-file-manager-preview v-model="preview" v-if="preview" :key="preview.path" />
578
- </q-drawer>
579
-
580
- <q-page-container :style="{ height }">
581
-
582
- <q-dialog v-model="showPreviewImgDialog" full-width full-height auto-close>
583
- <q-card>
584
- <q-card-section>
585
- <q-img :src="previewImg"></q-img>
586
- </q-card-section>
587
- </q-card>
588
- </q-dialog>
589
-
590
- <q-dialog v-model="showUploadFiles" persistent transition-show="scale" transition-hide="scale">
591
- <l-card style="width:300px">
592
- <q-card-section>
593
- <q-file ref="file" v-model="uploadFiles" multiple name="file" label="Files" :color="light.color"></q-file>
594
- </q-card-section>
595
-
596
- <q-card-actions align="right">
597
- <q-btn flat :label="$t('Cancel')" :color="light.color" v-close-popup></q-btn>
598
- <q-btn flat :label="$t('Upload')" :color="light.color" @click="onUploadFiles"></q-btn>
599
- </q-card-actions>
600
- </l-card>
601
- </q-dialog>
602
-
603
- <q-toolbar>
604
- <q-breadcrumbs :active-color="$light.color">
605
- <q-breadcrumbs-el v-for="(b, index) in breadcrumbs" :label="b.label" :key="index" @click="path = b.path"
606
- href="javascript:void(0)"></q-breadcrumbs-el>
607
- </q-breadcrumbs>
608
- <q-space></q-space>
609
-
610
-
611
- <q-btn flat round icon="sym_o_drive_file_move" v-if="selected.length > 0">
612
- <l-file-manager-move @selected="moveToFolder($event)" />
613
- <q-tooltip>
614
- {{ $t('Move to') }}
615
- </q-tooltip>
616
- </q-btn>
617
-
618
- <q-btn flat round icon="o_delete" @click="onDeleteSelected" v-if="selected.length > 0">
619
- <q-tooltip>
620
- {{ $t('Delete') }}
621
- </q-tooltip>
622
- </q-btn>
623
- <q-btn flat round :icon="grid ? 'list_view' : 'grid_view'" @click="grid = !grid">
624
- <q-tooltip>
625
- {{ grid ? $t('List view') : $t('Grid view') }}
626
- </q-tooltip>
627
- </q-btn>
628
- </q-toolbar>
629
-
630
- <template v-if="grid">
631
- <q-table :title="$t('Folders')" flat grid :columns="columns" :rows="foldersGrid" hide-pagination
632
- :pagination="{ rowsPerPage: 0 }">
633
- <template v-slot:item="props">
634
- <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4" @click="onDblclickRow(null, props.row, null)">
635
- <q-card flat bordered>
636
- <q-item>
637
- <q-item-section avatar>
638
- <q-icon name="sym_o_folder" size="sm"></q-icon>
639
- </q-item-section>
640
- <q-item-section>
641
- {{ props.row.name }}
642
- </q-item-section>
643
- <q-item-section avatar>
644
- <q-checkbox v-model="selected" :val="props.row" :color="$light.color" />
645
- </q-item-section>
646
- </q-item>
647
- </q-card>
648
- </div>
649
- </template>
650
- </q-table>
651
-
652
- <q-table :title="$t('Files')" flat grid :columns="columns" :rows="filesGrid" hide-pagination
653
- :pagination="{ rowsPerPage: 0 }">
654
-
655
- <template v-slot:item="props">
656
- <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4" @click="onClickRow(null, props.row, null)">
657
- <q-card flat bordered>
658
- <q-item>
659
- <q-item-section avatar>
660
- <q-icon name="sym_o_description" size="sm"></q-icon>
661
- </q-item-section>
662
- <q-item-section no-wrap>
663
- {{ props.row.name }}
664
- </q-item-section>
665
- <q-item-section avatar>
666
- <q-checkbox v-model="selected" :val="props.row" :color="$light.color" />
667
- </q-item-section>
668
- </q-item>
669
-
670
- <q-img v-if="props.row.canPreview" :src="props.row.imagePath"></q-img>
671
-
672
- </q-card>
673
- </div>
674
- </template>
675
- </q-table>
676
- </template>
677
- <template v-else>
678
-
679
- <q-table flat :columns="columns" :rows="items" @row-dblclick="onDblclickRow" @row-click="onClickRow"
680
- :pagination="pagination" row-key="path" selection="multiple" v-model:selected="selected" dense
681
- :loading="loading" :loading-label="$t('Loading...')" :no-data-label="$t('No data available')">
682
- <template #body-cell-icon="props">
683
- <q-td auto-width>
684
- <q-icon name="sym_o_folder" v-if="props.value == 'folder'" size="sm" />
685
- <q-icon name="sym_o_description" v-else size="sm" />
686
- </q-td>
687
- </template>
688
-
689
- <template #body-cell-action="props">
690
- <q-td auto-width>
691
- <q-btn flat icon="sym_o_more_vert" round dense>
692
- <q-menu>
693
- <q-list>
694
- <q-item clickable v-close-popup @click="onDeleteRow(props.row)" v-if="canDeleteRow(props.row)">
695
- <q-item-section avatar>
696
- <q-icon name="sym_o_delete"></q-icon>
697
- </q-item-section>
698
- <q-item-section>{{ $t('Delete') }}</q-item-section>
699
- </q-item>
700
-
701
- <q-item v-if="props.row.type == 'file'" clickable v-close-popup @click="onDownloadRow(props.row)">
702
- <q-item-section avatar>
703
- <q-icon name="sym_o_download"></q-icon>
704
- </q-item-section>
705
- <q-item-section>{{ $t('Download') }}</q-item-section>
706
- </q-item>
707
-
708
- <q-item clickable v-close-popup @click="onRenameRow(props.row)" v-if="canRenameRow(props.row)">
709
- <q-item-section avatar>
710
- <q-icon name="sym_o_edit"></q-icon>
711
- </q-item-section>
712
- <q-item-section>{{ $t('Rename') }}</q-item-section>
713
- </q-item>
714
-
715
- <q-item clickable v-close-popup @click="onPreview(props.row)" v-if="props.row.canPreview">
716
- <q-item-section avatar>
717
- <q-icon name="sym_o_preview"></q-icon>
718
- </q-item-section>
719
- <q-item-section>{{ $t('Preview') }}</q-item-section>
720
- </q-item>
721
-
722
- <q-item clickable v-close-popup v-if="props.row.mime == 'application/pdf'"
723
- @click="onPreviewPDF(props.row)">
724
- <q-item-section avatar>
725
- <q-icon name="sym_o_preview"></q-icon>
726
- </q-item-section>
727
- <q-item-section>{{ $t('Preview') }}</q-item-section>
728
- </q-item>
729
-
730
- <q-item clickable v-close-popup @click="onClickInfo(props.row)" class="lt-lg">
731
- <q-item-section avatar>
732
- <q-icon name="sym_o_info"></q-icon>
733
- </q-item-section>
734
- <q-item-section>{{ $t('Info') }}</q-item-section>
735
- </q-item>
736
-
737
- </q-list>
738
- </q-menu>
739
- </q-btn>
740
- </q-td>
741
- </template>
742
- </q-table>
743
- </template>
744
- </q-page-container>
745
- </q-layout>
746
- </template>
747
-
748
- <style>
749
- .q-item--active{background-color:#e0e0e0}
750
- </style>