Merge e65df8b244
into 35d1c09243
commit
70e205f773
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
v-show="show"
|
||||||
|
:style="{
|
||||||
|
top: `${props.pos.y}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["hide"]);
|
||||||
|
const props = defineProps<{ show: boolean; pos: { x: number; y: number } }>();
|
||||||
|
const contextMenu = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const left = computed(() => {
|
||||||
|
return Math.min(
|
||||||
|
props.pos.x,
|
||||||
|
window.innerWidth - (contextMenu.value?.clientWidth ?? 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
emit("hide");
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
document.addEventListener("click", hideContextMenu);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -8,6 +8,7 @@
|
||||||
@dragover="dragOver"
|
@dragover="dragOver"
|
||||||
@drop="drop"
|
@drop="drop"
|
||||||
@click="itemClick"
|
@click="itemClick"
|
||||||
|
@contextmenu="contextMenu"
|
||||||
:data-dir="isDir"
|
:data-dir="isDir"
|
||||||
:data-type="type"
|
:data-type="type"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
|
@ -220,6 +221,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
|
||||||
else click(event);
|
else click(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
fileStore.selected.length === 0 ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
fileStore.selected.indexOf(props.index) === -1
|
||||||
|
) {
|
||||||
|
click(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const click = (event: Event | KeyboardEvent) => {
|
const click = (event: Event | KeyboardEvent) => {
|
||||||
if (!singleClick.value && fileStore.selectedCount !== 0)
|
if (!singleClick.value && fileStore.selectedCount !== 0)
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<p>{{ $t("prompts.moveMessage") }}</p>
|
||||||
<file-list
|
<file-list
|
||||||
ref="fileList"
|
ref="fileList"
|
||||||
@update:selected="(val) => (dest = val)"
|
@update:selected="(val) => (dest = val)"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 220px;
|
||||||
|
border: 1px solid var(--borderSecondary);
|
||||||
|
box-shadow: 0 2px 4px var(--borderPrimary);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
@import "./mobile.css";
|
@import "./mobile.css";
|
||||||
@import "./epubReader.css";
|
@import "./epubReader.css";
|
||||||
@import "./mdPreview.css";
|
@import "./mdPreview.css";
|
||||||
|
@import "./context-menu.css";
|
||||||
|
|
||||||
/* For testing only
|
/* For testing only
|
||||||
:focus {
|
:focus {
|
||||||
|
|
|
@ -208,7 +208,10 @@
|
||||||
<h2 v-if="fileStore.req?.numDirs ?? false">
|
<h2 v-if="fileStore.req?.numDirs ?? false">
|
||||||
{{ t("files.folders") }}
|
{{ t("files.folders") }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="fileStore.req?.numDirs ?? false">
|
<div
|
||||||
|
v-if="fileStore.req?.numDirs ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in dirs"
|
v-for="item in dirs"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
|
@ -224,8 +227,13 @@
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
|
<h2 v-if="fileStore.req?.numFiles ?? false">
|
||||||
<div v-if="fileStore.req?.numFiles ?? false">
|
{{ t("files.files") }}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
v-if="fileStore.req?.numFiles ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in files"
|
v-for="item in files"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
|
@ -240,6 +248,53 @@
|
||||||
>
|
>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
<context-menu
|
||||||
|
:show="isContextMenuVisible"
|
||||||
|
:pos="contextMenuPos"
|
||||||
|
@hide="hideContextMenu"
|
||||||
|
>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.share"
|
||||||
|
icon="share"
|
||||||
|
:label="t('buttons.share')"
|
||||||
|
show="share"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.rename"
|
||||||
|
icon="mode_edit"
|
||||||
|
:label="t('buttons.rename')"
|
||||||
|
show="rename"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.copy"
|
||||||
|
id="copy-button"
|
||||||
|
icon="content_copy"
|
||||||
|
:label="t('buttons.copyFile')"
|
||||||
|
show="copy"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.move"
|
||||||
|
id="move-button"
|
||||||
|
icon="forward"
|
||||||
|
:label="t('buttons.moveFile')"
|
||||||
|
show="move"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.delete"
|
||||||
|
id="delete-button"
|
||||||
|
icon="delete"
|
||||||
|
:label="t('buttons.delete')"
|
||||||
|
show="delete"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.download"
|
||||||
|
icon="file_download"
|
||||||
|
:label="t('buttons.download')"
|
||||||
|
@action="download"
|
||||||
|
:counter="fileStore.selectedCount"
|
||||||
|
/>
|
||||||
|
<action icon="info" :label="t('buttons.info')" show="info" />
|
||||||
|
</context-menu>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
style="display: none"
|
style="display: none"
|
||||||
|
@ -292,6 +347,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
|
import ContextMenu from "@/components/ContextMenu.vue";
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
|
||||||
const dragCounter = ref<number>(0);
|
const dragCounter = ref<number>(0);
|
||||||
const width = ref<number>(window.innerWidth);
|
const width = ref<number>(window.innerWidth);
|
||||||
const itemWeight = ref<number>(0);
|
const itemWeight = ref<number>(0);
|
||||||
|
const isContextMenuVisible = ref<boolean>(false);
|
||||||
|
const contextMenuPos = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
|
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
|
@ -444,7 +502,7 @@ watch(req, () => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Check the columns size for the first time.
|
// Check the columns size for the first time.
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
|
|
||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
setItemWeight();
|
setItemWeight();
|
||||||
|
@ -638,7 +696,7 @@ const paste = (event: Event) => {
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const colunmsResize = () => {
|
const columnsResize = () => {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||||
if (items_ === null) return;
|
if (items_ === null) return;
|
||||||
|
@ -841,7 +899,7 @@ const toggleMultipleSelection = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const windowsResize = throttle(() => {
|
const windowsResize = throttle(() => {
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
width.value = window.innerWidth;
|
width.value = window.innerWidth;
|
||||||
|
|
||||||
// Listing element is not displayed
|
// Listing element is not displayed
|
||||||
|
@ -951,4 +1009,17 @@ const fillWindow = (fit = false) => {
|
||||||
// Set the number of displayed items
|
// Set the number of displayed items
|
||||||
showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
|
showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showContextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
isContextMenuVisible.value = true;
|
||||||
|
contextMenuPos.value = {
|
||||||
|
x: event.clientX + 8,
|
||||||
|
y: event.clientY + Math.floor(window.scrollY),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
isContextMenuVisible.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue