@cloudron/pankow 3.1.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.
- package/.gitlab-ci.yml +30 -0
- package/.jshintrc +8 -0
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/components/BottomBar.vue +22 -0
- package/components/Breadcrumb.vue +64 -0
- package/components/Button.vue +243 -0
- package/components/ButtonGroup.vue +37 -0
- package/components/Checkbox.vue +112 -0
- package/components/Dialog.vue +178 -0
- package/components/DirectoryView.vue +772 -0
- package/components/DirectoryViewListItem.vue +412 -0
- package/components/EmailInput.vue +22 -0
- package/components/FileUploader.vue +204 -0
- package/components/FormGroup.vue +26 -0
- package/components/Icon.vue +12 -0
- package/components/InputDialog.vue +170 -0
- package/components/InputGroup.vue +32 -0
- package/components/MainLayout.vue +63 -0
- package/components/Menu.vue +284 -0
- package/components/MenuItem.vue +106 -0
- package/components/MenuItemLink.vue +52 -0
- package/components/MultiSelect.vue +202 -0
- package/components/Notification.vue +163 -0
- package/components/NumberInput.vue +31 -0
- package/components/OfflineBanner.vue +47 -0
- package/components/PasswordInput.vue +86 -0
- package/components/Popover.vue +185 -0
- package/components/ProgressBar.vue +75 -0
- package/components/Radiobutton.vue +128 -0
- package/components/SideBar.vue +104 -0
- package/components/SingleSelect.vue +190 -0
- package/components/Spinner.vue +67 -0
- package/components/Switch.vue +94 -0
- package/components/TabView.vue +161 -0
- package/components/TableView.vue +187 -0
- package/components/TagInput.vue +104 -0
- package/components/TextInput.vue +58 -0
- package/components/TopBar.vue +88 -0
- package/fallbackImage.js +29 -0
- package/fetcher.js +81 -0
- package/gallery/CustomMenuItem.vue +40 -0
- package/gallery/DirectoryViewDemo.vue +73 -0
- package/gallery/Index.vue +790 -0
- package/gallery/folder.svg +151 -0
- package/gallery/index.html +25 -0
- package/gallery/index.js +10 -0
- package/gallery/logo.png +0 -0
- package/gallery/vite.config.mjs +9 -0
- package/gestures.js +60 -0
- package/index.js +86 -0
- package/logo.png +0 -0
- package/logo.svg +78 -0
- package/package.json +26 -0
- package/style.css +351 -0
- package/tooltip.js +83 -0
- package/utils.js +383 -0
- package/viewers/GenericViewer.vue +84 -0
- package/viewers/ImageViewer.vue +239 -0
- package/viewers/PdfViewer.vue +82 -0
- package/viewers/TextViewer.vue +221 -0
- package/viewers.js +11 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="directory-view"
|
|
4
|
+
:class="{ 'drop-target-active': dropTargetActive }"
|
|
5
|
+
tabindex="0"
|
|
6
|
+
ref="main"
|
|
7
|
+
@keydown.up.prevent="onKeyUp($event)"
|
|
8
|
+
@keydown.down.prevent="onKeyDown($event)"
|
|
9
|
+
@keydown.enter="onKeyEnter($event)"
|
|
10
|
+
@keydown.delete="onKeyDelete($event)"
|
|
11
|
+
@keydown.esc="onKeyEscape($event)"
|
|
12
|
+
@drop.stop.prevent="onDrop(null, $event)"
|
|
13
|
+
@dragover.stop.prevent="onDragOver($event)"
|
|
14
|
+
@dragenter.prevent
|
|
15
|
+
@dragexit="onDragExit($event)"
|
|
16
|
+
@keydown="onKeyEvent($event)"
|
|
17
|
+
>
|
|
18
|
+
<Menu ref="contextMenu" :model="contextMenuModel" />
|
|
19
|
+
<Menu ref="contextMenuBody" :model="contextMenuBodyModel" />
|
|
20
|
+
|
|
21
|
+
<Dialog ref="chownDialog" :title="tr('filemanager.chownDialog.title')" :confirmLabel="tr('filemanager.chownDialog.change')" @confirm="onChangeOwnerDialogSubmit(changeOwnerDialog.items, changeOwnerDialog.ownerId)">
|
|
22
|
+
<SingleSelect v-model="changeOwnerDialog.ownerId" :options="ownersModel" optionLabel="label" optionKey="uid"/>
|
|
23
|
+
</Dialog>
|
|
24
|
+
|
|
25
|
+
<div class="directory-view-empty-container" v-show="items.length === 0 && !busy" @contextmenu="onContextMenuBody($event)">
|
|
26
|
+
<slot name="empty">
|
|
27
|
+
Folder is empty
|
|
28
|
+
</slot>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="directory-view-header" v-show="items.length">
|
|
32
|
+
<div class="directory-view-header-icon"></div>
|
|
33
|
+
<div class="directory-view-header-name" @click="toggleSort(SORT_BY.NAME)"><i v-show="sortBy === SORT_BY.NAME" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.name') }}</div>
|
|
34
|
+
<div class="directory-view-header-star" v-show="showStar" @click="toggleSort(SORT_BY.STAR)"><i v-show="sortBy === SORT_BY.STAR" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> <i class="fa-regular fa-star"></i></div>
|
|
35
|
+
<div class="directory-view-header-owner" v-show="showOwner" @click="toggleSort(SORT_BY.OWNER)"><i v-show="sortBy === SORT_BY.OWNER" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.owner') }}</div>
|
|
36
|
+
<div class="directory-view-header-size" v-show="showSize" @click="toggleSort(SORT_BY.SIZE)"><i v-show="sortBy === SORT_BY.SIZE" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.size') }}</div>
|
|
37
|
+
<div class="directory-view-header-modified" v-show="showModified" @click="toggleSort(SORT_BY.MTIME)"><i v-show="sortBy === SORT_BY.MTIME" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.mtime') }}</div>
|
|
38
|
+
<div class="directory-view-header-actions"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="directory-view-body-container" v-show="items.length">
|
|
41
|
+
<div
|
|
42
|
+
class="directory-view-body"
|
|
43
|
+
@contextmenu="onContextMenuBody($event)"
|
|
44
|
+
>
|
|
45
|
+
<DirectoryViewListItem v-for="item in filteredSortedItems" :ref="(el) => this.elements[item.id] = el"
|
|
46
|
+
:key="item.id"
|
|
47
|
+
:fallbackIcon="fallbackIcon"
|
|
48
|
+
:item="item"
|
|
49
|
+
:show-owner="showOwner"
|
|
50
|
+
:show-share="showShare"
|
|
51
|
+
:show-extract="showExtract"
|
|
52
|
+
:show-size="showSize"
|
|
53
|
+
:show-star="showStar"
|
|
54
|
+
:show-modified="showModified"
|
|
55
|
+
:rename-handler="renameHandler"
|
|
56
|
+
:select-handler="onSelectAndFocus"
|
|
57
|
+
:drop-handler="onDrop"
|
|
58
|
+
:can-drop-handler="onCanDropHandler"
|
|
59
|
+
:star-handler="starHandler"
|
|
60
|
+
@contextmenu="onContextMenu(item, $event)"
|
|
61
|
+
@action-menu="onContextMenu"
|
|
62
|
+
@dblclick="onItemActivated(item)"
|
|
63
|
+
@activated="onItemActivated(item)"
|
|
64
|
+
@dragstart="onItemDragStart($event, item)"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="drag-handle"></div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script>
|
|
74
|
+
|
|
75
|
+
import SingleSelect from './SingleSelect.vue';
|
|
76
|
+
import DirectoryViewListItem from './DirectoryViewListItem.vue';
|
|
77
|
+
import Dialog from './Dialog.vue';
|
|
78
|
+
import Menu from './Menu.vue';
|
|
79
|
+
|
|
80
|
+
import { translation } from '../utils.js';
|
|
81
|
+
|
|
82
|
+
const SORT_BY = {
|
|
83
|
+
NAME: 'name',
|
|
84
|
+
OWNER: 'owner',
|
|
85
|
+
STAR: 'star',
|
|
86
|
+
SIZE: 'size',
|
|
87
|
+
MTIME: 'mtime',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const SORT_DIRECTION = {
|
|
91
|
+
ASC: 'asc',
|
|
92
|
+
DESC: 'desc',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
let rawSelected = [];
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
name: 'DirectoryView',
|
|
99
|
+
components: {
|
|
100
|
+
Dialog,
|
|
101
|
+
DirectoryViewListItem,
|
|
102
|
+
SingleSelect,
|
|
103
|
+
Menu
|
|
104
|
+
},
|
|
105
|
+
emits: [ 'selectionChanged', 'item-activated' ],
|
|
106
|
+
props: {
|
|
107
|
+
busy: {
|
|
108
|
+
type: Boolean,
|
|
109
|
+
default: false
|
|
110
|
+
},
|
|
111
|
+
showOwner: {
|
|
112
|
+
type: Boolean,
|
|
113
|
+
default: false
|
|
114
|
+
},
|
|
115
|
+
showSize: {
|
|
116
|
+
type: Boolean,
|
|
117
|
+
default: false
|
|
118
|
+
},
|
|
119
|
+
showStar: {
|
|
120
|
+
type: Boolean,
|
|
121
|
+
default: false
|
|
122
|
+
},
|
|
123
|
+
showExtract: {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
default: true
|
|
126
|
+
},
|
|
127
|
+
showShare: {
|
|
128
|
+
// if String its the name of the property for existing share indicator
|
|
129
|
+
type: [ Boolean, String ],
|
|
130
|
+
default: false
|
|
131
|
+
},
|
|
132
|
+
showModified: {
|
|
133
|
+
type: Boolean,
|
|
134
|
+
default: false
|
|
135
|
+
},
|
|
136
|
+
editable: {
|
|
137
|
+
type: Boolean,
|
|
138
|
+
default: true
|
|
139
|
+
},
|
|
140
|
+
multiDownload: {
|
|
141
|
+
type: Boolean,
|
|
142
|
+
default: false
|
|
143
|
+
},
|
|
144
|
+
tr: {
|
|
145
|
+
type: Function,
|
|
146
|
+
default(id) { console.warn('Missing tr for DirectoryView, using fallback.'); return translation(id); }
|
|
147
|
+
},
|
|
148
|
+
items: {
|
|
149
|
+
type: Array,
|
|
150
|
+
validator(value) {
|
|
151
|
+
function fail(prop, type, item) {
|
|
152
|
+
console.error(`DirectoryView.items[].${prop} must be a ${type}`, item);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const item of value) {
|
|
157
|
+
if (typeof item.id !== 'string') return fail('id', 'string', item);
|
|
158
|
+
if (typeof item.name !== 'string') return fail('name', 'string', item);
|
|
159
|
+
if (typeof item.icon !== 'string') return fail('icon', 'string', item);
|
|
160
|
+
if (typeof item.previewUrl !== 'string') return fail('previewUrl', 'string', item);
|
|
161
|
+
|
|
162
|
+
// optional
|
|
163
|
+
if (item.href && typeof item.href !== 'string') return fail('href', 'string', item);
|
|
164
|
+
if (item.target && typeof item.target !== 'string') return fail('target', 'string', item);
|
|
165
|
+
if (item.owner && typeof item.owner !== 'string') return fail('owner', 'string', item);
|
|
166
|
+
if (item.size && typeof item.size !== 'number') return fail('size', 'number', item);
|
|
167
|
+
if (item.star && typeof item.star !== 'boolean') return fail('star', 'boolean', item);
|
|
168
|
+
// if (item.modified && item.modified instanceof Date) return fail('modified', 'Date', item);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
ownersModel: {
|
|
175
|
+
type: Array,
|
|
176
|
+
default: []
|
|
177
|
+
},
|
|
178
|
+
fallbackIcon: String,
|
|
179
|
+
deleteHandler: {
|
|
180
|
+
type: Function,
|
|
181
|
+
default() { console.warn('Missing deleteHandler for DirectoryView'); }
|
|
182
|
+
},
|
|
183
|
+
renameHandler: {
|
|
184
|
+
type: Function,
|
|
185
|
+
default() { console.warn('Missing renameHandler for DirectoryView'); }
|
|
186
|
+
},
|
|
187
|
+
changeOwnerHandler: {
|
|
188
|
+
type: Function,
|
|
189
|
+
default() { console.warn('Missing changeOwnerHandler for DirectoryView'); }
|
|
190
|
+
},
|
|
191
|
+
pasteHandler: {
|
|
192
|
+
type: Function,
|
|
193
|
+
default() { console.warn('Missing pasteHandler for DirectoryView'); }
|
|
194
|
+
},
|
|
195
|
+
newFileHandler: {
|
|
196
|
+
type: Function,
|
|
197
|
+
default() { console.warn('Missing newFileHandler for DirectoryView'); }
|
|
198
|
+
},
|
|
199
|
+
newFolderHandler: {
|
|
200
|
+
type: Function,
|
|
201
|
+
default() { console.warn('Missing newFolderHandler for DirectoryView'); }
|
|
202
|
+
},
|
|
203
|
+
uploadFileHandler: {
|
|
204
|
+
type: Function,
|
|
205
|
+
default() { console.warn('Missing uploadFileHandler for DirectoryView'); }
|
|
206
|
+
},
|
|
207
|
+
uploadFolderHandler: {
|
|
208
|
+
type: Function,
|
|
209
|
+
default() { console.warn('Missing uploadFolderHandler for DirectoryView'); }
|
|
210
|
+
},
|
|
211
|
+
shareHandler: {
|
|
212
|
+
type: Function,
|
|
213
|
+
default() { console.warn('Missing shareHandler for DirectoryView'); }
|
|
214
|
+
},
|
|
215
|
+
starHandler: {
|
|
216
|
+
type: Function,
|
|
217
|
+
default() { console.warn('Missing starHandler for DirectoryView'); }
|
|
218
|
+
},
|
|
219
|
+
dropHandler: {
|
|
220
|
+
type: Function,
|
|
221
|
+
default() { console.warn('Missing dropHandler for DirectoryView'); }
|
|
222
|
+
},
|
|
223
|
+
downloadHandler: {
|
|
224
|
+
type: Function,
|
|
225
|
+
default() { console.warn('Missing downloadHandler for DirectoryView'); }
|
|
226
|
+
},
|
|
227
|
+
extractHandler: {
|
|
228
|
+
type: Function,
|
|
229
|
+
default() { console.warn('Missing extractHandler for DirectoryView'); }
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
computed: {
|
|
233
|
+
filteredSortedItems() {
|
|
234
|
+
this.invalidateSelected();
|
|
235
|
+
|
|
236
|
+
// first sort by sort column and direction, then make sure folders are always first
|
|
237
|
+
return this.items.sort((a, b) => {
|
|
238
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
239
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
240
|
+
|
|
241
|
+
let sort = 0;
|
|
242
|
+
if (this.sortBy === SORT_BY.NAME) {
|
|
243
|
+
if (a.name.toLowerCase() < b.name.toLowerCase()) sort = -1;
|
|
244
|
+
else if (a.name.toLowerCase() > b.name.toLowerCase()) sort = 1;
|
|
245
|
+
} else if (this.sortBy === SORT_BY.OWNER) {
|
|
246
|
+
if (a.owner.toLowerCase() < b.owner.toLowerCase()) sort = -1;
|
|
247
|
+
else if (a.owner.toLowerCase() > b.owner.toLowerCase()) sort = 1;
|
|
248
|
+
} else if (this.sortBy === SORT_BY.STAR) {
|
|
249
|
+
if (a.star && !b.star) sort = -1;
|
|
250
|
+
if (!a.star && b.star) sort = 1;
|
|
251
|
+
} else if (this.sortBy === SORT_BY.SIZE) {
|
|
252
|
+
if (a.size < b.size) sort = -1;
|
|
253
|
+
if (a.size > b.size) sort = 1;
|
|
254
|
+
} else if (this.sortBy === SORT_BY.MTIME) {
|
|
255
|
+
if (a.mtime < b.mtime) sort = -1;
|
|
256
|
+
if (a.mtime > b.mtime) sort = 1;
|
|
257
|
+
} else {
|
|
258
|
+
console.error('unknown SORT_BY', this.sortBy);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// tie break by id to avoid recursion
|
|
262
|
+
if (sort === 0) {
|
|
263
|
+
if (a.id < b.id) sort = -1;
|
|
264
|
+
else sort = 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return sort *= (this.sortDirection === SORT_DIRECTION.DESC ? -1 : 1);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
data() {
|
|
272
|
+
return {
|
|
273
|
+
_alreadyActivating: false,
|
|
274
|
+
SORT_BY,
|
|
275
|
+
SORT_DIRECTION,
|
|
276
|
+
elements: {}, // holds references by item.id
|
|
277
|
+
focusItem: null,
|
|
278
|
+
focusItemIndex: -1,
|
|
279
|
+
selectedCount: 0,
|
|
280
|
+
dropTargetActive: false,
|
|
281
|
+
sortBy: SORT_BY.NAME,
|
|
282
|
+
sortDirection: SORT_DIRECTION.ASC,
|
|
283
|
+
clipboard: {
|
|
284
|
+
action: '', // 'copy' or 'cut'
|
|
285
|
+
files: []
|
|
286
|
+
},
|
|
287
|
+
changeOwnerDialog: {
|
|
288
|
+
busy: false,
|
|
289
|
+
ownerId: '',
|
|
290
|
+
items: []
|
|
291
|
+
},
|
|
292
|
+
contextMenuBodyModel: [{
|
|
293
|
+
label: this.tr('filemanager.list.menu.paste'),
|
|
294
|
+
icon:'fa-regular fa-paste',
|
|
295
|
+
disabled: () => { return !this.clipboard.files || !this.clipboard.files.length; },
|
|
296
|
+
action: () => { this.pasteHandler(this.clipboard.action, this.clipboard.files, null); }
|
|
297
|
+
}, {
|
|
298
|
+
label: this.tr('filemanager.list.menu.selectAll'),
|
|
299
|
+
icon:'fa-solid fa-check-double',
|
|
300
|
+
action: this.onSelectAll
|
|
301
|
+
}, {
|
|
302
|
+
separator:true
|
|
303
|
+
}, {
|
|
304
|
+
label: this.tr('filemanager.toolbar.newFile'),
|
|
305
|
+
icon:'fa-solid fa-file-circle-plus',
|
|
306
|
+
action: this.newFileHandler
|
|
307
|
+
}, {
|
|
308
|
+
label: this.tr('filemanager.toolbar.newFolder'),
|
|
309
|
+
icon:'fa-solid fa-folder-plus',
|
|
310
|
+
action: this.newFolderHandler
|
|
311
|
+
}, {
|
|
312
|
+
separator:true
|
|
313
|
+
}, {
|
|
314
|
+
label: this.tr('filemanager.toolbar.uploadFile'),
|
|
315
|
+
icon:'fa-solid fa-file-arrow-up',
|
|
316
|
+
action: this.uploadFileHandler
|
|
317
|
+
}, {
|
|
318
|
+
label: this.tr('filemanager.toolbar.uploadFolder'),
|
|
319
|
+
icon:'fa-regular fa-folder-open',
|
|
320
|
+
action: this.uploadFolderHandler
|
|
321
|
+
}],
|
|
322
|
+
contextMenuModel: [{
|
|
323
|
+
label: this.tr('filemanager.list.menu.open'),
|
|
324
|
+
icon:'fa-regular fa-folder-open',
|
|
325
|
+
disabled: () => { return this.selectedCount > 1; },
|
|
326
|
+
action: () => { this.onItemActivated(this.getSelected()[0]); }
|
|
327
|
+
}, {
|
|
328
|
+
separator:true
|
|
329
|
+
}, {
|
|
330
|
+
label: this.tr('filemanager.list.menu.download'),
|
|
331
|
+
icon:'fa-solid fa-download',
|
|
332
|
+
action: this.onItemDownload,
|
|
333
|
+
disabled: () => { return this.multiDownload ? this.selectedCount === 0 : this.selectedCount !== 1; }
|
|
334
|
+
}, {
|
|
335
|
+
label: this.tr('filemanager.list.menu.share'),
|
|
336
|
+
icon:'fa-solid fa-share-nodes',
|
|
337
|
+
action: this.onItemShare,
|
|
338
|
+
disabled: () => { return this.selectedCount > 1; },
|
|
339
|
+
visible: this.showShare
|
|
340
|
+
}, {
|
|
341
|
+
label: this.tr('filemanager.list.menu.copy'),
|
|
342
|
+
icon:'fa-regular fa-copy',
|
|
343
|
+
action: this.onItemsCopy
|
|
344
|
+
}, {
|
|
345
|
+
label: this.tr('filemanager.list.menu.cut'),
|
|
346
|
+
icon:'fa-solid fa-scissors',
|
|
347
|
+
action: this.onItemsCut
|
|
348
|
+
}, {
|
|
349
|
+
label: this.tr('filemanager.list.menu.paste'),
|
|
350
|
+
icon:'fa-regular fa-paste',
|
|
351
|
+
disabled: () => { return !(this.focusItem && this.focusItem.isDirectory) || this.selectedCount > 1 || !this.clipboard.files || !this.clipboard.files.length; },
|
|
352
|
+
action: () => { this.pasteHandler(this.clipboard.action, this.clipboard.files, this.focusItem); }
|
|
353
|
+
}, {
|
|
354
|
+
label: this.tr('filemanager.list.menu.selectAll'),
|
|
355
|
+
icon:'fa-solid fa-check-double',
|
|
356
|
+
action: this.onSelectAll
|
|
357
|
+
}, {
|
|
358
|
+
label: this.tr('filemanager.list.menu.rename'),
|
|
359
|
+
icon:'fa-regular fa-pen-to-square',
|
|
360
|
+
action: this.onItemRenameBegin,
|
|
361
|
+
disabled: () => { return !this.editable || this.selectedCount > 1; }
|
|
362
|
+
}, {
|
|
363
|
+
label: this.tr('filemanager.list.menu.chown'),
|
|
364
|
+
icon:'fa-solid fa-user-pen',
|
|
365
|
+
action: this.onItemsChangeOwner,
|
|
366
|
+
visible: this.showOwner
|
|
367
|
+
}, {
|
|
368
|
+
label: this.tr('filemanager.list.menu.extract'),
|
|
369
|
+
action: this.onItemExtract,
|
|
370
|
+
visible: () => {
|
|
371
|
+
if (!this.showExtract) return false;
|
|
372
|
+
|
|
373
|
+
const file = this.getSelected()[0];
|
|
374
|
+
return !(this.selectedCount !== 1 || !(
|
|
375
|
+
file.name.match(/\.tgz$/) ||
|
|
376
|
+
file.name.match(/\.tar$/) ||
|
|
377
|
+
file.name.match(/\.7z$/) ||
|
|
378
|
+
file.name.match(/\.zip$/) ||
|
|
379
|
+
file.name.match(/\.tar\.gz$/) ||
|
|
380
|
+
file.name.match(/\.tar\.xz$/) ||
|
|
381
|
+
file.name.match(/\.tar\.bz2$/)));
|
|
382
|
+
}
|
|
383
|
+
}, {
|
|
384
|
+
separator:true
|
|
385
|
+
}, {
|
|
386
|
+
label: this.tr('filemanager.list.menu.delete'),
|
|
387
|
+
icon:'fa-regular fa-trash-can',
|
|
388
|
+
action: () => { this.deleteHandler(this.getSelected()); }
|
|
389
|
+
}],
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
methods: {
|
|
393
|
+
highlight(item) {
|
|
394
|
+
const id = item.id;
|
|
395
|
+
|
|
396
|
+
if (!this.elements[id]) return;
|
|
397
|
+
|
|
398
|
+
this.elements[id].highlight();
|
|
399
|
+
this.elements[id].$el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
400
|
+
},
|
|
401
|
+
highlightByName(name) {
|
|
402
|
+
const tmp = this.items.find((i) => i.name === name);
|
|
403
|
+
if (!tmp) return;
|
|
404
|
+
|
|
405
|
+
this.highlight(tmp);
|
|
406
|
+
},
|
|
407
|
+
toggleSort(sortBy) {
|
|
408
|
+
if (this.sortBy === sortBy) this.sortDirection = this.sortDirection === SORT_DIRECTION.ASC ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
|
|
409
|
+
else this.sortBy = sortBy;
|
|
410
|
+
},
|
|
411
|
+
onDrop(targetItem, event) {
|
|
412
|
+
this.dropTargetActive = false;
|
|
413
|
+
|
|
414
|
+
const targetItemName = targetItem ? targetItem.name : '';
|
|
415
|
+
|
|
416
|
+
if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
|
|
417
|
+
// can't drop selected files on same folder
|
|
418
|
+
if (!targetItemName) return;
|
|
419
|
+
|
|
420
|
+
this.dropHandler(targetItemName, null, this.getSelected());
|
|
421
|
+
} else if (event.dataTransfer && event.dataTransfer.items[0]) {
|
|
422
|
+
// this is dropping files from outside the browser
|
|
423
|
+
this.dropHandler(targetItemName, event.dataTransfer, null);
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
onCanDropHandler(item) {
|
|
427
|
+
if (!item.isDirectory) return false;
|
|
428
|
+
return !item.selected;
|
|
429
|
+
},
|
|
430
|
+
onDragOver(event) {
|
|
431
|
+
if (!event.dataTransfer) return;
|
|
432
|
+
|
|
433
|
+
if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
|
|
434
|
+
this.dropTargetActive = false;
|
|
435
|
+
} else {
|
|
436
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
437
|
+
this.dropTargetActive = true;
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
onDragExit(event) {
|
|
441
|
+
this.dropTargetActive = false;
|
|
442
|
+
},
|
|
443
|
+
onItemDragStart(event) {
|
|
444
|
+
const dragHandle = document.getElementsByClassName('drag-handle')[0];
|
|
445
|
+
|
|
446
|
+
// clear element
|
|
447
|
+
dragHandle.replaceChildren();
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < Math.min(this.selectedCount, 6); ++i) {
|
|
450
|
+
const dragHandleItem = document.createElement('div');
|
|
451
|
+
dragHandleItem.style.opacity = 1-((i*2)/10);
|
|
452
|
+
const dragHandleItemIcon = document.createElement('img');
|
|
453
|
+
dragHandleItemIcon.src = rawSelected[i].previewUrl || rawSelected[i].icon
|
|
454
|
+
dragHandleItem.append(dragHandleItemIcon, rawSelected[i].name);
|
|
455
|
+
dragHandle.append(dragHandleItem);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
event.dataTransfer.dropEffect = 'move'
|
|
459
|
+
event.dataTransfer.setData('application/x-pankow', 'selected');
|
|
460
|
+
event.dataTransfer.setDragImage(dragHandle, 0, 0);
|
|
461
|
+
},
|
|
462
|
+
onKeyUp(event) {
|
|
463
|
+
this.onSelectAndFocusIndex(this.focusItemIndex-1, event.shiftKey);
|
|
464
|
+
},
|
|
465
|
+
onKeyDown(event) {
|
|
466
|
+
this.onSelectAndFocusIndex(this.focusItemIndex+1, event.shiftKey);
|
|
467
|
+
},
|
|
468
|
+
onKeyEnter(event) {
|
|
469
|
+
if (this.focusItem) this.onItemActivated(this.focusItem);
|
|
470
|
+
},
|
|
471
|
+
async onKeyDelete(event) {
|
|
472
|
+
if (event.key !== 'Delete') return; // triggered also by backspace
|
|
473
|
+
if (!this.focusItem) return;
|
|
474
|
+
|
|
475
|
+
await this.deleteHandler(this.getSelected());
|
|
476
|
+
},
|
|
477
|
+
onKeyEscape(event) {
|
|
478
|
+
this.onUnselectAll();
|
|
479
|
+
},
|
|
480
|
+
onKeyEvent(event) {
|
|
481
|
+
if (event.key === 'c' && (event.ctrlKey || event.metaKey)) {
|
|
482
|
+
this.onItemsCopy();
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
} else if (event.key === 'v' && (event.ctrlKey || event.metaKey)) {
|
|
485
|
+
this.pasteHandler(this.clipboard.action, this.clipboard.files);
|
|
486
|
+
event.preventDefault();
|
|
487
|
+
} else if (event.key === 'x' && (event.ctrlKey || event.metaKey)) {
|
|
488
|
+
this.onItemsCut();
|
|
489
|
+
event.preventDefault();
|
|
490
|
+
} else if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {
|
|
491
|
+
this.onSelectAll();
|
|
492
|
+
event.preventDefault();
|
|
493
|
+
} else if (event.key === 'End') {
|
|
494
|
+
if (event.shiftKey) this.onSelectAndFocusToIndex(this.items.length-1);
|
|
495
|
+
else this.onSelectAndFocusIndex(this.items.length-1, event.shiftKey);
|
|
496
|
+
event.preventDefault();
|
|
497
|
+
} else if (event.key === 'Home') {
|
|
498
|
+
if (event.shiftKey) this.onSelectAndFocusToIndex(0);
|
|
499
|
+
else this.onSelectAndFocusIndex(0, event.shiftKey);
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
} else if (event.key === 'PageUp') {
|
|
502
|
+
const pageItemCount = parseInt(event.target.clientHeight / 55);
|
|
503
|
+
if (event.shiftKey) this.onSelectAndFocusToIndex(this.focusItemIndex-pageItemCount);
|
|
504
|
+
else this.onSelectAndFocusIndex(this.focusItemIndex-pageItemCount, event.shiftKey);
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
} else if (event.key === 'PageDown') {
|
|
507
|
+
const pageItemCount = parseInt(event.target.clientHeight / 55);
|
|
508
|
+
if (event.shiftKey) this.onSelectAndFocusToIndex(this.focusItemIndex+pageItemCount);
|
|
509
|
+
else this.onSelectAndFocusIndex(this.focusItemIndex+pageItemCount, event.shiftKey);
|
|
510
|
+
event.preventDefault();
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
onContextMenu(item, event) {
|
|
514
|
+
this.$refs.contextMenu.open(event);
|
|
515
|
+
this.onSelectAndFocus(item, event, false);
|
|
516
|
+
event.stopPropagation();
|
|
517
|
+
},
|
|
518
|
+
onContextMenuBody(event) {
|
|
519
|
+
this.$refs.contextMenuBody.open(event);
|
|
520
|
+
this.onUnselectAll();
|
|
521
|
+
},
|
|
522
|
+
onItemActivated(item) {
|
|
523
|
+
// prevent concurrent activations for some time
|
|
524
|
+
if (this._alreadyActivating) return;
|
|
525
|
+
this._alreadyActivating = true;
|
|
526
|
+
setTimeout(() => { this._alreadyActivating = false; }, 500);
|
|
527
|
+
|
|
528
|
+
this.$emit('item-activated', item);
|
|
529
|
+
},
|
|
530
|
+
onItemsChangeOwner(event) {
|
|
531
|
+
if (!this.focusItem) return console.warn('onItemsChangeOwner should only be triggered if at least one item is selected');
|
|
532
|
+
|
|
533
|
+
this.changeOwnerDialog.items = this.getSelected();
|
|
534
|
+
this.changeOwnerDialog.ownerId = this.focusItem.uid;
|
|
535
|
+
this.$refs.chownDialog.open();
|
|
536
|
+
},
|
|
537
|
+
onItemDownload(event) {
|
|
538
|
+
if (!this.focusItem) return console.warn('onItemDownload should only be triggered if at lesst one item is selected');
|
|
539
|
+
|
|
540
|
+
this.downloadHandler(this.multiDownload ? this.getSelected() : this.focusItem);
|
|
541
|
+
},
|
|
542
|
+
onItemExtract(event) {
|
|
543
|
+
if (this.selectedCount !== 1) return console.warn('onItemExtract should only be triggered if one item is selected');
|
|
544
|
+
|
|
545
|
+
this.extractHandler(this.focusItem);
|
|
546
|
+
},
|
|
547
|
+
onItemsCopy(event) {
|
|
548
|
+
if (!this.focusItem) return console.warn('onItemsCopy should only be triggered if at least one item is selected');
|
|
549
|
+
|
|
550
|
+
this.clipboard.action = 'copy';
|
|
551
|
+
this.clipboard.files = this.getSelected();
|
|
552
|
+
},
|
|
553
|
+
onItemsCut(event) {
|
|
554
|
+
if (!this.focusItem) return console.warn('onItemsCut should only be triggered if at least one item is selected');
|
|
555
|
+
|
|
556
|
+
this.clipboard.action = 'cut';
|
|
557
|
+
this.clipboard.files = this.getSelected();
|
|
558
|
+
},
|
|
559
|
+
onItemShare(event) {
|
|
560
|
+
if (this.selectedCount !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
561
|
+
|
|
562
|
+
this.shareHandler(this.focusItem);
|
|
563
|
+
},
|
|
564
|
+
onItemRenameBegin(event) {
|
|
565
|
+
if (this.selectedCount !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
566
|
+
|
|
567
|
+
this.elements[this.focusItem.id].onRenameBegin();
|
|
568
|
+
},
|
|
569
|
+
async onItemRenameSubmit(item, newName) {
|
|
570
|
+
await this.renameHandler(item, newName);
|
|
571
|
+
},
|
|
572
|
+
async onChangeOwnerDialogSubmit(items, newOwnerUid) {
|
|
573
|
+
this.changeOwnerDialog.busy = true;
|
|
574
|
+
await this.changeOwnerHandler(items, newOwnerUid);
|
|
575
|
+
this.changeOwnerDialog.visible = false;
|
|
576
|
+
this.changeOwnerDialog.items = [];
|
|
577
|
+
this.changeOwnerDialog.ownerId = this.ownersModel[0].uid;
|
|
578
|
+
this.changeOwnerDialog.busy = false;
|
|
579
|
+
this.$refs.chownDialog.close();
|
|
580
|
+
},
|
|
581
|
+
getSelected() {
|
|
582
|
+
return rawSelected;
|
|
583
|
+
},
|
|
584
|
+
invalidateSelected() {
|
|
585
|
+
rawSelected = this.items.filter((item) => item.selected).slice();
|
|
586
|
+
this.selectedCount = rawSelected.length;
|
|
587
|
+
this.$emit('selectionChanged', rawSelected);
|
|
588
|
+
},
|
|
589
|
+
onSelectAll() {
|
|
590
|
+
for (const item of this.items) item.selected = true;
|
|
591
|
+
if (!this.focusItem) this.setFocus(this.focusItemIndex);
|
|
592
|
+
|
|
593
|
+
this.invalidateSelected();
|
|
594
|
+
},
|
|
595
|
+
onUnselectAll() {
|
|
596
|
+
for (const item of this.items) item.selected = false;
|
|
597
|
+
if (this.focusItem) this.focusItem = null;
|
|
598
|
+
|
|
599
|
+
this.invalidateSelected();
|
|
600
|
+
},
|
|
601
|
+
setFocus(index) {
|
|
602
|
+
if (this.items[this.focusItemIndex]) this.items[this.focusItemIndex].focused = false;
|
|
603
|
+
if (this.focusItem) this.focusItem.focused = false;
|
|
604
|
+
if (!this.items[index]) return;
|
|
605
|
+
|
|
606
|
+
this.focusItem = this.items[index];
|
|
607
|
+
this.focusItem.focused = true;
|
|
608
|
+
this.focusItemIndex = index;
|
|
609
|
+
},
|
|
610
|
+
onSelectAndFocusToIndex(index) {
|
|
611
|
+
index = (index < 0) ? 0 : (index > this.items.length-1 ? this.items.length-1 : index); // keep in bounds
|
|
612
|
+
|
|
613
|
+
this.setFocus(index);
|
|
614
|
+
|
|
615
|
+
const item = this.items[index];
|
|
616
|
+
const itemIndex = this.items.findIndex(i => i.id === item.id);
|
|
617
|
+
const firstSelectedIndex = this.items.findIndex(item => item.selected);
|
|
618
|
+
const lastSelectedIndex = this.items.findLastIndex(item => item.selected);
|
|
619
|
+
|
|
620
|
+
if (index < firstSelectedIndex) for (let i = index; i < firstSelectedIndex; ++i) this.items[i].selected = true;
|
|
621
|
+
else if (index > lastSelectedIndex) for (let i = lastSelectedIndex; i <= index; ++i) this.items[i].selected = true;
|
|
622
|
+
|
|
623
|
+
if (this.elements[item.id]) this.elements[item.id].$el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
624
|
+
|
|
625
|
+
this.invalidateSelected();
|
|
626
|
+
},
|
|
627
|
+
onSelectAndFocusIndex(index, keepSelected = false) {
|
|
628
|
+
index = (index < 0) ? 0 : (index > this.items.length-1 ? this.items.length-1 : index); // keep in bounds
|
|
629
|
+
|
|
630
|
+
const item = this.items[index];
|
|
631
|
+
|
|
632
|
+
this.onSelectAndFocus(item, {}, keepSelected);
|
|
633
|
+
},
|
|
634
|
+
onSelectAndFocus(item, event, keepSelected = false) {
|
|
635
|
+
const itemIndex = this.items.findIndex(i => i.id === item.id);
|
|
636
|
+
this.setFocus(itemIndex);
|
|
637
|
+
|
|
638
|
+
// do not toggle selection if this is from right click
|
|
639
|
+
if (event.button === 2 && item.selected) keepSelected = true;
|
|
640
|
+
|
|
641
|
+
if (event.ctrlKey) {
|
|
642
|
+
if (keepSelected) item.selected = true;
|
|
643
|
+
else item.selected = !item.selected;
|
|
644
|
+
} else if (event.shiftKey) {
|
|
645
|
+
const firstSelectedIndex = this.items.findIndex(item => item.selected);
|
|
646
|
+
const lastSelectedIndex = this.items.findLastIndex(item => item.selected);
|
|
647
|
+
|
|
648
|
+
if (itemIndex < firstSelectedIndex) for (let i = itemIndex; i < firstSelectedIndex; ++i) this.items[i].selected = true;
|
|
649
|
+
else if (itemIndex > lastSelectedIndex) for (let i = lastSelectedIndex; i <= itemIndex; ++i) this.items[i].selected = true;
|
|
650
|
+
} else {
|
|
651
|
+
if (!keepSelected) for (const item of this.items) item.selected = false;
|
|
652
|
+
item.selected = true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (this.elements[item.id]) this.elements[item.id].$el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
656
|
+
|
|
657
|
+
this.invalidateSelected();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
</script>
|
|
663
|
+
|
|
664
|
+
<style scoped>
|
|
665
|
+
|
|
666
|
+
.directory-view {
|
|
667
|
+
display: block;
|
|
668
|
+
overflow: hidden;
|
|
669
|
+
height: 100%;
|
|
670
|
+
width: 100%;
|
|
671
|
+
user-select: none;
|
|
672
|
+
border: 2px solid transparent;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.directory-view-empty-container {
|
|
676
|
+
display: flex;
|
|
677
|
+
justify-content: center;
|
|
678
|
+
align-items: center;
|
|
679
|
+
overflow: hidden;
|
|
680
|
+
height: 100%;
|
|
681
|
+
width: 100%;
|
|
682
|
+
user-select: none;
|
|
683
|
+
border: 2px solid transparent;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.directory-view.drop-target-active {
|
|
687
|
+
border: 2px solid var(--pankow-color-primary);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.directory-view-header {
|
|
691
|
+
padding: 10px;
|
|
692
|
+
padding-top: 20px;
|
|
693
|
+
display: flex;
|
|
694
|
+
font-weight: bold;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.directory-view-body-container {
|
|
698
|
+
overflow: hidden;
|
|
699
|
+
height: calc(100% - 38px); /* 38px is the header size */
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.directory-view-body {
|
|
703
|
+
padding: 0 10px;
|
|
704
|
+
padding-bottom: 10px;
|
|
705
|
+
height: 100%;
|
|
706
|
+
overflow: auto;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.directory-view-header-icon {
|
|
710
|
+
width: 40px;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.directory-view-header-name {
|
|
714
|
+
padding-left: 10px;
|
|
715
|
+
flex-grow: 1;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.directory-view-header-star {
|
|
719
|
+
width: 80px;
|
|
720
|
+
text-align: center;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.directory-view-header-owner {
|
|
724
|
+
width: 100px;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.directory-view-header-size {
|
|
728
|
+
width: 100px;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.directory-view-header-modified {
|
|
732
|
+
width: 100px;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.directory-view-header-actions {
|
|
736
|
+
width: 40px;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.dialog-header {
|
|
740
|
+
font-weight: 600;
|
|
741
|
+
font-size: 1.25rem;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.dialog-dropdown {
|
|
745
|
+
width: 100%;
|
|
746
|
+
margin-top: 5px;
|
|
747
|
+
margin-bottom: 1.5rem;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.dialog-submit {
|
|
751
|
+
margin-top: 5px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.drag-handle {
|
|
755
|
+
position: absolute;
|
|
756
|
+
top: -1000px;
|
|
757
|
+
background: transparent;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.drag-handle :deep(div) {
|
|
761
|
+
display: flex;
|
|
762
|
+
align-items: center;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.drag-handle :deep(img) {
|
|
766
|
+
width: 45px;
|
|
767
|
+
height: 45px;
|
|
768
|
+
object-fit: cover;
|
|
769
|
+
padding: 5px;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
</style>
|