From 61753bce6c5b54bf9652251c6a8cebc8b5c81996 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sat, 18 May 2024 17:48:49 -0500 Subject: [PATCH] added browser rendering --- .gitmodules | 6 + apps/browser-rendering | 1 + apps/web/src/components/Main-2.tsx | 709 ++++++++++++++++++ .../src/components/Sidebar/SettingsTab.tsx | 99 +++ .../components/dev/SessionProviderWrapper.tsx | 12 + .../src/components/dev/tailwindindicator.tsx | 16 + 6 files changed, 843 insertions(+) create mode 100644 .gitmodules create mode 160000 apps/browser-rendering create mode 100644 apps/web/src/components/Main-2.tsx create mode 100644 apps/web/src/components/Sidebar/SettingsTab.tsx create mode 100644 apps/web/src/components/dev/SessionProviderWrapper.tsx create mode 100644 apps/web/src/components/dev/tailwindindicator.tsx diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e8be54ff --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "apps/rowser-rendering"] + path = apps/rowser-rendering + url = https://github.com/dhravya/markdowner +[submodule "apps/browser-rendering"] + path = apps/browser-rendering + url = https://github.com/dhravya/markdowner diff --git a/apps/browser-rendering b/apps/browser-rendering new file mode 160000 index 00000000..3119b29f --- /dev/null +++ b/apps/browser-rendering @@ -0,0 +1 @@ +Subproject commit 3119b29faa3342a0e6465793c9649ff6176459b4 diff --git a/apps/web/src/components/Main-2.tsx b/apps/web/src/components/Main-2.tsx new file mode 100644 index 00000000..1b602712 --- /dev/null +++ b/apps/web/src/components/Main-2.tsx @@ -0,0 +1,709 @@ +"use client"; +import { MemoryDrawer } from "./MemoryDrawer"; +import useViewport from "@/hooks/useViewport"; +import { AnimatePresence } from "framer-motion"; +import { cn } from "@/lib/utils"; + +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 } from "./ui/dialog"; +import useTouchHold from "@/hooks/useTouchHold"; +import { DialogTrigger } from "@radix-ui/react-dialog"; +import { + AddExistingMemoryToSpace, + AddMemoryPage, + NoteAddPage, + SpaceAddPage, +} from "./Sidebar/AddMemoryDialog"; +import { ExpandedSpace } from "./Sidebar/ExpandedSpace"; +import { StoredContent, StoredSpace } from "@/server/db/schema"; +import { useDebounce } from "@/hooks/useDebounce"; +import { NoteEdit } from "./Sidebar/EditNoteDialog"; +import DeleteConfirmation from "./Sidebar/DeleteConfirmation"; + +import { ProfileDrawer } from "./ProfileDrawer"; + +function supportsDVH() { + try { + return CSS.supports("height: 100dvh"); + } catch { + return false; + } +} + +function pseudoRandomizeColorWithName(name: string) { + const colorsAvailable = [ + "99e9f2", + "a5d8ff", + "d0bfff", + "eebefa", + "fcc2d7", + "b2f2bb", + "96f2d7", + "ffec99", + "ffd8a8", + "ffc9c9", + ]; + + const colorIndex = + name + .split("") + .map((char) => char.charCodeAt(0)) + .reduce((acc, charCode) => acc + charCode, 0) % colorsAvailable.length; + + return colorsAvailable[colorIndex]; +} + +export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { + const { width } = useViewport(); + + 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 +

+
+ + + + + + e.preventDefault()} + > + + { + setAddMemoryState("page"); + }} + > + + Page to Memory + + + + { + setAddMemoryState("note"); + }} + > + + Note + + + + { + setAddMemoryState("space"); + }} + > + + Space + + + + + +
+
+ + ) : ( + + ) + } + className="mt-4 w-full text-black" + value={searchQuery} + onChange={(e) => setSearcyQuery(e.target.value)} + /> +
+
+ {typeof window !== "undefined" ? ( + 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) => ( + + ))} + + ) + ) : ( + <> + {Array.from({ + length: spaces.length + freeMemories.length, + }).map((_, i) => ( +
+ ))} + + )} +
+
+ {width <= 768 && } +
+
+ {width <= 768 && } +
+ + ); +} + +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 > 20 + ? title.slice(0, 20) + "..." + : title + : "Untitled Memory"; + + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const [moreDropdownOpen, setMoreDropdownOpen] = useState(false); + + const touchEventProps = useTouchHold({ + onHold() { + setMoreDropdownOpen(true); + }, + }); + return ( + + + + + + 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 > 20 ? name.slice(0, 20) + "..." : name; + + return ( + + ); +} + +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)} + /> + ) : ( + <> + )} + + + ); +} diff --git a/apps/web/src/components/Sidebar/SettingsTab.tsx b/apps/web/src/components/Sidebar/SettingsTab.tsx new file mode 100644 index 00000000..31b8380d --- /dev/null +++ b/apps/web/src/components/Sidebar/SettingsTab.tsx @@ -0,0 +1,99 @@ +import { Box, LogOut } from "lucide-react"; +import { signOut, useSession } from "next-auth/react"; +import { useEffect, useState } from "react"; + +export function SettingsTab({ open }: { open: boolean }) { + const { data: session } = useSession(); + + const [tweetStat, setTweetStat] = useState<[number, number] | null>(); + const [memoryStat, setMemoryStat] = useState<[number, number] | null>(); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/getCount").then(async (resp) => { + const data = (await resp.json()) as any; + setTweetStat([data.tweetsCount, data.tweetsLimit]); + setMemoryStat([data.pageCount, data.pageLimit]); + setLoading(false); + }); + }, [open]); + + return ( +
+
+

Settings

+
+ { + (e.target as HTMLImageElement).src = + "/icons/white_without_bg.png"; + }} + /> +
+

{session?.user?.name}

+ {session?.user?.email} + +
+
+
+
+

+ + Storage +

+ {loading ? ( +
+
+
+
+ ) : ( + <> +
+

+ Memories +
+ {memoryStat?.join("/")} +
+

+
+
0 ? "5%" : "0%", + }} + className="bg-rgray-5 h-full rounded-full" + /> +
+
+
+

+ Tweets +
+ {tweetStat?.join("/")} +
+

+
+
0 ? "5%" : "0%", + }} + className="h-full rounded-full bg-white" + /> +
+
+ + )} +
+
+ ); +} diff --git a/apps/web/src/components/dev/SessionProviderWrapper.tsx b/apps/web/src/components/dev/SessionProviderWrapper.tsx new file mode 100644 index 00000000..71f77886 --- /dev/null +++ b/apps/web/src/components/dev/SessionProviderWrapper.tsx @@ -0,0 +1,12 @@ +import { SessionProvider } from "next-auth/react"; +import React from "react"; + +function SessionProviderWrapper({ children }: { children: React.ReactNode }) { + if (typeof window === "undefined") { + return <>{children}; + } else { + return {children}; + } +} + +export default SessionProviderWrapper; diff --git a/apps/web/src/components/dev/tailwindindicator.tsx b/apps/web/src/components/dev/tailwindindicator.tsx new file mode 100644 index 00000000..fd70276d --- /dev/null +++ b/apps/web/src/components/dev/tailwindindicator.tsx @@ -0,0 +1,16 @@ +export function TailwindIndicator() { + if (process.env.NODE_ENV === "production") return null; + + return ( +
+
+
xs
+
sm
+
md
+
lg
+
xl
+
2xl
+
+
+ ); +}