@cloudron/pankow 4.1.9 → 4.1.10
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/components/DirectoryView.vue +742 -624
- package/components/DirectoryViewGridItem.vue +167 -142
- package/components/DirectoryViewListItem.vue +178 -159
- package/components/FileUploader.vue +161 -150
- package/package.json +2 -2
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
@contextmenu="onContextMenuBody($event)"
|
|
48
48
|
>
|
|
49
49
|
<template v-if="viewMode === 'list'">
|
|
50
|
-
<DirectoryViewListItem v-for="item in filteredSortedItems" :ref="(el) =>
|
|
50
|
+
<DirectoryViewListItem v-for="item in filteredSortedItems" :ref="(el) => setItemRef(item.id, el)"
|
|
51
51
|
:key="item.id"
|
|
52
52
|
:fallbackIcon="fallbackIcon"
|
|
53
53
|
:item="item"
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
/>
|
|
71
71
|
</template>
|
|
72
72
|
<template v-else-if="viewMode === 'grid'">
|
|
73
|
-
<DirectoryViewGridItem v-for="item in filteredSortedItems" :ref="(el) =>
|
|
73
|
+
<DirectoryViewGridItem v-for="item in filteredSortedItems" :ref="(el) => setItemRef(item.id, el)"
|
|
74
74
|
:key="item.id"
|
|
75
75
|
:fallbackIcon="fallbackIcon"
|
|
76
76
|
:item="item"
|
|
@@ -95,7 +95,9 @@
|
|
|
95
95
|
</div>
|
|
96
96
|
</template>
|
|
97
97
|
|
|
98
|
-
<script>
|
|
98
|
+
<script setup>
|
|
99
|
+
|
|
100
|
+
import { ref, reactive, computed, useTemplateRef } from 'vue';
|
|
99
101
|
|
|
100
102
|
import SingleSelect from './SingleSelect.vue';
|
|
101
103
|
import DirectoryViewListItem from './DirectoryViewListItem.vue';
|
|
@@ -106,10 +108,10 @@ import Menu from './Menu.vue';
|
|
|
106
108
|
import { translation } from '../utils.js';
|
|
107
109
|
|
|
108
110
|
const SORT_BY = {
|
|
109
|
-
NAME:
|
|
111
|
+
NAME: 'name',
|
|
110
112
|
OWNER: 'owner',
|
|
111
|
-
STAR:
|
|
112
|
-
SIZE:
|
|
113
|
+
STAR: 'star',
|
|
114
|
+
SIZE: 'size',
|
|
113
115
|
MTIME: 'mtime',
|
|
114
116
|
};
|
|
115
117
|
|
|
@@ -120,675 +122,791 @@ const SORT_DIRECTION = {
|
|
|
120
122
|
|
|
121
123
|
let rawSelected = [];
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
SingleSelect,
|
|
130
|
-
Menu
|
|
131
|
-
},
|
|
132
|
-
emits: [ 'selectionChanged', 'item-activated' ],
|
|
133
|
-
props: {
|
|
134
|
-
viewMode: {
|
|
135
|
-
type: String,
|
|
136
|
-
default: 'list',
|
|
137
|
-
validator(value) {
|
|
138
|
-
return ['list', 'grid'].includes(value);
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
busy: {
|
|
142
|
-
type: Boolean,
|
|
143
|
-
default: false
|
|
144
|
-
},
|
|
145
|
-
showOwner: {
|
|
146
|
-
type: Boolean,
|
|
147
|
-
default: false
|
|
148
|
-
},
|
|
149
|
-
showSize: {
|
|
150
|
-
type: Boolean,
|
|
151
|
-
default: false
|
|
152
|
-
},
|
|
153
|
-
showStar: {
|
|
154
|
-
type: Boolean,
|
|
155
|
-
default: false
|
|
156
|
-
},
|
|
157
|
-
showExtract: {
|
|
158
|
-
type: Boolean,
|
|
159
|
-
default: true
|
|
160
|
-
},
|
|
161
|
-
showNewFile: {
|
|
162
|
-
type: Boolean,
|
|
163
|
-
default: true
|
|
164
|
-
},
|
|
165
|
-
showNewFolder: {
|
|
166
|
-
type: Boolean,
|
|
167
|
-
default: true
|
|
168
|
-
},
|
|
169
|
-
showCut: {
|
|
170
|
-
type: Boolean,
|
|
171
|
-
default: true
|
|
172
|
-
},
|
|
173
|
-
showCopy: {
|
|
174
|
-
type: Boolean,
|
|
175
|
-
default: true
|
|
176
|
-
},
|
|
177
|
-
showPaste: {
|
|
178
|
-
type: Boolean,
|
|
179
|
-
default: true
|
|
180
|
-
},
|
|
181
|
-
showRename: {
|
|
182
|
-
type: Boolean,
|
|
183
|
-
default: true
|
|
184
|
-
},
|
|
185
|
-
showSelectAll: {
|
|
186
|
-
type: Boolean,
|
|
187
|
-
default: true
|
|
188
|
-
},
|
|
189
|
-
showDownload: {
|
|
190
|
-
type: Boolean,
|
|
191
|
-
default: true
|
|
125
|
+
const props = defineProps({
|
|
126
|
+
viewMode: {
|
|
127
|
+
type: String,
|
|
128
|
+
default: 'list',
|
|
129
|
+
validator(value) {
|
|
130
|
+
return ['list', 'grid'].includes(value);
|
|
192
131
|
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
132
|
+
},
|
|
133
|
+
busy: {
|
|
134
|
+
type: Boolean,
|
|
135
|
+
default: false,
|
|
136
|
+
},
|
|
137
|
+
showOwner: {
|
|
138
|
+
type: Boolean,
|
|
139
|
+
default: false,
|
|
140
|
+
},
|
|
141
|
+
showSize: {
|
|
142
|
+
type: Boolean,
|
|
143
|
+
default: false,
|
|
144
|
+
},
|
|
145
|
+
showStar: {
|
|
146
|
+
type: Boolean,
|
|
147
|
+
default: false,
|
|
148
|
+
},
|
|
149
|
+
showExtract: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
default: true,
|
|
152
|
+
},
|
|
153
|
+
showNewFile: {
|
|
154
|
+
type: Boolean,
|
|
155
|
+
default: true,
|
|
156
|
+
},
|
|
157
|
+
showNewFolder: {
|
|
158
|
+
type: Boolean,
|
|
159
|
+
default: true,
|
|
160
|
+
},
|
|
161
|
+
showCut: {
|
|
162
|
+
type: Boolean,
|
|
163
|
+
default: true,
|
|
164
|
+
},
|
|
165
|
+
showCopy: {
|
|
166
|
+
type: Boolean,
|
|
167
|
+
default: true,
|
|
168
|
+
},
|
|
169
|
+
showPaste: {
|
|
170
|
+
type: Boolean,
|
|
171
|
+
default: true,
|
|
172
|
+
},
|
|
173
|
+
showRename: {
|
|
174
|
+
type: Boolean,
|
|
175
|
+
default: true,
|
|
176
|
+
},
|
|
177
|
+
showSelectAll: {
|
|
178
|
+
type: Boolean,
|
|
179
|
+
default: true,
|
|
180
|
+
},
|
|
181
|
+
showDownload: {
|
|
182
|
+
type: Boolean,
|
|
183
|
+
default: true,
|
|
184
|
+
},
|
|
185
|
+
showDelete: {
|
|
186
|
+
type: Boolean,
|
|
187
|
+
default: true,
|
|
188
|
+
},
|
|
189
|
+
showShare: {
|
|
190
|
+
type: Boolean,
|
|
191
|
+
default: false,
|
|
192
|
+
},
|
|
193
|
+
showModified: {
|
|
194
|
+
type: Boolean,
|
|
195
|
+
default: false,
|
|
196
|
+
},
|
|
197
|
+
shareIndicatorProperty: {
|
|
198
|
+
type: String,
|
|
199
|
+
default: '',
|
|
200
|
+
},
|
|
201
|
+
editable: {
|
|
202
|
+
type: Boolean,
|
|
203
|
+
default: true,
|
|
204
|
+
},
|
|
205
|
+
multiDownload: {
|
|
206
|
+
type: Boolean,
|
|
207
|
+
default: false,
|
|
208
|
+
},
|
|
209
|
+
tr: {
|
|
210
|
+
type: Function,
|
|
211
|
+
default(id) {
|
|
212
|
+
console.warn('Missing tr for DirectoryView, using fallback.');
|
|
213
|
+
return translation(id);
|
|
221
214
|
},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
for (const item of value) {
|
|
231
|
-
if (typeof item.id !== 'string') return fail('id', 'string', item);
|
|
232
|
-
if (typeof item.name !== 'string') return fail('name', 'string', item);
|
|
233
|
-
if (typeof item.icon !== 'string') return fail('icon', 'string', item);
|
|
234
|
-
if (typeof item.previewUrl !== 'string') return fail('previewUrl', 'string', item);
|
|
235
|
-
|
|
236
|
-
// optional
|
|
237
|
-
if (item.href && typeof item.href !== 'string') return fail('href', 'string', item);
|
|
238
|
-
if (item.target && typeof item.target !== 'string') return fail('target', 'string', item);
|
|
239
|
-
if (item.owner && typeof item.owner !== 'string') return fail('owner', 'string', item);
|
|
240
|
-
if (item.size && typeof item.size !== 'number') return fail('size', 'number', item);
|
|
241
|
-
if (item.star && typeof item.star !== 'boolean') return fail('star', 'boolean', item);
|
|
242
|
-
// if (item.modified && item.modified instanceof Date) return fail('modified', 'Date', item);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return true;
|
|
215
|
+
},
|
|
216
|
+
items: {
|
|
217
|
+
type: Array,
|
|
218
|
+
validator(value) {
|
|
219
|
+
function fail(prop, type, item) {
|
|
220
|
+
console.error(`DirectoryView.items[].${prop} must be a ${type}`, item);
|
|
221
|
+
return false;
|
|
246
222
|
}
|
|
223
|
+
|
|
224
|
+
for (const item of value) {
|
|
225
|
+
if (typeof item.id !== 'string') return fail('id', 'string', item);
|
|
226
|
+
if (typeof item.name !== 'string') return fail('name', 'string', item);
|
|
227
|
+
if (typeof item.icon !== 'string') return fail('icon', 'string', item);
|
|
228
|
+
if (typeof item.previewUrl !== 'string') return fail('previewUrl', 'string', item);
|
|
229
|
+
|
|
230
|
+
if (item.href && typeof item.href !== 'string') return fail('href', 'string', item);
|
|
231
|
+
if (item.target && typeof item.target !== 'string') return fail('target', 'string', item);
|
|
232
|
+
if (item.owner && typeof item.owner !== 'string') return fail('owner', 'string', item);
|
|
233
|
+
if (item.size && typeof item.size !== 'number') return fail('size', 'number', item);
|
|
234
|
+
if (item.star && typeof item.star !== 'boolean') return fail('star', 'boolean', item);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return true;
|
|
247
238
|
},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
239
|
+
},
|
|
240
|
+
ownersModel: {
|
|
241
|
+
type: Array,
|
|
242
|
+
default: () => [],
|
|
243
|
+
},
|
|
244
|
+
fallbackIcon: String,
|
|
245
|
+
showUploadFile: {
|
|
246
|
+
type: Boolean,
|
|
247
|
+
default: false,
|
|
248
|
+
},
|
|
249
|
+
showUploadFolder: {
|
|
250
|
+
type: Boolean,
|
|
251
|
+
default: false,
|
|
252
|
+
},
|
|
253
|
+
deleteHandler: {
|
|
254
|
+
type: Function,
|
|
255
|
+
default() {
|
|
256
|
+
console.warn('Missing deleteHandler for DirectoryView');
|
|
256
257
|
},
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
},
|
|
259
|
+
renameHandler: {
|
|
260
|
+
type: Function,
|
|
261
|
+
default() {
|
|
262
|
+
console.warn('Missing renameHandler for DirectoryView');
|
|
260
263
|
},
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
},
|
|
265
|
+
changeOwnerHandler: {
|
|
266
|
+
type: Function,
|
|
267
|
+
default() {
|
|
268
|
+
console.warn('Missing changeOwnerHandler for DirectoryView');
|
|
264
269
|
},
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
},
|
|
271
|
+
pasteHandler: {
|
|
272
|
+
type: Function,
|
|
273
|
+
default() {
|
|
274
|
+
console.warn('Missing pasteHandler for DirectoryView');
|
|
268
275
|
},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
},
|
|
277
|
+
newFileHandler: {
|
|
278
|
+
type: Function,
|
|
279
|
+
default() {
|
|
280
|
+
console.warn('Missing newFileHandler for DirectoryView');
|
|
272
281
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
282
|
+
},
|
|
283
|
+
newFolderHandler: {
|
|
284
|
+
type: Function,
|
|
285
|
+
default() {
|
|
286
|
+
console.warn('Missing newFolderHandler for DirectoryView');
|
|
276
287
|
},
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
},
|
|
289
|
+
uploadFileHandler: {
|
|
290
|
+
type: Function,
|
|
291
|
+
default() {
|
|
292
|
+
console.warn('Missing uploadFileHandler for DirectoryView');
|
|
280
293
|
},
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
},
|
|
295
|
+
uploadFolderHandler: {
|
|
296
|
+
type: Function,
|
|
297
|
+
default() {
|
|
298
|
+
console.warn('Missing uploadFolderHandler for DirectoryView');
|
|
284
299
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
300
|
+
},
|
|
301
|
+
shareHandler: {
|
|
302
|
+
type: Function,
|
|
303
|
+
default() {
|
|
304
|
+
console.warn('Missing shareHandler for DirectoryView');
|
|
288
305
|
},
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
},
|
|
307
|
+
starHandler: {
|
|
308
|
+
type: Function,
|
|
309
|
+
default() {
|
|
310
|
+
console.warn('Missing starHandler for DirectoryView');
|
|
292
311
|
},
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
},
|
|
313
|
+
dropHandler: {
|
|
314
|
+
type: Function,
|
|
315
|
+
default() {
|
|
316
|
+
console.warn('Missing dropHandler for DirectoryView');
|
|
296
317
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
318
|
+
},
|
|
319
|
+
downloadHandler: {
|
|
320
|
+
type: Function,
|
|
321
|
+
default() {
|
|
322
|
+
console.warn('Missing downloadHandler for DirectoryView');
|
|
300
323
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
},
|
|
325
|
+
extractHandler: {
|
|
326
|
+
type: Function,
|
|
327
|
+
default() {
|
|
328
|
+
console.warn('Missing extractHandler for DirectoryView');
|
|
304
329
|
},
|
|
305
|
-
refreshHandler: {
|
|
306
|
-
type: Function,
|
|
307
|
-
default: null
|
|
308
|
-
}
|
|
309
330
|
},
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// first sort by sort column and direction, then make sure folders are always first
|
|
315
|
-
return this.items.sort((a, b) => {
|
|
316
|
-
if (a.isDirectory && !b.isDirectory) return -1;
|
|
317
|
-
if (!a.isDirectory && b.isDirectory) return 1;
|
|
318
|
-
|
|
319
|
-
let sort = 0;
|
|
320
|
-
if (this.sortBy === SORT_BY.NAME) {
|
|
321
|
-
if (a.name.toLowerCase() < b.name.toLowerCase()) sort = -1;
|
|
322
|
-
else if (a.name.toLowerCase() > b.name.toLowerCase()) sort = 1;
|
|
323
|
-
} else if (this.sortBy === SORT_BY.OWNER) {
|
|
324
|
-
if (a.owner.toLowerCase() < b.owner.toLowerCase()) sort = -1;
|
|
325
|
-
else if (a.owner.toLowerCase() > b.owner.toLowerCase()) sort = 1;
|
|
326
|
-
} else if (this.sortBy === SORT_BY.STAR) {
|
|
327
|
-
if (a.star && !b.star) sort = -1;
|
|
328
|
-
if (!a.star && b.star) sort = 1;
|
|
329
|
-
} else if (this.sortBy === SORT_BY.SIZE) {
|
|
330
|
-
if (a.size < b.size) sort = -1;
|
|
331
|
-
if (a.size > b.size) sort = 1;
|
|
332
|
-
} else if (this.sortBy === SORT_BY.MTIME) {
|
|
333
|
-
if (a.mtime < b.mtime) sort = -1;
|
|
334
|
-
if (a.mtime > b.mtime) sort = 1;
|
|
335
|
-
} else {
|
|
336
|
-
console.error('unknown SORT_BY', this.sortBy);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// tie break by id to avoid recursion
|
|
340
|
-
if (sort === 0) {
|
|
341
|
-
if (a.id < b.id) sort = -1;
|
|
342
|
-
else sort = 1;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return sort *= (this.sortDirection === SORT_DIRECTION.DESC ? -1 : 1);
|
|
346
|
-
});
|
|
347
|
-
}
|
|
331
|
+
refreshHandler: {
|
|
332
|
+
type: Function,
|
|
333
|
+
default: null,
|
|
348
334
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
label: this.tr('filemanager.toolbar.refresh'),
|
|
384
|
-
icon:'fa-solid fa-arrow-rotate-right',
|
|
385
|
-
visible: () => { return typeof this.refreshHandler === 'function'; },
|
|
386
|
-
action: this.refreshHandler,
|
|
387
|
-
}, {
|
|
388
|
-
separator:true,
|
|
389
|
-
visible: () => { return this.showNewFile || this.showNewFolder; },
|
|
390
|
-
}, {
|
|
391
|
-
label: this.tr('filemanager.toolbar.newFile'),
|
|
392
|
-
icon:'fa-solid fa-file-circle-plus',
|
|
393
|
-
action: this.newFileHandler,
|
|
394
|
-
visible: () => { return this.showNewFile; },
|
|
395
|
-
}, {
|
|
396
|
-
label: this.tr('filemanager.toolbar.newFolder'),
|
|
397
|
-
icon:'fa-solid fa-folder-plus',
|
|
398
|
-
action: this.newFolderHandler,
|
|
399
|
-
visible: () => { return this.showNewFolder; },
|
|
400
|
-
}, {
|
|
401
|
-
separator:true,
|
|
402
|
-
visible: () => { return this.showUploadFile || this.showUploadFolder; },
|
|
403
|
-
}, {
|
|
404
|
-
label: this.tr('filemanager.toolbar.uploadFile'),
|
|
405
|
-
icon:'fa-solid fa-file-arrow-up',
|
|
406
|
-
action: this.uploadFileHandler,
|
|
407
|
-
visible: () => { return this.showUploadFile; },
|
|
408
|
-
}, {
|
|
409
|
-
label: this.tr('filemanager.toolbar.uploadFolder'),
|
|
410
|
-
icon:'fa-regular fa-folder-open',
|
|
411
|
-
action: this.uploadFolderHandler,
|
|
412
|
-
visible: () => { return this.showUploadFolder; },
|
|
413
|
-
}],
|
|
414
|
-
contextMenuModel: [{
|
|
415
|
-
label: this.tr('filemanager.list.menu.open'),
|
|
416
|
-
icon:'fa-regular fa-folder-open',
|
|
417
|
-
disabled: () => { return this.selectedCount > 1; },
|
|
418
|
-
action: () => { this.onItemActivated(this.getSelected()[0]); }
|
|
419
|
-
}, {
|
|
420
|
-
separator:true,
|
|
421
|
-
visible: () => { return this.showDownload || this.showShare || this.showCopy || this.showCut || this.showPaste || this.showSelectAll; },
|
|
422
|
-
}, {
|
|
423
|
-
label: this.tr('filemanager.list.menu.download'),
|
|
424
|
-
icon:'fa-solid fa-download',
|
|
425
|
-
action: this.onItemDownload,
|
|
426
|
-
visible: () => { return this.showDownload; },
|
|
427
|
-
disabled: () => { return this.multiDownload ? this.selectedCount === 0 : this.selectedCount !== 1; }
|
|
428
|
-
}, {
|
|
429
|
-
label: this.tr('filemanager.list.menu.share'),
|
|
430
|
-
icon:'fa-solid fa-share-nodes',
|
|
431
|
-
action: this.onItemShare,
|
|
432
|
-
disabled: () => { return this.selectedCount > 1; },
|
|
433
|
-
visible: () => { return this.showShare; },
|
|
434
|
-
}, {
|
|
435
|
-
label: this.tr('filemanager.list.menu.copy'),
|
|
436
|
-
icon:'fa-regular fa-copy',
|
|
437
|
-
action: this.onItemsCopy,
|
|
438
|
-
visible: () => { return this.showCopy; },
|
|
439
|
-
}, {
|
|
440
|
-
label: this.tr('filemanager.list.menu.cut'),
|
|
441
|
-
icon:'fa-solid fa-scissors',
|
|
442
|
-
action: this.onItemsCut,
|
|
443
|
-
visible: () => { return this.showCut; },
|
|
444
|
-
}, {
|
|
445
|
-
label: this.tr('filemanager.list.menu.paste'),
|
|
446
|
-
icon:'fa-regular fa-paste',
|
|
447
|
-
visible: () => { return this.showPaste; },
|
|
448
|
-
disabled: () => { return !(this.focusItem && this.focusItem.isDirectory) || this.selectedCount > 1 || !this.clipboard.files || !this.clipboard.files.length; },
|
|
449
|
-
action: () => { this.pasteHandler(this.clipboard.action, this.clipboard.files, this.focusItem); }
|
|
450
|
-
}, {
|
|
451
|
-
label: this.tr('filemanager.list.menu.selectAll'),
|
|
452
|
-
icon:'fa-solid fa-check-double',
|
|
453
|
-
visible: () => { return this.showSelectAll; },
|
|
454
|
-
action: this.onSelectAll
|
|
455
|
-
}, {
|
|
456
|
-
label: this.tr('filemanager.list.menu.rename'),
|
|
457
|
-
icon:'fa-regular fa-pen-to-square',
|
|
458
|
-
action: this.onItemRenameBegin,
|
|
459
|
-
visible: () => { return this.showRename; },
|
|
460
|
-
disabled: () => { return !this.editable || this.selectedCount > 1; }
|
|
461
|
-
}, {
|
|
462
|
-
label: this.tr('filemanager.list.menu.chown'),
|
|
463
|
-
icon:'fa-solid fa-user-pen',
|
|
464
|
-
action: this.onItemsChangeOwner,
|
|
465
|
-
visible: () => { return this.showOwner; },
|
|
466
|
-
}, {
|
|
467
|
-
label: this.tr('filemanager.list.menu.extract'),
|
|
468
|
-
action: this.onItemExtract,
|
|
469
|
-
visible: () => {
|
|
470
|
-
if (!this.showExtract) return false;
|
|
471
|
-
|
|
472
|
-
const file = this.getSelected()[0];
|
|
473
|
-
return !(this.selectedCount !== 1 || !(
|
|
474
|
-
file.name.match(/\.tgz$/) ||
|
|
475
|
-
file.name.match(/\.tar$/) ||
|
|
476
|
-
file.name.match(/\.7z$/) ||
|
|
477
|
-
file.name.match(/\.zip$/) ||
|
|
478
|
-
file.name.match(/\.tar\.gz$/) ||
|
|
479
|
-
file.name.match(/\.tar\.xz$/) ||
|
|
480
|
-
file.name.match(/\.tar\.bz2$/)));
|
|
481
|
-
}
|
|
482
|
-
}, {
|
|
483
|
-
separator:true,
|
|
484
|
-
visible: () => { return this.showDelete; },
|
|
485
|
-
}, {
|
|
486
|
-
label: this.tr('filemanager.list.menu.delete'),
|
|
487
|
-
icon:'fa-regular fa-trash-can',
|
|
488
|
-
action: () => { this.deleteHandler(this.getSelected()); },
|
|
489
|
-
visible: () => { return this.showDelete; },
|
|
490
|
-
}],
|
|
491
|
-
};
|
|
492
|
-
},
|
|
493
|
-
methods: {
|
|
494
|
-
highlight(item) {
|
|
495
|
-
const id = item.id;
|
|
496
|
-
|
|
497
|
-
if (!this.elements[id]) return;
|
|
498
|
-
|
|
499
|
-
this.elements[id].highlight();
|
|
500
|
-
this.elements[id].$el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
501
|
-
},
|
|
502
|
-
highlightByName(name) {
|
|
503
|
-
const tmp = this.items.find((i) => i.name === name);
|
|
504
|
-
if (!tmp) return;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const emit = defineEmits(['selectionChanged', 'item-activated']);
|
|
338
|
+
|
|
339
|
+
const gridBody = useTemplateRef('gridBody');
|
|
340
|
+
const contextMenu = useTemplateRef('contextMenu');
|
|
341
|
+
const contextMenuBody = useTemplateRef('contextMenuBody');
|
|
342
|
+
const chownDialog = useTemplateRef('chownDialog');
|
|
343
|
+
|
|
344
|
+
const _alreadyActivating = ref(false);
|
|
345
|
+
const elements = reactive({});
|
|
346
|
+
const focusItem = ref(null);
|
|
347
|
+
const focusItemIndex = ref(-1);
|
|
348
|
+
const selectedCount = ref(0);
|
|
349
|
+
const dropTargetActive = ref(false);
|
|
350
|
+
const sortBy = ref(SORT_BY.NAME);
|
|
351
|
+
const sortDirection = ref(SORT_DIRECTION.ASC);
|
|
352
|
+
const clipboard = reactive({
|
|
353
|
+
action: '',
|
|
354
|
+
files: [],
|
|
355
|
+
});
|
|
356
|
+
const changeOwnerDialog = reactive({
|
|
357
|
+
busy: false,
|
|
358
|
+
ownerId: '',
|
|
359
|
+
items: [],
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
function setItemRef(id, el) {
|
|
363
|
+
if (el) {
|
|
364
|
+
elements[id] = el;
|
|
365
|
+
} else {
|
|
366
|
+
delete elements[id];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
505
369
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (this.sortBy === sortBy) this.sortDirection = this.sortDirection === SORT_DIRECTION.ASC ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
|
|
510
|
-
else this.sortBy = sortBy;
|
|
511
|
-
},
|
|
512
|
-
onDrop(targetItem, event) {
|
|
513
|
-
this.dropTargetActive = false;
|
|
370
|
+
function scrollItemIntoView(componentRef) {
|
|
371
|
+
componentRef?.$el?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
|
|
372
|
+
}
|
|
514
373
|
|
|
515
|
-
|
|
374
|
+
function getSelected() {
|
|
375
|
+
return rawSelected;
|
|
376
|
+
}
|
|
516
377
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
378
|
+
function invalidateSelected() {
|
|
379
|
+
rawSelected = props.items.filter((item) => item.selected).slice();
|
|
380
|
+
selectedCount.value = rawSelected.length;
|
|
381
|
+
emit('selectionChanged', rawSelected);
|
|
382
|
+
}
|
|
520
383
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
this.dropHandler(targetItemName, event.dataTransfer, null);
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
onCanDropHandler(item) {
|
|
528
|
-
if (!item.isDirectory) return false;
|
|
529
|
-
return !item.selected;
|
|
530
|
-
},
|
|
531
|
-
onDragOver(event) {
|
|
532
|
-
if (!event.dataTransfer) return;
|
|
533
|
-
|
|
534
|
-
if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
|
|
535
|
-
this.dropTargetActive = false;
|
|
536
|
-
} else {
|
|
537
|
-
event.dataTransfer.dropEffect = 'copy';
|
|
538
|
-
this.dropTargetActive = true;
|
|
539
|
-
}
|
|
540
|
-
},
|
|
541
|
-
onDragExit(event) {
|
|
542
|
-
this.dropTargetActive = false;
|
|
543
|
-
},
|
|
544
|
-
onItemDragStart(event, item) {
|
|
545
|
-
// auto-select the dragged item if it is not already part of the selection
|
|
546
|
-
if (item && !item.selected) {
|
|
547
|
-
this.onSelectAndFocus(item, {}, false);
|
|
548
|
-
}
|
|
384
|
+
function highlight(item) {
|
|
385
|
+
const id = item.id;
|
|
386
|
+
if (!elements[id]) return;
|
|
549
387
|
|
|
550
|
-
|
|
388
|
+
elements[id].highlight();
|
|
389
|
+
scrollItemIntoView(elements[id]);
|
|
390
|
+
}
|
|
551
391
|
|
|
552
|
-
|
|
553
|
-
|
|
392
|
+
function highlightByName(name) {
|
|
393
|
+
const tmp = props.items.find((i) => i.name === name);
|
|
394
|
+
if (!tmp) return;
|
|
554
395
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
dragHandleItem.style.opacity = 1-((i*2)/10);
|
|
558
|
-
const dragHandleItemIcon = document.createElement('img');
|
|
559
|
-
dragHandleItemIcon.src = rawSelected[i].previewUrl || rawSelected[i].icon
|
|
560
|
-
dragHandleItem.append(dragHandleItemIcon, rawSelected[i].name);
|
|
561
|
-
dragHandle.append(dragHandleItem);
|
|
562
|
-
}
|
|
396
|
+
highlight(tmp);
|
|
397
|
+
}
|
|
563
398
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const firstRowTop = el.children[0].getBoundingClientRect().top;
|
|
572
|
-
const tolerance = 2;
|
|
573
|
-
let cols = 0;
|
|
574
|
-
for (const child of el.children) {
|
|
575
|
-
if (Math.abs(child.getBoundingClientRect().top - firstRowTop) <= tolerance) cols++;
|
|
576
|
-
else break;
|
|
577
|
-
}
|
|
578
|
-
return cols || 1;
|
|
579
|
-
},
|
|
580
|
-
onKeyUp(event) {
|
|
581
|
-
const step = this.viewMode === 'grid' ? this.getGridColumns() : 1;
|
|
582
|
-
this.onSelectAndFocusIndex(this.focusItemIndex - step, event.shiftKey);
|
|
583
|
-
},
|
|
584
|
-
onKeyDown(event) {
|
|
585
|
-
const step = this.viewMode === 'grid' ? this.getGridColumns() : 1;
|
|
586
|
-
this.onSelectAndFocusIndex(this.focusItemIndex + step, event.shiftKey);
|
|
587
|
-
},
|
|
588
|
-
onKeyLeft(event) {
|
|
589
|
-
if (this.viewMode !== 'grid') return;
|
|
590
|
-
this.onSelectAndFocusIndex(this.focusItemIndex - 1, event.shiftKey);
|
|
591
|
-
},
|
|
592
|
-
onKeyRight(event) {
|
|
593
|
-
if (this.viewMode !== 'grid') return;
|
|
594
|
-
this.onSelectAndFocusIndex(this.focusItemIndex + 1, event.shiftKey);
|
|
595
|
-
},
|
|
596
|
-
onKeyEnter(event) {
|
|
597
|
-
if (this.focusItem) this.onItemActivated(this.focusItem);
|
|
598
|
-
},
|
|
599
|
-
async onKeyDelete(event) {
|
|
600
|
-
if (event.key !== 'Delete') return; // triggered also by backspace
|
|
601
|
-
if (!this.focusItem) return;
|
|
399
|
+
function toggleSort(by) {
|
|
400
|
+
if (sortBy.value === by) {
|
|
401
|
+
sortDirection.value = sortDirection.value === SORT_DIRECTION.ASC ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
|
|
402
|
+
} else {
|
|
403
|
+
sortBy.value = by;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
602
406
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
onKeyEscape(event) {
|
|
606
|
-
this.onUnselectAll();
|
|
607
|
-
},
|
|
608
|
-
onKeyEvent(event) {
|
|
609
|
-
if (event.key === 'c' && (event.ctrlKey || event.metaKey)) {
|
|
610
|
-
this.onItemsCopy();
|
|
611
|
-
event.preventDefault();
|
|
612
|
-
} else if (event.key === 'v' && (event.ctrlKey || event.metaKey)) {
|
|
613
|
-
this.pasteHandler(this.clipboard.action, this.clipboard.files);
|
|
614
|
-
event.preventDefault();
|
|
615
|
-
} else if (event.key === 'x' && (event.ctrlKey || event.metaKey)) {
|
|
616
|
-
this.onItemsCut();
|
|
617
|
-
event.preventDefault();
|
|
618
|
-
} else if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {
|
|
619
|
-
this.onSelectAll();
|
|
620
|
-
event.preventDefault();
|
|
621
|
-
} else if (event.key === 'End') {
|
|
622
|
-
if (event.shiftKey) this.onSelectAndFocusToIndex(this.items.length-1);
|
|
623
|
-
else this.onSelectAndFocusIndex(this.items.length-1, event.shiftKey);
|
|
624
|
-
event.preventDefault();
|
|
625
|
-
} else if (event.key === 'Home') {
|
|
626
|
-
if (event.shiftKey) this.onSelectAndFocusToIndex(0);
|
|
627
|
-
else this.onSelectAndFocusIndex(0, event.shiftKey);
|
|
628
|
-
event.preventDefault();
|
|
629
|
-
} else if (event.key === 'PageUp') {
|
|
630
|
-
const pageItemCount = parseInt(event.target.clientHeight / 55);
|
|
631
|
-
if (event.shiftKey) this.onSelectAndFocusToIndex(this.focusItemIndex-pageItemCount);
|
|
632
|
-
else this.onSelectAndFocusIndex(this.focusItemIndex-pageItemCount, event.shiftKey);
|
|
633
|
-
event.preventDefault();
|
|
634
|
-
} else if (event.key === 'PageDown') {
|
|
635
|
-
const pageItemCount = parseInt(event.target.clientHeight / 55);
|
|
636
|
-
if (event.shiftKey) this.onSelectAndFocusToIndex(this.focusItemIndex+pageItemCount);
|
|
637
|
-
else this.onSelectAndFocusIndex(this.focusItemIndex+pageItemCount, event.shiftKey);
|
|
638
|
-
event.preventDefault();
|
|
639
|
-
}
|
|
640
|
-
},
|
|
641
|
-
onContextMenu(item, event) {
|
|
642
|
-
this.$refs.contextMenu.open(event);
|
|
643
|
-
this.onSelectAndFocus(item, event, false);
|
|
644
|
-
event.stopPropagation();
|
|
645
|
-
},
|
|
646
|
-
onContextMenuBody(event) {
|
|
647
|
-
this.$refs.contextMenuBody.open(event);
|
|
648
|
-
this.onUnselectAll();
|
|
649
|
-
},
|
|
650
|
-
onItemActivated(item) {
|
|
651
|
-
// prevent concurrent activations for some time
|
|
652
|
-
if (this._alreadyActivating) return;
|
|
653
|
-
this._alreadyActivating = true;
|
|
654
|
-
setTimeout(() => { this._alreadyActivating = false; }, 500);
|
|
407
|
+
function onDrop(targetItem, event) {
|
|
408
|
+
dropTargetActive.value = false;
|
|
655
409
|
|
|
656
|
-
|
|
657
|
-
},
|
|
658
|
-
onItemsChangeOwner(event) {
|
|
659
|
-
if (!this.focusItem) return console.warn('onItemsChangeOwner should only be triggered if at least one item is selected');
|
|
410
|
+
const targetItemName = targetItem ? targetItem.name : '';
|
|
660
411
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
this.$refs.chownDialog.open();
|
|
664
|
-
},
|
|
665
|
-
onItemDownload(event) {
|
|
666
|
-
if (!this.focusItem) return console.warn('onItemDownload should only be triggered if at lesst one item is selected');
|
|
412
|
+
if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
|
|
413
|
+
if (!targetItemName) return;
|
|
667
414
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
415
|
+
props.dropHandler(targetItemName, null, getSelected());
|
|
416
|
+
} else if (event.dataTransfer && event.dataTransfer.items[0]) {
|
|
417
|
+
props.dropHandler(targetItemName, event.dataTransfer, null);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
672
420
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
421
|
+
function onCanDropHandler(item) {
|
|
422
|
+
if (!item.isDirectory) return false;
|
|
423
|
+
return !item.selected;
|
|
424
|
+
}
|
|
677
425
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
},
|
|
681
|
-
onItemsCut(event) {
|
|
682
|
-
if (!this.focusItem) return console.warn('onItemsCut should only be triggered if at least one item is selected');
|
|
426
|
+
function onDragOver(event) {
|
|
427
|
+
if (!event.dataTransfer) return;
|
|
683
428
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
429
|
+
if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
|
|
430
|
+
dropTargetActive.value = false;
|
|
431
|
+
} else {
|
|
432
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
433
|
+
dropTargetActive.value = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
689
436
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (this.selectedCount !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
437
|
+
function onDragExit() {
|
|
438
|
+
dropTargetActive.value = false;
|
|
439
|
+
}
|
|
694
440
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
},
|
|
700
|
-
async onChangeOwnerDialogSubmit(items, newOwnerUid) {
|
|
701
|
-
this.changeOwnerDialog.busy = true;
|
|
702
|
-
await this.changeOwnerHandler(items, newOwnerUid);
|
|
703
|
-
this.changeOwnerDialog.visible = false;
|
|
704
|
-
this.changeOwnerDialog.items = [];
|
|
705
|
-
this.changeOwnerDialog.ownerId = this.ownersModel[0].uid;
|
|
706
|
-
this.changeOwnerDialog.busy = false;
|
|
707
|
-
this.$refs.chownDialog.close();
|
|
708
|
-
},
|
|
709
|
-
getSelected() {
|
|
710
|
-
return rawSelected;
|
|
711
|
-
},
|
|
712
|
-
invalidateSelected() {
|
|
713
|
-
rawSelected = this.items.filter((item) => item.selected).slice();
|
|
714
|
-
this.selectedCount = rawSelected.length;
|
|
715
|
-
this.$emit('selectionChanged', rawSelected);
|
|
716
|
-
},
|
|
717
|
-
onSelectAll() {
|
|
718
|
-
for (const item of this.items) item.selected = true;
|
|
719
|
-
if (!this.focusItem) this.setFocus(this.focusItemIndex);
|
|
441
|
+
function onItemDragStart(event, item) {
|
|
442
|
+
if (item && !item.selected) {
|
|
443
|
+
onSelectAndFocus(item, {}, false);
|
|
444
|
+
}
|
|
720
445
|
|
|
721
|
-
|
|
722
|
-
},
|
|
723
|
-
onUnselectAll() {
|
|
724
|
-
for (const item of this.items) item.selected = false;
|
|
725
|
-
if (this.focusItem) this.focusItem = null;
|
|
446
|
+
const dragHandle = document.getElementsByClassName('drag-handle')[0];
|
|
726
447
|
|
|
727
|
-
|
|
728
|
-
},
|
|
729
|
-
setFocus(index) {
|
|
730
|
-
if (this.items[this.focusItemIndex]) this.items[this.focusItemIndex].focused = false;
|
|
731
|
-
if (this.focusItem) this.focusItem.focused = false;
|
|
732
|
-
if (!this.items[index]) return;
|
|
733
|
-
|
|
734
|
-
this.focusItem = this.items[index];
|
|
735
|
-
this.focusItem.focused = true;
|
|
736
|
-
this.focusItemIndex = index;
|
|
737
|
-
},
|
|
738
|
-
onSelectAndFocusToIndex(index) {
|
|
739
|
-
index = (index < 0) ? 0 : (index > this.items.length-1 ? this.items.length-1 : index); // keep in bounds
|
|
448
|
+
dragHandle.replaceChildren();
|
|
740
449
|
|
|
741
|
-
|
|
450
|
+
for (let i = 0; i < Math.min(selectedCount.value, 6); ++i) {
|
|
451
|
+
const dragHandleItem = document.createElement('div');
|
|
452
|
+
dragHandleItem.style.opacity = 1 - ((i * 2) / 10);
|
|
453
|
+
const dragHandleItemIcon = document.createElement('img');
|
|
454
|
+
dragHandleItemIcon.src = rawSelected[i].previewUrl || rawSelected[i].icon;
|
|
455
|
+
dragHandleItem.append(dragHandleItemIcon, rawSelected[i].name);
|
|
456
|
+
dragHandle.append(dragHandleItem);
|
|
457
|
+
}
|
|
742
458
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
459
|
+
event.dataTransfer.dropEffect = 'move';
|
|
460
|
+
event.dataTransfer.setData('application/x-pankow', 'selected');
|
|
461
|
+
event.dataTransfer.setDragImage(dragHandle, 0, 0);
|
|
462
|
+
}
|
|
747
463
|
|
|
748
|
-
|
|
749
|
-
|
|
464
|
+
function getGridColumns() {
|
|
465
|
+
const el = gridBody.value;
|
|
466
|
+
if (props.viewMode !== 'grid' || !el || !el.children.length) return 1;
|
|
467
|
+
const firstRowTop = el.children[0].getBoundingClientRect().top;
|
|
468
|
+
const tolerance = 2;
|
|
469
|
+
let cols = 0;
|
|
470
|
+
for (const child of el.children) {
|
|
471
|
+
if (Math.abs(child.getBoundingClientRect().top - firstRowTop) <= tolerance) cols++;
|
|
472
|
+
else break;
|
|
473
|
+
}
|
|
474
|
+
return cols || 1;
|
|
475
|
+
}
|
|
750
476
|
|
|
751
|
-
|
|
477
|
+
function onKeyUp(event) {
|
|
478
|
+
const step = props.viewMode === 'grid' ? getGridColumns() : 1;
|
|
479
|
+
onSelectAndFocusIndex(focusItemIndex.value - step, event.shiftKey);
|
|
480
|
+
}
|
|
752
481
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
482
|
+
function onKeyDown(event) {
|
|
483
|
+
const step = props.viewMode === 'grid' ? getGridColumns() : 1;
|
|
484
|
+
onSelectAndFocusIndex(focusItemIndex.value + step, event.shiftKey);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function onKeyLeft(event) {
|
|
488
|
+
if (props.viewMode !== 'grid') return;
|
|
489
|
+
onSelectAndFocusIndex(focusItemIndex.value - 1, event.shiftKey);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function onKeyRight(event) {
|
|
493
|
+
if (props.viewMode !== 'grid') return;
|
|
494
|
+
onSelectAndFocusIndex(focusItemIndex.value + 1, event.shiftKey);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function onKeyEnter() {
|
|
498
|
+
if (focusItem.value) onItemActivated(focusItem.value);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function onKeyDelete(event) {
|
|
502
|
+
if (event.key !== 'Delete') return;
|
|
503
|
+
if (!focusItem.value) return;
|
|
504
|
+
|
|
505
|
+
await props.deleteHandler(getSelected());
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function onKeyEscape() {
|
|
509
|
+
onUnselectAll();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function onKeyEvent(event) {
|
|
513
|
+
if (event.key === 'c' && (event.ctrlKey || event.metaKey)) {
|
|
514
|
+
onItemsCopy();
|
|
515
|
+
event.preventDefault();
|
|
516
|
+
} else if (event.key === 'v' && (event.ctrlKey || event.metaKey)) {
|
|
517
|
+
props.pasteHandler(clipboard.action, clipboard.files);
|
|
518
|
+
event.preventDefault();
|
|
519
|
+
} else if (event.key === 'x' && (event.ctrlKey || event.metaKey)) {
|
|
520
|
+
onItemsCut();
|
|
521
|
+
event.preventDefault();
|
|
522
|
+
} else if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {
|
|
523
|
+
onSelectAll();
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
} else if (event.key === 'End') {
|
|
526
|
+
if (event.shiftKey) onSelectAndFocusToIndex(props.items.length - 1);
|
|
527
|
+
else onSelectAndFocusIndex(props.items.length - 1, event.shiftKey);
|
|
528
|
+
event.preventDefault();
|
|
529
|
+
} else if (event.key === 'Home') {
|
|
530
|
+
if (event.shiftKey) onSelectAndFocusToIndex(0);
|
|
531
|
+
else onSelectAndFocusIndex(0, event.shiftKey);
|
|
532
|
+
event.preventDefault();
|
|
533
|
+
} else if (event.key === 'PageUp') {
|
|
534
|
+
const pageItemCount = parseInt(event.target.clientHeight / 55, 10);
|
|
535
|
+
if (event.shiftKey) onSelectAndFocusToIndex(focusItemIndex.value - pageItemCount);
|
|
536
|
+
else onSelectAndFocusIndex(focusItemIndex.value - pageItemCount, event.shiftKey);
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
} else if (event.key === 'PageDown') {
|
|
539
|
+
const pageItemCount = parseInt(event.target.clientHeight / 55, 10);
|
|
540
|
+
if (event.shiftKey) onSelectAndFocusToIndex(focusItemIndex.value + pageItemCount);
|
|
541
|
+
else onSelectAndFocusIndex(focusItemIndex.value + pageItemCount, event.shiftKey);
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function onContextMenu(item, event) {
|
|
547
|
+
contextMenu.value?.open(event);
|
|
548
|
+
onSelectAndFocus(item, event, false);
|
|
549
|
+
event.stopPropagation();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function onContextMenuBody(event) {
|
|
553
|
+
contextMenuBody.value?.open(event);
|
|
554
|
+
onUnselectAll();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function onItemActivated(item) {
|
|
558
|
+
if (_alreadyActivating.value) return;
|
|
559
|
+
_alreadyActivating.value = true;
|
|
560
|
+
setTimeout(() => {
|
|
561
|
+
_alreadyActivating.value = false;
|
|
562
|
+
}, 500);
|
|
563
|
+
|
|
564
|
+
emit('item-activated', item);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function onItemsChangeOwner() {
|
|
568
|
+
if (!focusItem.value) return console.warn('onItemsChangeOwner should only be triggered if at least one item is selected');
|
|
569
|
+
|
|
570
|
+
changeOwnerDialog.items = getSelected();
|
|
571
|
+
changeOwnerDialog.ownerId = focusItem.value.uid;
|
|
572
|
+
chownDialog.value?.open();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function onItemDownload() {
|
|
576
|
+
if (!focusItem.value) return console.warn('onItemDownload should only be triggered if at lesst one item is selected');
|
|
577
|
+
|
|
578
|
+
props.downloadHandler(props.multiDownload ? getSelected() : focusItem.value);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function onItemExtract() {
|
|
582
|
+
if (selectedCount.value !== 1) return console.warn('onItemExtract should only be triggered if one item is selected');
|
|
583
|
+
|
|
584
|
+
props.extractHandler(focusItem.value);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function onItemsCopy() {
|
|
588
|
+
if (!focusItem.value) return console.warn('onItemsCopy should only be triggered if at least one item is selected');
|
|
589
|
+
|
|
590
|
+
clipboard.action = 'copy';
|
|
591
|
+
clipboard.files = getSelected();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function onItemsCut() {
|
|
595
|
+
if (!focusItem.value) return console.warn('onItemsCut should only be triggered if at least one item is selected');
|
|
596
|
+
|
|
597
|
+
clipboard.action = 'cut';
|
|
598
|
+
clipboard.files = getSelected();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function onItemShare() {
|
|
602
|
+
if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
603
|
+
|
|
604
|
+
props.shareHandler(focusItem.value);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function onItemRenameBegin() {
|
|
608
|
+
if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
609
|
+
|
|
610
|
+
elements[focusItem.value.id]?.onRenameBegin();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async function onChangeOwnerDialogSubmit(items, newOwnerUid) {
|
|
614
|
+
changeOwnerDialog.busy = true;
|
|
615
|
+
await props.changeOwnerHandler(items, newOwnerUid);
|
|
616
|
+
changeOwnerDialog.visible = false;
|
|
617
|
+
changeOwnerDialog.items = [];
|
|
618
|
+
changeOwnerDialog.ownerId = props.ownersModel[0].uid;
|
|
619
|
+
changeOwnerDialog.busy = false;
|
|
620
|
+
chownDialog.value?.close();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function onSelectAll() {
|
|
624
|
+
for (const item of props.items) item.selected = true;
|
|
625
|
+
if (!focusItem.value) setFocus(focusItemIndex.value);
|
|
626
|
+
|
|
627
|
+
invalidateSelected();
|
|
628
|
+
}
|
|
757
629
|
|
|
758
|
-
|
|
630
|
+
function onUnselectAll() {
|
|
631
|
+
for (const item of props.items) item.selected = false;
|
|
632
|
+
if (focusItem.value) focusItem.value = null;
|
|
759
633
|
|
|
760
|
-
|
|
634
|
+
invalidateSelected();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function setFocus(index) {
|
|
638
|
+
if (props.items[focusItemIndex.value]) props.items[focusItemIndex.value].focused = false;
|
|
639
|
+
if (focusItem.value) focusItem.value.focused = false;
|
|
640
|
+
if (!props.items[index]) return;
|
|
641
|
+
|
|
642
|
+
focusItem.value = props.items[index];
|
|
643
|
+
focusItem.value.focused = true;
|
|
644
|
+
focusItemIndex.value = index;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function onSelectAndFocusToIndex(index) {
|
|
648
|
+
index = index < 0 ? 0 : index > props.items.length - 1 ? props.items.length - 1 : index;
|
|
649
|
+
|
|
650
|
+
setFocus(index);
|
|
651
|
+
|
|
652
|
+
const item = props.items[index];
|
|
653
|
+
const firstSelectedIndex = props.items.findIndex((i) => i.selected);
|
|
654
|
+
const lastSelectedIndex = props.items.findLastIndex((i) => i.selected);
|
|
655
|
+
|
|
656
|
+
if (index < firstSelectedIndex) for (let i = index; i < firstSelectedIndex; ++i) props.items[i].selected = true;
|
|
657
|
+
else if (index > lastSelectedIndex) for (let i = lastSelectedIndex; i <= index; ++i) props.items[i].selected = true;
|
|
658
|
+
|
|
659
|
+
scrollItemIntoView(elements[item.id]);
|
|
660
|
+
|
|
661
|
+
invalidateSelected();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function onSelectAndFocusIndex(index, keepSelected = false) {
|
|
665
|
+
index = index < 0 ? 0 : index > props.items.length - 1 ? props.items.length - 1 : index;
|
|
666
|
+
|
|
667
|
+
const item = props.items[index];
|
|
668
|
+
|
|
669
|
+
onSelectAndFocus(item, {}, keepSelected);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function onSelectAndFocus(item, event, keepSelected = false) {
|
|
673
|
+
const itemIndex = props.items.findIndex((i) => i.id === item.id);
|
|
674
|
+
setFocus(itemIndex);
|
|
675
|
+
|
|
676
|
+
if (event.button === 2 && item.selected) keepSelected = true;
|
|
677
|
+
|
|
678
|
+
if (event.ctrlKey) {
|
|
679
|
+
if (keepSelected) item.selected = true;
|
|
680
|
+
else item.selected = !item.selected;
|
|
681
|
+
} else if (event.shiftKey) {
|
|
682
|
+
const firstSelectedIndex = props.items.findIndex((i) => i.selected);
|
|
683
|
+
const lastSelectedIndex = props.items.findLastIndex((i) => i.selected);
|
|
684
|
+
|
|
685
|
+
if (itemIndex < firstSelectedIndex) for (let i = itemIndex; i < firstSelectedIndex; ++i) props.items[i].selected = true;
|
|
686
|
+
else if (itemIndex > lastSelectedIndex) for (let i = lastSelectedIndex; i <= itemIndex; ++i) props.items[i].selected = true;
|
|
687
|
+
} else {
|
|
688
|
+
if (!keepSelected) for (const it of props.items) it.selected = false;
|
|
689
|
+
item.selected = true;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
scrollItemIntoView(elements[item.id]);
|
|
693
|
+
|
|
694
|
+
invalidateSelected();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const contextMenuBodyModel = [
|
|
698
|
+
{
|
|
699
|
+
label: props.tr('filemanager.list.menu.paste'),
|
|
700
|
+
icon: 'fa-regular fa-paste',
|
|
701
|
+
disabled: () => !clipboard.files || !clipboard.files.length,
|
|
702
|
+
action: () => {
|
|
703
|
+
props.pasteHandler(clipboard.action, clipboard.files, null);
|
|
761
704
|
},
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
label: props.tr('filemanager.list.menu.selectAll'),
|
|
708
|
+
icon: 'fa-solid fa-check-double',
|
|
709
|
+
action: onSelectAll,
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
separator: true,
|
|
713
|
+
visible: () => typeof props.refreshHandler === 'function',
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
label: props.tr('filemanager.toolbar.refresh'),
|
|
717
|
+
icon: 'fa-solid fa-arrow-rotate-right',
|
|
718
|
+
visible: () => typeof props.refreshHandler === 'function',
|
|
719
|
+
action: props.refreshHandler,
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
separator: true,
|
|
723
|
+
visible: () => props.showNewFile || props.showNewFolder,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
label: props.tr('filemanager.toolbar.newFile'),
|
|
727
|
+
icon: 'fa-solid fa-file-circle-plus',
|
|
728
|
+
action: props.newFileHandler,
|
|
729
|
+
visible: () => props.showNewFile,
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
label: props.tr('filemanager.toolbar.newFolder'),
|
|
733
|
+
icon: 'fa-solid fa-folder-plus',
|
|
734
|
+
action: props.newFolderHandler,
|
|
735
|
+
visible: () => props.showNewFolder,
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
separator: true,
|
|
739
|
+
visible: () => props.showUploadFile || props.showUploadFolder,
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
label: props.tr('filemanager.toolbar.uploadFile'),
|
|
743
|
+
icon: 'fa-solid fa-file-arrow-up',
|
|
744
|
+
action: props.uploadFileHandler,
|
|
745
|
+
visible: () => props.showUploadFile,
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
label: props.tr('filemanager.toolbar.uploadFolder'),
|
|
749
|
+
icon: 'fa-regular fa-folder-open',
|
|
750
|
+
action: props.uploadFolderHandler,
|
|
751
|
+
visible: () => props.showUploadFolder,
|
|
752
|
+
},
|
|
753
|
+
];
|
|
782
754
|
|
|
783
|
-
|
|
755
|
+
const contextMenuModel = [
|
|
756
|
+
{
|
|
757
|
+
label: props.tr('filemanager.list.menu.open'),
|
|
758
|
+
icon: 'fa-regular fa-folder-open',
|
|
759
|
+
disabled: () => selectedCount.value > 1,
|
|
760
|
+
action: () => {
|
|
761
|
+
onItemActivated(getSelected()[0]);
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
separator: true,
|
|
766
|
+
visible: () =>
|
|
767
|
+
props.showDownload ||
|
|
768
|
+
props.showShare ||
|
|
769
|
+
props.showCopy ||
|
|
770
|
+
props.showCut ||
|
|
771
|
+
props.showPaste ||
|
|
772
|
+
props.showSelectAll,
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
label: props.tr('filemanager.list.menu.download'),
|
|
776
|
+
icon: 'fa-solid fa-download',
|
|
777
|
+
action: onItemDownload,
|
|
778
|
+
visible: () => props.showDownload,
|
|
779
|
+
disabled: () => (props.multiDownload ? selectedCount.value === 0 : selectedCount.value !== 1),
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
label: props.tr('filemanager.list.menu.share'),
|
|
783
|
+
icon: 'fa-solid fa-share-nodes',
|
|
784
|
+
action: onItemShare,
|
|
785
|
+
disabled: () => selectedCount.value > 1,
|
|
786
|
+
visible: () => props.showShare,
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
label: props.tr('filemanager.list.menu.copy'),
|
|
790
|
+
icon: 'fa-regular fa-copy',
|
|
791
|
+
action: onItemsCopy,
|
|
792
|
+
visible: () => props.showCopy,
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
label: props.tr('filemanager.list.menu.cut'),
|
|
796
|
+
icon: 'fa-solid fa-scissors',
|
|
797
|
+
action: onItemsCut,
|
|
798
|
+
visible: () => props.showCut,
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
label: props.tr('filemanager.list.menu.paste'),
|
|
802
|
+
icon: 'fa-regular fa-paste',
|
|
803
|
+
visible: () => props.showPaste,
|
|
804
|
+
disabled: () =>
|
|
805
|
+
!(focusItem.value && focusItem.value.isDirectory) ||
|
|
806
|
+
selectedCount.value > 1 ||
|
|
807
|
+
!clipboard.files ||
|
|
808
|
+
!clipboard.files.length,
|
|
809
|
+
action: () => {
|
|
810
|
+
props.pasteHandler(clipboard.action, clipboard.files, focusItem.value);
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
label: props.tr('filemanager.list.menu.selectAll'),
|
|
815
|
+
icon: 'fa-solid fa-check-double',
|
|
816
|
+
visible: () => props.showSelectAll,
|
|
817
|
+
action: onSelectAll,
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
label: props.tr('filemanager.list.menu.rename'),
|
|
821
|
+
icon: 'fa-regular fa-pen-to-square',
|
|
822
|
+
action: onItemRenameBegin,
|
|
823
|
+
visible: () => props.showRename,
|
|
824
|
+
disabled: () => !props.editable || selectedCount.value > 1,
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
label: props.tr('filemanager.list.menu.chown'),
|
|
828
|
+
icon: 'fa-solid fa-user-pen',
|
|
829
|
+
action: onItemsChangeOwner,
|
|
830
|
+
visible: () => props.showOwner,
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
label: props.tr('filemanager.list.menu.extract'),
|
|
834
|
+
action: onItemExtract,
|
|
835
|
+
visible: () => {
|
|
836
|
+
if (!props.showExtract) return false;
|
|
837
|
+
|
|
838
|
+
const file = getSelected()[0];
|
|
839
|
+
return !(
|
|
840
|
+
selectedCount.value !== 1 ||
|
|
841
|
+
!file ||
|
|
842
|
+
!(
|
|
843
|
+
file.name.match(/\.tgz$/) ||
|
|
844
|
+
file.name.match(/\.tar$/) ||
|
|
845
|
+
file.name.match(/\.7z$/) ||
|
|
846
|
+
file.name.match(/\.zip$/) ||
|
|
847
|
+
file.name.match(/\.tar\.gz$/) ||
|
|
848
|
+
file.name.match(/\.tar\.xz$/) ||
|
|
849
|
+
file.name.match(/\.tar\.bz2$/)
|
|
850
|
+
)
|
|
851
|
+
);
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
separator: true,
|
|
856
|
+
visible: () => props.showDelete,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
label: props.tr('filemanager.list.menu.delete'),
|
|
860
|
+
icon: 'fa-regular fa-trash-can',
|
|
861
|
+
action: () => {
|
|
862
|
+
props.deleteHandler(getSelected());
|
|
863
|
+
},
|
|
864
|
+
visible: () => props.showDelete,
|
|
865
|
+
},
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
const filteredSortedItems = computed(() => {
|
|
869
|
+
invalidateSelected();
|
|
870
|
+
|
|
871
|
+
return props.items.sort((a, b) => {
|
|
872
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
873
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
874
|
+
|
|
875
|
+
let sort = 0;
|
|
876
|
+
if (sortBy.value === SORT_BY.NAME) {
|
|
877
|
+
if (a.name.toLowerCase() < b.name.toLowerCase()) sort = -1;
|
|
878
|
+
else if (a.name.toLowerCase() > b.name.toLowerCase()) sort = 1;
|
|
879
|
+
} else if (sortBy.value === SORT_BY.OWNER) {
|
|
880
|
+
if (a.owner.toLowerCase() < b.owner.toLowerCase()) sort = -1;
|
|
881
|
+
else if (a.owner.toLowerCase() > b.owner.toLowerCase()) sort = 1;
|
|
882
|
+
} else if (sortBy.value === SORT_BY.STAR) {
|
|
883
|
+
if (a.star && !b.star) sort = -1;
|
|
884
|
+
if (!a.star && b.star) sort = 1;
|
|
885
|
+
} else if (sortBy.value === SORT_BY.SIZE) {
|
|
886
|
+
if (a.size < b.size) sort = -1;
|
|
887
|
+
if (a.size > b.size) sort = 1;
|
|
888
|
+
} else if (sortBy.value === SORT_BY.MTIME) {
|
|
889
|
+
if (a.mtime < b.mtime) sort = -1;
|
|
890
|
+
if (a.mtime > b.mtime) sort = 1;
|
|
891
|
+
} else {
|
|
892
|
+
console.error('unknown SORT_BY', sortBy.value);
|
|
893
|
+
}
|
|
784
894
|
|
|
785
|
-
|
|
895
|
+
if (sort === 0) {
|
|
896
|
+
if (a.id < b.id) sort = -1;
|
|
897
|
+
else sort = 1;
|
|
786
898
|
}
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
899
|
|
|
790
|
-
|
|
900
|
+
return (sort *= sortDirection.value === SORT_DIRECTION.DESC ? -1 : 1);
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
defineExpose({
|
|
905
|
+
highlight,
|
|
906
|
+
highlightByName,
|
|
907
|
+
});
|
|
791
908
|
|
|
909
|
+
</script>
|
|
792
910
|
<style scoped>
|
|
793
911
|
|
|
794
912
|
.directory-view {
|