Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
Go + Wails v3 + Vue 3 + SQLite + FreeRDP3 (purego) 183 tests, 76 source files, 9,910 lines of code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
2.7 KiB
Vue
98 lines
2.7 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="visible"
|
|
class="fixed inset-0 z-50"
|
|
@click="close"
|
|
@contextmenu.prevent="close"
|
|
>
|
|
<div
|
|
ref="menuRef"
|
|
class="fixed bg-[#161b22] border border-[#30363d] rounded-lg shadow-2xl py-1 min-w-[160px] overflow-hidden"
|
|
:style="{ left: position.x + 'px', top: position.y + 'px' }"
|
|
@click.stop
|
|
>
|
|
<template v-for="(item, idx) in items" :key="idx">
|
|
<!-- Separator -->
|
|
<div v-if="item.separator" class="my-1 border-t border-[#30363d]" />
|
|
|
|
<!-- Menu item -->
|
|
<button
|
|
v-else
|
|
class="w-full flex items-center gap-2.5 px-3 py-1.5 text-xs text-left transition-colors cursor-pointer"
|
|
:class="item.danger
|
|
? 'text-[var(--wraith-accent-red)] hover:bg-[var(--wraith-accent-red)]/10'
|
|
: 'text-[var(--wraith-text-secondary)] hover:bg-[#30363d] hover:text-[var(--wraith-text-primary)]'
|
|
"
|
|
@click="handleClick(item)"
|
|
>
|
|
<span v-if="item.icon" class="w-4 h-4 flex items-center justify-center shrink-0" v-html="item.icon" />
|
|
<span class="flex-1">{{ item.label }}</span>
|
|
<span v-if="item.shortcut" class="text-[10px] text-[var(--wraith-text-muted)]">{{ item.shortcut }}</span>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, nextTick } from "vue";
|
|
|
|
export interface ContextMenuItem {
|
|
label?: string;
|
|
icon?: string;
|
|
shortcut?: string;
|
|
danger?: boolean;
|
|
separator?: boolean;
|
|
action?: () => void;
|
|
}
|
|
|
|
const visible = ref(false);
|
|
const position = ref({ x: 0, y: 0 });
|
|
const items = ref<ContextMenuItem[]>([]);
|
|
const menuRef = ref<HTMLDivElement | null>(null);
|
|
|
|
function open(event: MouseEvent, menuItems: ContextMenuItem[]): void {
|
|
items.value = menuItems;
|
|
visible.value = true;
|
|
|
|
// Position at cursor, adjusting if near viewport edges
|
|
nextTick(() => {
|
|
const menu = menuRef.value;
|
|
if (!menu) {
|
|
position.value = { x: event.clientX, y: event.clientY };
|
|
return;
|
|
}
|
|
|
|
let x = event.clientX;
|
|
let y = event.clientY;
|
|
|
|
const menuWidth = menu.offsetWidth;
|
|
const menuHeight = menu.offsetHeight;
|
|
|
|
if (x + menuWidth > window.innerWidth) {
|
|
x = window.innerWidth - menuWidth - 4;
|
|
}
|
|
if (y + menuHeight > window.innerHeight) {
|
|
y = window.innerHeight - menuHeight - 4;
|
|
}
|
|
|
|
position.value = { x, y };
|
|
});
|
|
}
|
|
|
|
function close(): void {
|
|
visible.value = false;
|
|
}
|
|
|
|
function handleClick(item: ContextMenuItem): void {
|
|
if (item.action) {
|
|
item.action();
|
|
}
|
|
close();
|
|
}
|
|
|
|
defineExpose({ open, close, visible });
|
|
</script>
|