import { LatLng } from "leaflet"; import { uniqBy } from "lodash-es"; import { FileIcon, LinkIcon, LoaderIcon, type LucideIcon, MapPinIcon, Maximize2Icon, MicIcon, MoreHorizontalIcon, PlusIcon, } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useDebounce } from "react-use"; import { LinkMemoDialog, LocationDialog } from "@/components/MemoMetadata"; import { useReverseGeocoding } from "@/components/map"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, 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 { 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 [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 { state: locationState, locationInitialized, handlePositionChange: handleLocationPositionChange, getLocation, reset: locationReset, updateCoordinate, setPlaceholder, } = location; const [debouncedPosition, setDebouncedPosition] = useState(undefined); useDebounce( () => { setDebouncedPosition(locationState.position); }, 1000, [locationState.position], ); const { data: displayName } = useReverseGeocoding(debouncedPosition?.lat, debouncedPosition?.lng); useEffect(() => { if (displayName) { setPlaceholder(displayName); } }, [displayName, setPlaceholder]); const isUploading = selectingFlag || isUploadingProp; const handleOpenLinkDialog = useCallback(() => { setLinkDialogOpen(true); }, []); const handleLocationClick = useCallback(() => { setLocationDialogOpen(true); if (!initialLocation && !locationInitialized) { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { handleLocationPositionChange(new LatLng(position.coords.latitude, position.coords.longitude)); }, (error) => { console.error("Geolocation error:", error); }, ); } } }, [initialLocation, locationInitialized, handleLocationPositionChange]); const handleLocationConfirm = useCallback(() => { const newLocation = getLocation(); if (newLocation) { onLocationChange(newLocation); setLocationDialogOpen(false); } }, [getLocation, onLocationChange]); const handleLocationCancel = useCallback(() => { locationReset(); setLocationDialogOpen(false); }, [locationReset]); const handleToggleFocusMode = useCallback(() => { onToggleFocusMode?.(); setMoreSubmenuOpen(false); }, [onToggleFocusMode]); const menuItems = useMemo( () => [ { key: "upload", label: t("editor.insert-menu.upload-file"), icon: FileIcon, onClick: handleUploadClick, }, { key: "record-audio", label: t("editor.audio-recorder.trigger"), icon: MicIcon, onClick: () => props.onAudioRecorderClick?.(), }, { key: "link", label: t("editor.insert-menu.link-memo"), icon: LinkIcon, onClick: handleOpenLinkDialog, }, { key: "location", label: t("editor.insert-menu.add-location"), icon: MapPinIcon, onClick: handleLocationClick, }, ] satisfies Array<{ key: string; label: string; icon: LucideIcon; onClick: () => void }>, [handleLocationClick, handleOpenLinkDialog, handleUploadClick, props, t], ); return ( <> {menuItems.slice(0, 2).map((item) => ( {item.label} ))} {menuItems.slice(2).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;