client: combine selection of files & folders

This commit is contained in:
Johann150 2022-12-23 02:20:36 +01:00
parent df9064c284
commit d26e2588e3
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
3 changed files with 105 additions and 89 deletions

View file

@ -52,7 +52,7 @@ const props = withDefaults(defineProps<{
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'chosen', r: foundkey.entities.DriveFile): void; (ev: 'chosen', r: foundkey.entities.DriveFile, extendSelection: boolean): void;
(ev: 'dragstart'): void; (ev: 'dragstart'): void;
(ev: 'dragend'): void; (ev: 'dragend'): void;
}>(); }>();
@ -95,9 +95,7 @@ function getMenu(): MenuItem[] {
function onClick(ev: MouseEvent): void { function onClick(ev: MouseEvent): void {
if (props.selectMode) { if (props.selectMode) {
emit('chosen', props.file); emit('chosen', props.file, ev.ctrlKey);
} else {
os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
} }
} }
@ -330,7 +328,7 @@ async function deleteFile(): Promise<void> {
overflow: hidden; overflow: hidden;
> .ext { > .ext {
opacity: 0.5; opacity: 0.7;
} }
} }
} }

View file

@ -1,10 +1,10 @@
<template> <template>
<div <div
class="rghtznwe" class="rghtznwe"
:class="{ draghover }" :class="{ draghover, isSelected }"
draggable="true" draggable="true"
:title="title" :title="title"
@click="onClick" @click="selected"
@contextmenu.stop="onContextmenu" @contextmenu.stop="onContextmenu"
@mouseover="onMouseover" @mouseover="onMouseover"
@mouseout="onMouseout" @mouseout="onMouseout"
@ -15,15 +15,16 @@
@dragstart="onDragstart" @dragstart="onDragstart"
@dragend="onDragend" @dragend="onDragend"
> >
<div class="thumbnail" @click.stop="emit('move', folder)">
<i class="fas fa-folder-open fa-fw hover"></i>
<i class="fas fa-folder fa-fw"></i>
</div>
<p class="name"> <p class="name">
<template v-if="hover"><i class="fas fa-folder-open fa-fw"></i></template>
<template v-if="!hover"><i class="fas fa-folder fa-fw"></i></template>
{{ folder.name }} {{ folder.name }}
</p> </p>
<p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload">
{{ i18n.ts.uploadFolder }} {{ i18n.ts.uploadFolder }}
</p> </p>
<button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button>
</div> </div>
</template> </template>
@ -44,7 +45,7 @@ const props = withDefaults(defineProps<{
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'chosen', v: foundkey.entities.DriveFolder): void; (ev: 'chosen', v: foundkey.entities.DriveFolder, extendSelection: boolean): void;
(ev: 'move', v: foundkey.entities.DriveFolder): void; (ev: 'move', v: foundkey.entities.DriveFolder): void;
(ev: 'upload', file: File, folder: foundkey.entities.DriveFolder); (ev: 'upload', file: File, folder: foundkey.entities.DriveFolder);
(ev: 'removeFile', v: foundkey.entities.DriveFile['id']): void; (ev: 'removeFile', v: foundkey.entities.DriveFile['id']): void;
@ -59,20 +60,10 @@ const isDragging = ref(false);
const title = computed(() => props.folder.name); const title = computed(() => props.folder.name);
function checkboxClicked() { function selected(ev: MouseEvent) {
emit('chosen', props.folder); if (props.selectMode) {
} emit('chosen', props.folder, ev.ctrlKey);
}
function onClick() {
emit('move', props.folder);
}
function onMouseover() {
hover.value = true;
}
function onMouseout() {
hover.value = false;
} }
function onDragover(ev: DragEvent) { function onDragover(ev: DragEvent) {
@ -260,29 +251,34 @@ function onContextmenu(ev: MouseEvent) {
.rghtznwe { .rghtznwe {
position: relative; position: relative;
padding: 8px; padding: 8px;
height: 64px; min-height: 180px;
border-radius: 4px; border-radius: 8px;
&, * { &, * {
cursor: pointer; cursor: pointer;
} }
*:not(.checkbox) { > .thumbnail {
pointer-events: none; width: 110px;
} height: 110px;
margin: auto;
> .checkbox { /* same style as drive-file-thumbnail.vue */
position: absolute; position: relative;
bottom: 8px; display: flex;
right: 8px; background: var(--panel);
width: 16px; border-radius: 8px;
height: 16px; overflow: clip;
background: #fff;
border: solid 1px #000;
&.checked { > i {
background: var(--accent); pointer-events: none;
margin: auto;
font-size: 33px;
color: #777;
} }
&:not(:hover) > i.hover,
&:hover > i:not(.hover) { display: none; }
} }
&.draghover { &.draghover {
@ -299,23 +295,37 @@ function onContextmenu(ev: MouseEvent) {
} }
} }
> .name { &.isSelected {
margin: 0; background: var(--accent);
font-size: 0.9em;
color: var(--desktopDriveFolderFg);
> i { &:hover {
margin-right: 4px; background: var(--accentLighten);
margin-left: 2px;
text-align: left;
} }
> .name {
color: #fff;
}
> .thumbnail {
color: #fff;
}
}
> .name {
display: block;
margin: 4px 0 0 0;
font-size: 0.8em;
text-align: center;
word-break: break-all;
color: var(--fg);
overflow: hidden;
} }
> .upload { > .upload {
margin: 4px 4px; margin: 4px 4px;
font-size: 0.8em; font-size: 0.8em;
text-align: right; text-align: center;
color: var(--desktopDriveFolderFg); opacity: 0.7;
} }
} }
</style> </style>

View file

@ -52,8 +52,9 @@
:key="f.id" :key="f.id"
v-anim="i" v-anim="i"
:file="f" :file="f"
:is-selected="selectedFiles.some(x => x.id === f.id)" :select-mode="select !== 'folder'"
@chosen="chooseFile" :is-selected="selected.some(x => x.id === f.id)"
@chosen="choose"
@dragstart="isDragSource = true" @dragstart="isDragSource = true"
@dragend="isDragSource = false" @dragend="isDragSource = false"
/> />
@ -62,8 +63,9 @@
:key="f.id" :key="f.id"
v-anim="i" v-anim="i"
:folder="f" :folder="f"
:is-selected="selectedFolders.some(x => x.id === f.id)" :select-mode="select !== 'file'"
@chosen="chooseFolder" :is-selected="selected.some(x => x.id === f.id)"
@chosen="choose"
@move="move" @move="move"
@upload="upload" @upload="upload"
@removeFile="removeFile" @removeFile="removeFile"
@ -106,7 +108,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'selected', v: foundkey.entities.DriveFile | foundkey.entities.DriveFolder): void; (ev: 'selected', v: foundkey.entities.DriveFile | foundkey.entities.DriveFolder): void;
(ev: 'change-selection', v: foundkey.entities.DriveFile[] | foundkey.entities.DriveFolder[]): void; (ev: 'change-selection', v: Array<foundkey.entities.DriveFile | foundkey.entities.DriveFolder>): void;
(ev: 'move-root'): void; (ev: 'move-root'): void;
(ev: 'cd', v: foundkey.entities.DriveFolder | null): void; (ev: 'cd', v: foundkey.entities.DriveFolder | null): void;
(ev: 'open-folder', v: foundkey.entities.DriveFolder): void; (ev: 'open-folder', v: foundkey.entities.DriveFolder): void;
@ -121,8 +123,7 @@ const connection = stream.useChannel('drive');
let folder = $ref<foundkey.entities.DriveFolder | null>(null); let folder = $ref<foundkey.entities.DriveFolder | null>(null);
let hierarchyFolders = $ref<foundkey.entities.DriveFolder[]>([]); let hierarchyFolders = $ref<foundkey.entities.DriveFolder[]>([]);
let selectedFiles = $ref<foundkey.entities.DriveFile[]>([]); let selected = $ref<Array<foundkey.entities.DriveFile | foundkey.entities.DriveFolder>>([]);
let selectedFolders = $ref<foundkey.entities.DriveFolder[]>([]);
let keepOriginal = $ref<boolean>(defaultStore.state.keepOriginalUploading); let keepOriginal = $ref<boolean>(defaultStore.state.keepOriginalUploading);
// //
@ -356,42 +357,49 @@ function upload(file: File, folderToUpload?: foundkey.entities.DriveFolder | nul
uploadFile(file, folderToUpload?.id ?? null, undefined, keepOriginal); uploadFile(file, folderToUpload?.id ?? null, undefined, keepOriginal);
} }
function chooseFile(file: foundkey.entities.DriveFile) { function choose(choice: foundkey.entities.DriveFile | foundkey.entities.DriveFolder, extendSelection: boolean) {
const isAlreadySelected = selectedFiles.some(f => f.id === file.id); const alreadySelected = selectedFiles.some(f => f.id === file.id);
if (props.multiple) {
if (isAlreadySelected) {
selectedFiles = selectedFiles.filter(f => f.id !== file.id);
} else {
selectedFiles.push(file);
}
emit('change-selection', selectedFiles);
} else {
if (isAlreadySelected) {
emit('selected', file);
} else {
selectedFiles = [file];
emit('change-selection', [file]);
}
}
}
function chooseFolder(folderToChoose: foundkey.entities.DriveFolder) { const action = (() => {
const isAlreadySelected = selectedFolders.some(f => f.id === folderToChoose.id); if (props.select != null) {
if (props.multiple) { // file picker mode, extendSelection is disregarded
if (isAlreadySelected) { if (props.multiple && alreadySelected) {
selectedFolders = selectedFolders.filter(f => f.id !== folderToChoose.id); return 'remove';
} else if (props.multiple) {
return 'add';
} else if (!props.multiple && alreadySelected) {
return 'emit';
} else {
return 'set';
}
} else { } else {
selectedFolders.push(folderToChoose); // explorer mode, props.multiple is disregarded
} if (extendSelection && alreadySelected) {
emit('change-selection', selectedFolders); return 'remove';
} else { } else if (extendSelection) {
if (isAlreadySelected) { return 'add';
emit('selected', folderToChoose); } else if (!alreadySelected) {
} else { return 'set';
selectedFolders = [folderToChoose]; }
emit('change-selection', [folderToChoose]); // already selected && ! extend selection is a noop
} }
})();
switch (action) {
case 'emit':
emit('selected', choice);
return; // don't emit the change-selection event
case 'add':
selected.push(choice);
break;
case 'set':
selected = [choice];
break;
case 'remove':
selected = selected.filter(f => f.id !== choice.id);
break;
} }
emit('change-selection', selected);
} }
function move(target?: string | foundkey.entities.DriveFolder) { function move(target?: string | foundkey.entities.DriveFolder) {