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

View file

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

View file

@ -52,8 +52,9 @@
:key="f.id"
v-anim="i"
:file="f"
:is-selected="selectedFiles.some(x => x.id === f.id)"
@chosen="chooseFile"
:select-mode="select !== 'folder'"
:is-selected="selected.some(x => x.id === f.id)"
@chosen="choose"
@dragstart="isDragSource = true"
@dragend="isDragSource = false"
/>
@ -62,8 +63,9 @@
:key="f.id"
v-anim="i"
:folder="f"
:is-selected="selectedFolders.some(x => x.id === f.id)"
@chosen="chooseFolder"
:select-mode="select !== 'file'"
:is-selected="selected.some(x => x.id === f.id)"
@chosen="choose"
@move="move"
@upload="upload"
@removeFile="removeFile"
@ -106,7 +108,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{
(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: 'cd', v: foundkey.entities.DriveFolder | null): 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 hierarchyFolders = $ref<foundkey.entities.DriveFolder[]>([]);
let selectedFiles = $ref<foundkey.entities.DriveFile[]>([]);
let selectedFolders = $ref<foundkey.entities.DriveFolder[]>([]);
let selected = $ref<Array<foundkey.entities.DriveFile | foundkey.entities.DriveFolder>>([]);
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);
}
function chooseFile(file: foundkey.entities.DriveFile) {
const isAlreadySelected = 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 choose(choice: foundkey.entities.DriveFile | foundkey.entities.DriveFolder, extendSelection: boolean) {
const alreadySelected = selectedFiles.some(f => f.id === file.id);
function chooseFolder(folderToChoose: foundkey.entities.DriveFolder) {
const isAlreadySelected = selectedFolders.some(f => f.id === folderToChoose.id);
if (props.multiple) {
if (isAlreadySelected) {
selectedFolders = selectedFolders.filter(f => f.id !== folderToChoose.id);
const action = (() => {
if (props.select != null) {
// file picker mode, extendSelection is disregarded
if (props.multiple && alreadySelected) {
return 'remove';
} else if (props.multiple) {
return 'add';
} else if (!props.multiple && alreadySelected) {
return 'emit';
} else {
selectedFolders.push(folderToChoose);
return 'set';
}
emit('change-selection', selectedFolders);
} else {
if (isAlreadySelected) {
emit('selected', folderToChoose);
} else {
selectedFolders = [folderToChoose];
emit('change-selection', [folderToChoose]);
// explorer mode, props.multiple is disregarded
if (extendSelection && alreadySelected) {
return 'remove';
} else if (extendSelection) {
return 'add';
} else if (!alreadySelected) {
return 'set';
}
// 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) {