import { Editor } from "novel"; import { useAutoAnimate } from "@formkit/auto-animate/react"; import { MemoryWithImage, MemoryWithImages3, MemoryWithImages2, } from "@/assets/MemoryWithImages"; import { Input, InputWithIcon } from "../ui/input"; import { ArrowUpRight, Edit3, Loader, Minus, MoreHorizontal, Plus, Search, Sparkles, Text, Trash2, } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu"; import { useEffect, useMemo, useRef, useState } from "react"; import { Variant, useAnimate, motion } from "framer-motion"; import { SearchResult, useMemory } from "@/contexts/MemoryContext"; import { SpaceIcon } from "@/assets/Memories"; import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogHeader, DialogFooter, DialogClose, } from "../ui/dialog"; import useViewport from "@/hooks/useViewport"; import useTouchHold from "@/hooks/useTouchHold"; import { DialogTrigger } from "@radix-ui/react-dialog"; import { AddExistingMemoryToSpace, AddMemoryPage, MemorySelectedItem, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog"; import { ExpandedSpace } from "./ExpandedSpace"; import { StoredContent, StoredSpace } from "@/server/db/schema"; import { useDebounce } from "@/hooks/useDebounce"; import { NoteEdit } from "./EditNoteDialog"; import DeleteConfirmation from "./DeleteConfirmation"; export function MemoriesBar({ isOpen }: { isOpen: boolean }) { const [parent, enableAnimations] = useAutoAnimate(); const { spaces, deleteSpace, freeMemories, search } = useMemory(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [addMemoryState, setAddMemoryState] = useState< "page" | "note" | "space" | "existing-memory" | null >(null); const [expandedSpace, setExpandedSpace] = useState(null); const [searchQuery, setSearcyQuery] = useState(""); const [searchLoading, setSearchLoading] = useState(false); const query = useDebounce(searchQuery, 500); const [searchResults, setSearchResults] = useState([]); useEffect(() => { const q = query.trim(); if (q.length < 1) { setSearchResults([]); return; } setSearchLoading(true); (async () => { setSearchResults(await search(q)); setSearchLoading(false); })(); }, [query]); useEffect(() => { if (!isOpen) { setExpandedSpace(null) } }, [isOpen]) if (expandedSpace) { return ( setExpandedSpace(null)} // close={() => setExpandedSpace(null)} /> ); } return (

Your Memories

) : ( ) } className="bg-rgray-4 mt-2 w-full" value={searchQuery} onChange={(e) => setSearcyQuery(e.target.value)} />
e.preventDefault()}> { setAddMemoryState("page"); }} > Page to Memory { setAddMemoryState("note"); }} > Note { setAddMemoryState("space"); }} > Space
{query.trim().length > 0 ? ( <> {searchResults.map(({ type, space, memory }, i) => ( <> {type === "memory" && ( { setSearchResults(prev => prev.filter(i => i.memory?.id !== memory.id)) }} /> )} {type === "space" && ( { setSearchResults(prev => prev.filter(i => i.space?.id !== space.id)) deleteSpace(space.id) }} /> )} ))} ) : ( <> {spaces.map((space) => ( deleteSpace(space.id)} key={space.id} onClick={() => setExpandedSpace(space.id)} {...space} /> ))} {freeMemories.map((m) => ( ))} )}
); } const SpaceExitVariant: Variant = { opacity: 0, scale: 0, borderRadius: "50%", background: "var(--gray-1)", transition: { duration: 0.2, }, }; export function MemoryItem(props: StoredContent & { onDelete?: () => void; removeFromSpace?: () => Promise; }) { const { id, title, image, type, url, onDelete, removeFromSpace } = props const { deleteMemory } = useMemory() const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : ""; const [isDialogOpen, setIsDialogOpen] = useState(false); const [moreDropdownOpen, setMoreDropdownOpen] = useState(false) const touchEventProps = useTouchHold({ onHold() { setMoreDropdownOpen(true); }, }); return (
{ type === "note" ? ( ) : ( ) } {type === "page" ? { deleteMemory(id); onDelete?.() }} url={url} /> : type === "note" ? setIsDialogOpen(true)} onDelete={() => { deleteMemory(id); onDelete?.() }} /> : null}
{type === "page" ? ( window.open(url)} className="h-16 w-16" id={id.toString()} src={image!} onError={(e) => { (e.target as HTMLImageElement).src = "/icons/white_without_bg.png"; }} /> ) : type === "note" ? ( setIsDialogOpen(true)} className="h-16 w-16" /> ) : ( <> )}
setIsDialogOpen(false)} memory={props} />
); } export function SpaceItem({ name, id, onDelete, onClick, }: StoredSpace & { onDelete: () => void; onClick?: () => void }) { const { cachedMemories } = useMemory(); const [itemRef, animateItem] = useAnimate(); const { width } = useViewport(); const [moreDropdownOpen, setMoreDropdownOpen] = useState(false); const touchEventProps = useTouchHold({ onHold() { setMoreDropdownOpen(true); }, }); const spaceMemories = useMemo(() => { return cachedMemories.filter((m) => m.space === id); }, [cachedMemories]); const _name = name.length > 10 ? name.slice(0, 10) + "..." : name; return ( { onDelete(); return; if (!itemRef.current || width < 768) { onDelete(); return; } // const trash = document.querySelector("#trash")! as HTMLDivElement; // const trashBin = document.querySelector("#trash-button")!; // const trashRect = trashBin.getBoundingClientRect(); // const scopeRect = itemRef.current.getBoundingClientRect(); // const el = document.createElement("div"); // el.style.position = "fixed"; // el.style.top = "0"; // el.style.left = "0"; // el.style.width = "15px"; // el.style.height = "15px"; // el.style.backgroundColor = "var(--gray-7)"; // el.style.zIndex = "60"; // el.style.borderRadius = "50%"; // el.style.transform = "scale(5)"; // el.style.opacity = "0"; // trash.dataset["open"] = "true"; // const initial = { // x: scopeRect.left + scopeRect.width / 2, // y: scopeRect.top + scopeRect.height / 2, // }; // const delta = { // x: // trashRect.left + // trashRect.width / 2 - // scopeRect.left + // scopeRect.width / 2, // y: // trashRect.top + // trashRect.height / 4 - // scopeRect.top + // scopeRect.height / 2, // }; // const end = { // x: trashRect.left + trashRect.width / 2, // y: trashRect.top + trashRect.height / 4, // }; // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`; // animateItem(itemRef.current, SpaceExitVariant, { // duration: 0.2, // }).then(() => { // itemRef.current.style.scale = "0"; // onDelete(); // }); // document.body.appendChild(el); // el.animate( // { // transform: ["scale(5)", "scale(1)"], // opacity: [0, 0.3, 1], // }, // { // duration: 200, // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", // fill: "forwards", // }, // ); // el.animate( // { // offsetDistance: ["0%", "100%"], // }, // { // duration: 2000, // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", // fill: "forwards", // delay: 200, // }, // ).onfinish = () => { // el.animate( // { transform: "scale(0)", opacity: 0 }, // { duration: 200, fill: "forwards" }, // ).onfinish = () => { // el.remove(); // }; // }; }} /> {spaceMemories.length > 2 ? ( (c.type === "note" ? "/note.svg" : c.image)) .reverse() as string[] } /> ) : spaceMemories.length > 1 ? ( (c.type === "note" ? "/note.svg" : c.image)) .reverse() as string[] } /> ) : spaceMemories.length === 1 ? ( ) : (
)}
); } export function SpaceMoreButton({ onDelete, isOpen, setIsOpen, onEdit, }: { onDelete?: () => void; isOpen?: boolean; onEdit?: () => void; setIsOpen?: (open: boolean) => void; }) { return ( Edit Delete ); } export function PageMoreButton({ onDelete, isOpen, setIsOpen, url, removeFromSpace }: { onDelete?: () => void; isOpen?: boolean; url: string; setIsOpen?: (open: boolean) => void; removeFromSpace?: () => Promise; }) { return ( window.open(url)}> Open {removeFromSpace && ( Remove from space )} Delete ); } export function NoteMoreButton({ onDelete, isOpen, setIsOpen, onEdit, removeFromSpace }: { onDelete?: () => void; isOpen?: boolean; onEdit?: () => void; setIsOpen?: (open: boolean) => void; removeFromSpace?: () => Promise; }) { return ( Edit {removeFromSpace && ( Remove from space )} Delete ); } export function AddMemoryModal({ type, children, defaultSpaces, onAdd, data }: { type: "page" | "note" | "space" | "existing-memory" | null; children?: React.ReactNode | React.ReactNode[]; defaultSpaces?: number[]; data?: { space?: { title: string, id: number, }, fromSpaces?: number[], notInSpaces?: number[], }, onAdd?: (data?: StoredSpace | StoredContent | StoredContent[]) => void, }) { const [isDialogOpen, setIsDialogOpen] = useState(false); return ( {children} { e.preventDefault(); const novel = document.querySelector('[contenteditable="true"]') as | HTMLDivElement | undefined; if (novel) { novel.autofocus = false; novel.onfocus = () => { ( document.querySelector("[data-modal-autofocus]") as | HTMLInputElement | undefined )?.focus(); novel.onfocus = null; }; } ( document.querySelector("[data-modal-autofocus]") as | HTMLInputElement | undefined )?.focus(); }} className="w-max max-w-[auto]" > {type === "page" ? ( setIsDialogOpen(false)} /> ) : type === "note" ? ( setIsDialogOpen(false)} /> ) : type === "space" ? ( setIsDialogOpen(false)} /> ) : type === "existing-memory" ? ( setIsDialogOpen(false)} /> ) : ( <> )} ); }