client: combine selection of files & folders
This commit is contained in:
parent
df9064c284
commit
d26e2588e3
3 changed files with 105 additions and 89 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 onClick() {
|
||||
emit('move', props.folder);
|
||||
}
|
||||
|
||||
function onMouseover() {
|
||||
hover.value = true;
|
||||
}
|
||||
|
||||
function onMouseout() {
|
||||
hover.value = false;
|
||||
function selected(ev: MouseEvent) {
|
||||
if (props.selectMode) {
|
||||
emit('chosen', props.folder, ev.ctrlKey);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
pointer-events: none;
|
||||
}
|
||||
> .thumbnail {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: auto;
|
||||
|
||||
> .checkbox {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #fff;
|
||||
border: solid 1px #000;
|
||||
/* same style as drive-file-thumbnail.vue */
|
||||
position: relative;
|
||||
display: flex;
|
||||
background: var(--panel);
|
||||
border-radius: 8px;
|
||||
overflow: clip;
|
||||
|
||||
&.checked {
|
||||
background: var(--accent);
|
||||
> i {
|
||||
pointer-events: none;
|
||||
margin: auto;
|
||||
font-size: 33px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
&: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>
|
||||
|
|
|
@ -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 {
|
||||
return 'set';
|
||||
}
|
||||
} else {
|
||||
selectedFolders.push(folderToChoose);
|
||||
}
|
||||
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) {
|
||||
|
|
Loading…
Reference in a new issue