space add

This commit is contained in:
yxshv 2024-04-13 20:04:20 +05:30
parent 5b34007124
commit 0d4e91068f
4 changed files with 144 additions and 26 deletions

View file

@ -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()
}

View file

@ -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>

View file

@ -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 ? (
) : spaceMemories.length > 1 ? (
<MemoryWithImages2
className="h-24 w-24"
id={id.toString()}
images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
) : spaceMemories.length === 1 ? (
<MemoryWithImage
className="h-24 w-24"
id={id.toString()}
image={spaceMemories[0].image!}
/>
) : (
<MemoryWithImages2
className="h-24 w-24"
id={id.toString()}
images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
)}
<div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-">
</div>
)}
</motion.div>
);
}

View file

@ -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,