forked from bai/curriculum-project-hub
0fd21e51f7
- 新 GridLibraryView:双击下钻文件夹/项目,面包屑+返回跳级,项目内文件同网格, 双击进 FileEditor 预览;工具条按上下文出新建文件夹/项目/文件 - 右键菜单(ContextMenu):打开/新建子文件夹/重命名/授权管理/详情/删除, 按节点 role 动态显隐;文件菜单:打开预览/下载/删除;空白处右键出新建+刷新 - GridCard 大图标卡片(琥珀文件夹/立方体项目/文档文件);授权与详情复用 GrantsPanel/OverviewPanel(Modal 加 maxW prop);Icon 补 5 个图标 - 管理后台 /database 保留树状 LibraryView 不动;后端零改动
56 lines
1.7 KiB
Svelte
56 lines
1.7 KiB
Svelte
<script lang="ts" module>
|
|
import type { IconName } from "./Icon.svelte";
|
|
|
|
export interface MenuItem {
|
|
readonly label: string;
|
|
readonly icon?: IconName;
|
|
readonly danger?: boolean;
|
|
readonly onclick: () => void;
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
/**
|
|
* 通用右键菜单:光标处弹出,点任意处/再次右键关闭。
|
|
* 位置做视口夹取(右侧/底部溢出时向内收)。
|
|
*/
|
|
import Icon from "./Icon.svelte";
|
|
|
|
let { x, y, items, onclose }: { x: number; y: number; items: readonly MenuItem[]; onclose: () => void } = $props();
|
|
|
|
const MENU_W = 178;
|
|
const ITEM_H = 34;
|
|
const px = $derived(
|
|
typeof window === "undefined" ? x : Math.max(4, Math.min(x, window.innerWidth - MENU_W - 8)),
|
|
);
|
|
const py = $derived(
|
|
typeof window === "undefined" ? y : Math.max(4, Math.min(y, window.innerHeight - items.length * ITEM_H - 20)),
|
|
);
|
|
</script>
|
|
|
|
<div
|
|
class="fixed inset-0 z-50"
|
|
role="presentation"
|
|
onclick={onclose}
|
|
oncontextmenu={(e) => { e.preventDefault(); onclose(); }}
|
|
>
|
|
<div
|
|
class="fixed rounded-xl border border-line-soft bg-panel py-1.5 shadow-[0_8px_28px_rgba(26,26,24,.12)]"
|
|
style="left:{px}px;top:{py}px;width:{MENU_W}px"
|
|
>
|
|
{#each items as item (item.label)}
|
|
<button
|
|
class="flex w-full items-center gap-2.5 px-3.5 py-[7px] text-left text-[12.5px] transition {item.danger
|
|
? 'text-danger hover:bg-hover'
|
|
: 'text-ink hover:bg-hover'}"
|
|
onclick={() => { onclose(); item.onclick(); }}
|
|
>
|
|
{#if item.icon}
|
|
<span class={item.danger ? "" : "text-ink-3"}><Icon name={item.icon} size={14} /></span>
|
|
{/if}
|
|
{item.label}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|