feat/context-menu #10

Merged
tristonarmstrong merged 2 commits from feat/context-menu into main 2024-10-12 17:32:33 +00:00
6 changed files with 153 additions and 22 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -11,6 +11,7 @@
"commit": "cz" "commit": "cz"
}, },
"dependencies": { "dependencies": {
"@kaioken-core/hooks": "^0.0.8",
"@tauri-apps/api": "^1", "@tauri-apps/api": "^1",
"caniuse-lite": "^1.0.30001667", "caniuse-lite": "^1.0.30001667",
"kaioken": "^0.32.1", "kaioken": "^0.32.1",

View File

@ -9,6 +9,7 @@ export function App() {
return ( return (
<ToastContextProvider> <ToastContextProvider>
<MemoInfyCanvas /> <MemoInfyCanvas />
<div id="context-menu-portal" />
</ToastContextProvider> </ToastContextProvider>
) )
} }

View File

@ -0,0 +1,52 @@
import { useClickOutside, useKeyStroke, useMouse } from "@kaioken-core/hooks";
import { Portal, signal, useEffect, useRef } from "kaioken";
namespace ContextMenuPortal {
export interface Props {
children: JSX.Children
open: boolean
closeAction: (() => void) | null | undefined
}
}
export function ContextMenuPortal({ children, open, closeAction }: ContextMenuPortal.Props) {
const { mouse } = useMouse()
const pos = signal({ x: 0, y: 0 })
const ref = useRef<HTMLDivElement>(null)
useClickOutside(ref, () => {
closeAction?.()
})
useEffect(() => {
if (!open) return
pos.value = { x: mouse.x, y: mouse.y }
function _handleEscapeKey(e: KeyboardEvent) {
if (e.key !== "Escape") return
closeAction?.()
}
document.addEventListener("keydown", _handleEscapeKey)
return () => {
document.removeEventListener("keydown", _handleEscapeKey)
}
}, [open])
if (!open) return null
return (
<Portal
container={() => document.querySelector("#context-menu-portal")!}
>
<div
ref={ref}
className="fixed z-10 select-none shadow-md rounded min-w-[150px]"
style={{
left: `${pos.value.x}px`,
top: `${pos.value.y}px`,
zIndex: `1000`
}}
>
{children}
</div>
</Portal>
)
}

View File

@ -5,6 +5,7 @@ import { LayerEnum } from "../utils/enums"
import images, { ImageCardType } from "../signals/images" import images, { ImageCardType } from "../signals/images"
import { updateLocalStorage } from "../utils/localStorage" import { updateLocalStorage } from "../utils/localStorage"
import { useThemeDetector } from "../utils/useThemeDetector" import { useThemeDetector } from "../utils/useThemeDetector"
import { ContextMenuPortal } from "./ContextMenuPortal"
namespace ImageCard { namespace ImageCard {
export interface ImageCardProps { export interface ImageCardProps {
@ -22,6 +23,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
const offsetY = useRef(0) const offsetY = useRef(0)
const initialResizeX = useRef(0) const initialResizeX = useRef(0)
const initialResizeY = useRef(0) const initialResizeY = useRef(0)
const openContextMenu = signal(false)
function debounceLSUpdate(time?: number) { function debounceLSUpdate(time?: number) {
debounce(() => { debounce(() => {
@ -93,8 +95,19 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
ImagesSignal.default.images.notify() ImagesSignal.default.images.notify()
} }
function _handleMouseClick(e: MouseEvent) {
e.preventDefault()
openContextMenu.value = !openContextMenu.value
}
function _handleContextClose() {
openContextMenu.value = false
}
return ( return (
<>
<div <div
oncontextmenu={_handleMouseClick}
onmousedown={_handleMouseDown} onmousedown={_handleMouseDown}
className="select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#1c1c1c] absolute" className="select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#1c1c1c] absolute"
style={{ style={{
@ -117,6 +130,31 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
<ExpandIcon cb={_handleResizeMouseDown} /> <ExpandIcon cb={_handleResizeMouseDown} />
</div > </div >
<ContextMenuPortal open={openContextMenu.value} closeAction={_handleContextClose}>
<div className="bg-[#3c3c3c] flex flex-col rounded">
<div className="flex justify-between items-center">
<div className="text-md dark:text-[#999] text-black px-2 py-1">
{item.title}
</div>
</div>
<hr className="border dark:border-[#2c2c2c] border-[#ddd] m-0 p-0" />
<div>
<ul>
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
<button onclick={_handleClose} className="text-md dark:text-[#999] text-black">
Delete
</button>
</li>
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
</li>
</ul>
</div>
</div>
</ContextMenuPortal>
</>
) )
} }

View File

@ -9,6 +9,7 @@ import { ChangeEvent } from "tiny-markdown-editor"
import { Divider } from "./Divider" import { Divider } from "./Divider"
import { ExportIcon } from "./icons/ExportIcon" import { ExportIcon } from "./icons/ExportIcon"
import { createFileAndExport } from "../utils/createFileAndExport" import { createFileAndExport } from "../utils/createFileAndExport"
import { ContextMenuPortal } from "./ContextMenuPortal"
namespace NoteCard { namespace NoteCard {
export interface NoteCardProps { export interface NoteCardProps {
@ -26,6 +27,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
const offsetY = useRef(0) const offsetY = useRef(0)
const initialResizeX = useRef(0) const initialResizeX = useRef(0)
const initialResizeY = useRef(0) const initialResizeY = useRef(0)
const openContextMenu = signal(false)
const { debounce } = useDebounce() const { debounce } = useDebounce()
@ -113,6 +115,15 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
createFileAndExport("Note", item.contents, "text/markdown") createFileAndExport("Note", item.contents, "text/markdown")
} }
function _handleMouseClick(e: MouseEvent) {
e.preventDefault()
openContextMenu.value = !openContextMenu.value
}
function _handleContextClose() {
openContextMenu.value = false
}
const cardPositionStyle = { const cardPositionStyle = {
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`, zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
width: `${item.dimensions.w}px`, width: `${item.dimensions.w}px`,
@ -127,6 +138,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
return ( return (
<div <div
oncontextmenu={_handleMouseClick}
onmousedown={_handleFocusCard} onmousedown={_handleFocusCard}
style={cardPositionStyle} style={cardPositionStyle}
className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#efeff0] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute" className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#efeff0] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
@ -158,6 +170,33 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
<MarkDownEditor initial={item.contents} onChange={_handleMdChange} /> <MarkDownEditor initial={item.contents} onChange={_handleMdChange} />
<ExpandIcon cb={_handleResizeMouseDown} /> <ExpandIcon cb={_handleResizeMouseDown} />
</div> </div>
<ContextMenuPortal open={openContextMenu.value} closeAction={_handleContextClose}>
<div className="bg-[#3c3c3c] flex flex-col rounded">
<div className="flex justify-between items-center">
<div className="text-md dark:text-[#999] text-black px-2 py-1">
{item.title}
</div>
</div>
<hr className="border dark:border-[#2c2c2c] border-[#ddd] m-0 p-0" />
<div>
<ul>
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
<button onclick={_handleClose} className="text-md dark:text-[#999] text-black">
Delete
</button>
</li>
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
<button onclick={_handleExportClick} className="text-md dark:text-[#999] text-black">
export
</button>
</li>
</ul>
</div>
</div>
</ContextMenuPortal>
</div > </div >
) )