import { LatLng } from "leaflet"; import { uniqBy } from "lodash-es"; import { FileIcon, LinkIcon, LoaderIcon, type LucideIcon, MapPinIcon, Maximize2Icon, MoreHorizontalIcon, PlusIcon, TableIcon, } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useDebounce } from "react-use"; import { useReverseGeocoding } from "@/components/map"; import TableEditorDialog from "@/components/TableEditorDialog"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, useDropdownMenuSubHoverDelay, } from "@/components/ui/dropdown-menu"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; import { LinkMemoDialog, LocationDialog } from "../components"; import { useFileUpload, useLinkMemo, useLocation } from "../hooks"; import { useEditorContext } from "../state"; import type { InsertMenuProps } from "../types"; import type { LocalFile } from "../types/attachment"; const InsertMenu = (props: InsertMenuProps) => { const t = useTranslate(); const { state, actions, dispatch } = useEditorContext(); const { location: initialLocation, onLocationChange, onToggleFocusMode, isUploading: isUploadingProp } = props; const [linkDialogOpen, setLinkDialogOpen] = useState(false); const [locationDialogOpen, setLocationDialogOpen] = useState(false); const [tableDialogOpen, setTableDialogOpen] = useState(false); const [moreSubmenuOpen, setMoreSubmenuOpen] = useState(false); const { handleTriggerEnter, handleTriggerLeave, handleContentEnter, handleContentLeave } = useDropdownMenuSubHoverDelay( 150, setMoreSubmenuOpen, ); const { fileInputRef, selectingFlag, handleFileInputChange, handleUploadClick } = useFileUpload((newFiles: LocalFile[]) => { newFiles.forEach((file) => dispatch(actions.addLocalFile(file))); }); const linkMemo = useLinkMemo({ isOpen: linkDialogOpen, currentMemoName: props.memoName, existingRelations: state.metadata.relations, onAddRelation: (relation: MemoRelation) => { dispatch(actions.setMetadata({ relations: uniqBy([...state.metadata.relations, relation], (r) => r.relatedMemo?.name) })); setLinkDialogOpen(false); }, }); const location = useLocation(props.location); const [debouncedPosition, setDebouncedPosition] = useState(undefined); useDebounce( () => { setDebouncedPosition(location.state.position); }, 1000, [location.state.position], ); const { data: displayName } = useReverseGeocoding(debouncedPosition?.lat, debouncedPosition?.lng); useEffect(() => { if (displayName) { location.setPlaceholder(displayName); } }, [displayName]); const isUploading = selectingFlag || isUploadingProp; const handleOpenLinkDialog = useCallback(() => { setLinkDialogOpen(true); }, []); const handleOpenTableDialog = useCallback(() => { setTableDialogOpen(true); }, []); const handleTableConfirm = useCallback( (markdown: string) => { props.onInsertText?.(markdown); }, [props], ); const handleLocationClick = useCallback(() => { setLocationDialogOpen(true); if (!initialLocation && !location.locationInitialized) { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { location.handlePositionChange(new LatLng(position.coords.latitude, position.coords.longitude)); }, (error) => { console.error("Geolocation error:", error); }, ); } } }, [initialLocation, location]); const handleLocationConfirm = useCallback(() => { const newLocation = location.getLocation(); if (newLocation) { onLocationChange(newLocation); setLocationDialogOpen(false); } }, [location, onLocationChange]); const handleLocationCancel = useCallback(() => { location.reset(); setLocationDialogOpen(false); }, [location]); const handlePositionChange = useCallback( (position: LatLng) => { location.handlePositionChange(position); }, [location], ); const handleToggleFocusMode = useCallback(() => { onToggleFocusMode?.(); setMoreSubmenuOpen(false); }, [onToggleFocusMode]); const menuItems = useMemo( () => [ { key: "upload", label: t("common.upload"), icon: FileIcon, onClick: handleUploadClick, }, { key: "table", label: "Table", icon: TableIcon, onClick: handleOpenTableDialog, }, { key: "link", label: t("tooltip.link-memo"), icon: LinkIcon, onClick: handleOpenLinkDialog, }, { key: "location", label: t("tooltip.select-location"), icon: MapPinIcon, onClick: handleLocationClick, }, ] satisfies Array<{ key: string; label: string; icon: LucideIcon; onClick: () => void }>, [handleLocationClick, handleOpenLinkDialog, handleOpenTableDialog, handleUploadClick, t], ); return ( <> {menuItems.map((item) => ( {item.label} ))} {/* View submenu with Focus Mode */} {t("common.more")} {t("editor.focus-mode")}
{t("editor.slash-commands")}
{/* Hidden file input */} ); }; export default InsertMenu;