forked from FoundKeyGang/FoundKey
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<{
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue