diff options
| author | yxshv <[email protected]> | 2024-04-13 20:04:20 +0530 |
|---|---|---|
| committer | yxshv <[email protected]> | 2024-04-13 20:04:20 +0530 |
| commit | 0d4e91068f8b16ddb956e9c302ef62b9e38b1788 (patch) | |
| tree | d078ea44ccd8b1b5c6a93d25c7d7ca8468455a1b /apps/web/src | |
| parent | spaces dialog (diff) | |
| download | supermemory-0d4e91068f8b16ddb956e9c302ef62b9e38b1788.tar.xz supermemory-0d4e91068f8b16ddb956e9c302ef62b9e38b1788.zip | |
space add
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/actions/db.ts | 82 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/AddMemoryDialog.tsx | 32 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/MemoriesBar.tsx | 27 | ||||
| -rw-r--r-- | apps/web/src/contexts/MemoryContext.tsx | 29 |
4 files changed, 144 insertions, 26 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts index b12ed13b..c5481392 100644 --- a/apps/web/src/actions/db.ts +++ b/apps/web/src/actions/db.ts @@ -8,10 +8,10 @@ import { storedContent, StoredSpace, users, - space + space, } from "@/server/db/schema"; import { SearchResult } from "@/contexts/MemoryContext"; -import { like, eq, and, sql } from "drizzle-orm"; +import { like, eq, and, sql, exists, asc, notExists } from "drizzle-orm"; import { union } from "drizzle-orm/sqlite-core" // @todo: (future) pagination not yet needed @@ -137,3 +137,81 @@ export async function addMemory( ); return _content; } + +export async function addSpace(name: string, memories: number[]) { + + const user = await getUser(); + + if (!user) { + return null + } + + const [addedSpace] = await db + .insert(space) + .values({ + name: name, + user: user.id + }).returning(); + + const addedMemories = memories.length > 0 ? await db.insert(contentToSpace) + .values(memories.map(m => ({ + contentId: m, + spaceId: addedSpace.id + }))).returning() : [] + + return { + space: addedSpace, + addedMemories + } +} + + +export async function fetchContentForSpace( + spaceId: number, + range?: { + offset: number; + limit: number; + }, +) { + + const query = db + .select() + .from(storedContent) + .where( + exists( + db.select().from(contentToSpace).where(and(eq(contentToSpace.spaceId, spaceId), eq(contentToSpace.contentId, storedContent.id))), + ), + ).orderBy(asc(storedContent.title)) + + return range ? await query.limit(range.limit).offset(range.offset) : await query.all() +} + +export async function fetchFreeMemories( + range?: { + offset: number; + limit: number; + } +) { + + const user = await getUser() + + if (!user) { + return [] + } + + const query = db + .select() + .from(storedContent) + .where( + and( + notExists( + db.select().from(contentToSpace).where(eq(contentToSpace.contentId, storedContent.id)), + ), + eq(storedContent.user, user.id), + ) + + ).orderBy(asc(storedContent.title)) + + return range ? await query.limit(range.limit).offset(range.offset) : await query.all() + +} diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx index 4f8ef734..1482774e 100644 --- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -12,9 +12,10 @@ import { Markdown } from "tiptap-markdown"; import { useEffect, useRef, useState } from "react"; import { FilterMemories, FilterSpaces } from "./FilterCombobox"; import { useMemory } from "@/contexts/MemoryContext"; -import { Command, Plus, X } from "lucide-react"; +import { Loader, Plus, X } from "lucide-react"; import { StoredContent } from "@/server/db/schema"; import { cleanUrl } from "@/lib/utils"; +import { motion } from "framer-motion" export function AddMemoryPage() { const { addMemory } = useMemory(); @@ -157,8 +158,11 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { + const { addSpace } = useMemory() + const inputRef = useRef<HTMLInputElement>(null); const [name, setName] = useState(""); + const [loading, setLoading] = useState(false); const [selected, setSelected] = useState<StoredContent[]>([]); @@ -198,6 +202,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { type="url" data-modal-autofocus value={name} + disabled={loading} onChange={e => setName(e.target.value)} className="bg-rgray-4 mt-2 w-full focus-visible:data-[error=true]:ring-red-500/10 data-[error=true]:placeholder:text-red-400 placeholder:transition placeholder:duration-500" /> @@ -219,7 +224,8 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { <FilterMemories selected={selected} setSelected={setSelected} - className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4" + disabled={loading} + className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4 disabled:opacity-70 disabled:cursor-not-allowed" > <Plus className="w-5 h-5" /> Memory @@ -228,14 +234,28 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { type={undefined} onClick={() => { if (check()) { - + setLoading(true) + addSpace(name, selected.map(s => s.id)).then(() => closeDialog()) } }} - className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" + disabled={loading} + className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" > - Add + <motion.div + initial={{ x: '-50%', y: '-100%' }} + animate={loading && { y: '-50%', x: '-50%', opacity: 1 }} + className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2" + > + <Loader className="w-5 h-5 animate-spin text-rgray-11" /> + </motion.div> + <motion.div + initial={{ y: '0%' }} + animate={loading && { opacity: 0, y: '30%' }} + > + Add + </motion.div> </button> - <DialogClose className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"> + <DialogClose disabled={loading} className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"> Cancel </DialogClose> </DialogFooter> diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx index 1c9e7143..88262f4e 100644 --- a/apps/web/src/components/Sidebar/MemoriesBar.tsx +++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx @@ -196,13 +196,16 @@ export function MemoryItem({ title, image }: StoredContent) { + + const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : '<no title>'; + return ( <div className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100" > <button data-space-text className="focus-visible:outline-none"> - {title} + {name} </button> <div className="w-24 h-24 flex justify-center items-center"> @@ -240,6 +243,8 @@ export function SpaceItem({ return cachedMemories.filter(m => m.space === id) }, [cachedMemories]) + const _name = name.length > 10 ? name.slice(0, 10) + "..." : name + return ( <motion.div ref={itemRef} @@ -248,7 +253,7 @@ export function SpaceItem({ className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100" > <button data-space-text className="focus-visible:outline-none"> - {name} + {_name} </button> <SpaceMoreButton isOpen={moreDropdownOpen} @@ -341,19 +346,23 @@ export function SpaceItem({ id={id.toString()} images={spaceMemories.map((c) => c.image).reverse() as string[]} /> - ) : spaceMemories.length === 1 ? ( - <MemoryWithImage + ) : spaceMemories.length > 1 ? ( + <MemoryWithImages2 className="h-24 w-24" id={id.toString()} - image={spaceMemories[0].image!} + images={spaceMemories.map((c) => c.image).reverse() as string[]} /> - ) : ( - <MemoryWithImages2 + ) : spaceMemories.length === 1 ? ( + <MemoryWithImage className="h-24 w-24" id={id.toString()} - images={spaceMemories.map((c) => c.image).reverse() as string[]} + image={spaceMemories[0].image!} /> - )} + ) : ( + <div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-"> + + </div> + )} </motion.div> ); } diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx index f2f09e80..08d166a6 100644 --- a/apps/web/src/contexts/MemoryContext.tsx +++ b/apps/web/src/contexts/MemoryContext.tsx @@ -1,8 +1,7 @@ "use client"; import React, { useCallback } from "react"; -import { CollectedSpaces } from "../../types/memory"; import { ChachedSpaceContent, StoredContent, storedContent, StoredSpace } from "@/server/db/schema"; -import { addMemory, searchMemoriesAndSpaces } from "@/actions/db"; +import { addMemory, searchMemoriesAndSpaces, addSpace, fetchContentForSpace } from "@/actions/db"; import { User } from "next-auth"; export type SearchResult = { @@ -16,7 +15,7 @@ export const MemoryContext = React.createContext<{ spaces: StoredSpace[]; deleteSpace: (id: number) => Promise<void>; freeMemories: StoredContent[]; - addSpace: (space: StoredSpace) => Promise<void>; + addSpace: typeof addSpace; addMemory: ( memory: typeof storedContent.$inferInsert, spaces?: number[], @@ -27,7 +26,7 @@ export const MemoryContext = React.createContext<{ spaces: [], freeMemories: [], addMemory: async () => {}, - addSpace: async () => {}, + addSpace: (async () => {}) as unknown as (typeof addSpace), deleteSpace: async () => {}, cachedMemories: [], search: async () => [] @@ -49,10 +48,6 @@ export const MemoryProvider: React.FC< const [cachedMemories, setCachedMemories] = React.useState<ChachedSpaceContent[]>( initialCachedMemories ); - - const addSpace = async (space: StoredSpace) => { - setSpaces((prev) => [...prev, space]); - } const deleteSpace = async (id: number) => { setSpaces((prev) => prev.filter((s) => s.id !== id)); @@ -68,13 +63,29 @@ export const MemoryProvider: React.FC< ) => { const content = await addMemory(memory, spaces); } + + const _addSpace: typeof addSpace = async (...params) => { + const { space: addedSpace, addedMemories } = (await addSpace(...params))!; + + setSpaces(prev => [...prev, addedSpace]) + const cachedMemories = (await fetchContentForSpace(addedSpace.id, { + offset: 0, + limit: 3 + })).map(m => ({ ...m, space: addedSpace.id })) + + setCachedMemories(prev => [...prev, ...cachedMemories]) + + return { + space: addedSpace, addedMemories + } + } return ( <MemoryContext.Provider value={{ search: searchMemoriesAndSpaces, spaces, - addSpace, + addSpace: _addSpace, deleteSpace, freeMemories, cachedMemories, |