pull/3343/merge
Omar Hussein 2025-03-20 20:24:04 +08:00 committed by GitHub
commit 70e205f773
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 156 additions and 7 deletions

View File

@ -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>

View File

@ -8,6 +8,7 @@
@dragover="dragOver"
@drop="drop"
@click="itemClick"
@contextmenu="contextMenu"
:data-dir="isDir"
:data-type="type"
:aria-label="name"
@ -220,6 +221,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
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) => {
if (!singleClick.value && fileStore.selectedCount !== 0)
event.preventDefault();

View File

@ -5,6 +5,7 @@
</div>
<div class="card-content">
<p>{{ $t("prompts.moveMessage") }}</p>
<file-list
ref="fileList"
@update:selected="(val) => (dest = val)"

View File

@ -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;
}

View File

@ -17,6 +17,7 @@
@import "./mobile.css";
@import "./epubReader.css";
@import "./mdPreview.css";
@import "./context-menu.css";
/* For testing only
:focus {
@ -456,4 +457,4 @@ html[dir="rtl"] .card-content .small + input {
html[dir="rtl"] .card.floating .card-content .file-list {
direction: ltr;
text-align: left;
}
}

View File

@ -208,7 +208,10 @@
<h2 v-if="fileStore.req?.numDirs ?? false">
{{ t("files.folders") }}
</h2>
<div v-if="fileStore.req?.numDirs ?? false">
<div
v-if="fileStore.req?.numDirs ?? false"
@contextmenu="showContextMenu"
>
<item
v-for="item in dirs"
:key="base64(item.name)"
@ -224,8 +227,13 @@
</item>
</div>
<h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
<div v-if="fileStore.req?.numFiles ?? false">
<h2 v-if="fileStore.req?.numFiles ?? false">
{{ t("files.files") }}
</h2>
<div
v-if="fileStore.req?.numFiles ?? false"
@contextmenu="showContextMenu"
>
<item
v-for="item in files"
:key="base64(item.name)"
@ -240,6 +248,53 @@
>
</item>
</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
style="display: none"
@ -292,6 +347,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
import Search from "@/components/Search.vue";
import Item from "@/components/files/ListingItem.vue";
import ContextMenu from "@/components/ContextMenu.vue";
import {
computed,
inject,
@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
const dragCounter = ref<number>(0);
const width = ref<number>(window.innerWidth);
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")!;
@ -444,7 +502,7 @@ watch(req, () => {
onMounted(() => {
// Check the columns size for the first time.
colunmsResize();
columnsResize();
// How much every listing item affects the window height
setItemWeight();
@ -638,7 +696,7 @@ const paste = (event: Event) => {
action(overwrite, rename);
};
const colunmsResize = () => {
const columnsResize = () => {
// Update the columns size based on the window width.
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
if (items_ === null) return;
@ -841,7 +899,7 @@ const toggleMultipleSelection = () => {
};
const windowsResize = throttle(() => {
colunmsResize();
columnsResize();
width.value = window.innerWidth;
// Listing element is not displayed
@ -951,4 +1009,17 @@ const fillWindow = (fit = false) => {
// Set the number of displayed items
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>