mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-10 22:41:17 +00:00
changed data structure to fetch spaces
This commit is contained in:
commit
0375128bd9
9 changed files with 162 additions and 448 deletions
|
|
@ -61,13 +61,9 @@ export default async function Home() {
|
|||
.where(eq(storedContent.user, userData.id))
|
||||
.all();
|
||||
|
||||
console.log(contents);
|
||||
|
||||
const collectedSpaces =
|
||||
contents.length > 0 ? await transformContent(contents) : [];
|
||||
|
||||
console.log('collected', collectedSpaces);
|
||||
|
||||
return (
|
||||
<div className="flex w-screen">
|
||||
<Sidebar selectChange={setSelectedItem} spaces={collectedSpaces} />
|
||||
|
|
|
|||
15
apps/web/src/app/ui/content.tsx
Normal file
15
apps/web/src/app/ui/content.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"use client";
|
||||
import Main from "@/components/Main";
|
||||
import Sidebar from "@/components/Sidebar/index";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Content() {
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="flex w-screen">
|
||||
<Sidebar selectChange={setSelectedItem} />
|
||||
<Main sidebarOpen={selectedItem !== null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +1,10 @@
|
|||
import Main from '@/components/Main';
|
||||
import Sidebar from '@/components/Sidebar/index';
|
||||
import { cookies } from 'next/headers';
|
||||
import { MemoryProvider } from "@/contexts/MemoryContext";
|
||||
import Content from "./content";
|
||||
|
||||
export default function Home() {
|
||||
const selectedItem = cookies().get('selectedItem')?.value;
|
||||
const setSelectedItem = async (selectedItem: string | null) => {
|
||||
'use server';
|
||||
cookies().set('selectedItem', selectedItem!);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-screen">
|
||||
{/* <Sidebar selectChange={setSelectedItem} spaces={spaces} /> */}
|
||||
<Main sidebarOpen={selectedItem !== null} />
|
||||
</div>
|
||||
<MemoryProvider spaces={[]}>
|
||||
<Content />
|
||||
</MemoryProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,32 @@
|
|||
'use client';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FilterCombobox } from './Sidebar/FilterCombobox';
|
||||
import { Textarea2 } from './ui/textarea';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { MemoryDrawer } from './MemoryDrawer';
|
||||
import useViewport from '@/hooks/useViewport';
|
||||
import { motion } from 'framer-motion';
|
||||
import { cn } from '@/lib/utils';
|
||||
"use client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FilterCombobox } from "./Sidebar/FilterCombobox";
|
||||
import { Textarea2 } from "./ui/textarea";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { MemoryDrawer } from "./MemoryDrawer";
|
||||
import useViewport from "@/hooks/useViewport";
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function supportsDVH() {
|
||||
try {
|
||||
return CSS.supports("height: 100dvh");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
||||
const [hide, setHide] = useState(false);
|
||||
const [value, setValue] = useState('');
|
||||
const [value, setValue] = useState("");
|
||||
const { width } = useViewport();
|
||||
|
||||
const textArea = useRef<HTMLTextAreaElement>(null);
|
||||
const main = useRef<HTMLDivElement>(null);
|
||||
|
||||
console.log('main px', sidebarOpen);
|
||||
|
||||
useEffect(() => {
|
||||
function onResize() {
|
||||
if (!main.current || !window.visualViewport) return;
|
||||
// setValue(
|
||||
// (prev) =>
|
||||
// prev +
|
||||
// " changed to " +
|
||||
// window.visualViewport?.height +
|
||||
// " " +
|
||||
// window.innerHeight,
|
||||
// );
|
||||
if (
|
||||
window.visualViewport.height < window.innerHeight + 20 &&
|
||||
window.visualViewport.height > window.innerHeight - 20
|
||||
|
|
@ -41,9 +39,9 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
}
|
||||
}
|
||||
|
||||
window.visualViewport?.addEventListener('resize', onResize);
|
||||
window.visualViewport?.addEventListener("resize", onResize);
|
||||
return () => {
|
||||
window.visualViewport?.removeEventListener('resize', onResize);
|
||||
window.visualViewport?.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -52,24 +50,20 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
data-sidebar-open={sidebarOpen}
|
||||
ref={main}
|
||||
className={cn(
|
||||
"sidebar flex w-full flex-col items-end justify-center gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
|
||||
hide
|
||||
? 'pb-5'
|
||||
: CSS.supports('height: 100dvh')
|
||||
? 'pb-[13vh]'
|
||||
: 'pb-[20vh]',
|
||||
"sidebar flex w-full flex-col items-end justify-center gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
|
||||
hide ? "pb-5" : supportsDVH() ? "pb-[13vh]" : "pb-[20vh]",
|
||||
)}
|
||||
>
|
||||
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl">
|
||||
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
|
||||
Ask your Second brain
|
||||
</h1>
|
||||
<Textarea2
|
||||
ref={textArea}
|
||||
className="mt-auto h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
|
||||
className="mt-auto h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:mt-0 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
|
||||
textAreaProps={{
|
||||
placeholder: 'Ask your SuperMemory...',
|
||||
placeholder: "Ask your SuperMemory...",
|
||||
className:
|
||||
'h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]',
|
||||
"h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
|
||||
value,
|
||||
autoFocus: true,
|
||||
onChange: (e) => setValue(e.target.value),
|
||||
|
|
|
|||
|
|
@ -20,141 +20,17 @@ import {
|
|||
} from "@/components/ui/popover";
|
||||
import { SpaceIcon } from "@/assets/Memories";
|
||||
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
|
||||
|
||||
const spaces = [
|
||||
{
|
||||
value: "1",
|
||||
label: "Cool Tech",
|
||||
},
|
||||
{
|
||||
value: "2",
|
||||
label: "Cool Courses",
|
||||
},
|
||||
{
|
||||
value: "3",
|
||||
label: "Cool Libraries",
|
||||
},
|
||||
{
|
||||
value: "4",
|
||||
label: "Cool People",
|
||||
},
|
||||
{
|
||||
value: "5",
|
||||
label: "Cool Projects",
|
||||
},
|
||||
{
|
||||
value: "6",
|
||||
label: "Cool Tools",
|
||||
},
|
||||
{
|
||||
value: "7",
|
||||
label: "Cool Websites",
|
||||
},
|
||||
{
|
||||
value: "8",
|
||||
label: "Cool Books",
|
||||
},
|
||||
{
|
||||
value: "9",
|
||||
label: "Cool Videos",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
label: "Cool Podcasts",
|
||||
},
|
||||
{
|
||||
value: "11",
|
||||
label: "Cool Articles",
|
||||
},
|
||||
{
|
||||
value: "12",
|
||||
label: "Cool Blogs",
|
||||
},
|
||||
{
|
||||
value: "13",
|
||||
label: "Cool News",
|
||||
},
|
||||
{
|
||||
value: "14",
|
||||
label: "Cool Forums",
|
||||
},
|
||||
{
|
||||
value: "15",
|
||||
label: "Cool Communities",
|
||||
},
|
||||
{
|
||||
value: "16",
|
||||
label: "Cool Events",
|
||||
},
|
||||
{
|
||||
value: "17",
|
||||
label: "Cool Jobs",
|
||||
},
|
||||
{
|
||||
value: "18",
|
||||
label: "Cool Companies",
|
||||
},
|
||||
{
|
||||
value: "19",
|
||||
label: "Cool Startups",
|
||||
},
|
||||
{
|
||||
value: "20",
|
||||
label: "Cool Investors",
|
||||
},
|
||||
{
|
||||
value: "21",
|
||||
label: "Cool Funds",
|
||||
},
|
||||
{
|
||||
value: "22",
|
||||
label: "Cool Incubators",
|
||||
},
|
||||
{
|
||||
value: "23",
|
||||
label: "Cool Accelerators",
|
||||
},
|
||||
{
|
||||
value: "24",
|
||||
label: "Cool Hackathons",
|
||||
},
|
||||
{
|
||||
value: "25",
|
||||
label: "Cool Conferences",
|
||||
},
|
||||
{
|
||||
value: "26",
|
||||
label: "Cool Workshops",
|
||||
},
|
||||
{
|
||||
value: "27",
|
||||
label: "Cool Seminars",
|
||||
},
|
||||
{
|
||||
value: "28",
|
||||
label: "Cool Webinars",
|
||||
},
|
||||
{
|
||||
value: "29",
|
||||
label: "Cool Courses",
|
||||
},
|
||||
{
|
||||
value: "30",
|
||||
label: "Cool Bootcamps",
|
||||
},
|
||||
{
|
||||
value: "31",
|
||||
label: "Cool Certifications",
|
||||
},
|
||||
];
|
||||
import { useMemory } from "@/contexts/MemoryContext";
|
||||
|
||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
export function FilterCombobox({ className, ...props }: Props) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [values, setValues] = React.useState<string[]>([]);
|
||||
const { spaces, addSpace } = useMemory();
|
||||
|
||||
const sortedSpaces = spaces.sort(({ value: a }, { value: b }) =>
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [values, setValues] = React.useState<number[]>([]);
|
||||
|
||||
const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
|
||||
values.includes(a) && !values.includes(b)
|
||||
? -1
|
||||
: values.includes(b) && !values.includes(a)
|
||||
|
|
@ -192,8 +68,8 @@ export function FilterCombobox({ className, ...props }: Props) {
|
|||
<Command
|
||||
filter={(val, search) =>
|
||||
spaces
|
||||
.find((s) => s.value === val)
|
||||
?.label.toLowerCase()
|
||||
.find((s) => s.id.toString() === val)
|
||||
?.title.toLowerCase()
|
||||
.includes(search.toLowerCase().trim())
|
||||
? 1
|
||||
: 0
|
||||
|
|
@ -206,13 +82,13 @@ export function FilterCombobox({ className, ...props }: Props) {
|
|||
<CommandGroup>
|
||||
{sortedSpaces.map((space) => (
|
||||
<CommandItem
|
||||
key={space.value}
|
||||
value={space.value}
|
||||
key={space.id}
|
||||
value={space.id.toString()}
|
||||
onSelect={(val) => {
|
||||
setValues((prev) =>
|
||||
prev.includes(val)
|
||||
? prev.filter((v) => v !== val)
|
||||
: [...prev, val],
|
||||
prev.includes(parseInt(val))
|
||||
? prev.filter((v) => v !== parseInt(val))
|
||||
: [...prev, parseInt(val)],
|
||||
);
|
||||
}}
|
||||
asChild
|
||||
|
|
@ -222,14 +98,14 @@ export function FilterCombobox({ className, ...props }: Props) {
|
|||
animate={{ opacity: 1, transition: { delay: 0.05 } }}
|
||||
transition={{ duration: 0.15 }}
|
||||
layout
|
||||
layoutId={`space-combobox-${space.value}`}
|
||||
layoutId={`space-combobox-${space.id}`}
|
||||
className="text-rgray-11"
|
||||
>
|
||||
<SpaceIcon className="mr-2 h-4 w-4" />
|
||||
{space.label}
|
||||
{values.includes(space.value)}
|
||||
{space.title}
|
||||
{values.includes(space.id)}
|
||||
<Check
|
||||
data-state-on={values.includes(space.value)}
|
||||
data-state-on={values.includes(space.id)}
|
||||
className={cn(
|
||||
"on:opacity-100 ml-auto h-4 w-4 opacity-0",
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,31 @@
|
|||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import {
|
||||
MemoryWithImage,
|
||||
MemoryWithImages3,
|
||||
MemoryWithImages2,
|
||||
} from '@/assets/MemoryWithImages';
|
||||
import { type CollectedSpaces } from '../../../types/memory';
|
||||
import { InputWithIcon } from '../ui/input';
|
||||
} from "@/assets/MemoryWithImages";
|
||||
import { type CollectedSpaces } from "../../../types/memory";
|
||||
import { InputWithIcon } from "../ui/input";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Edit3,
|
||||
MoreHorizontal,
|
||||
Search,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '../ui/dropdown-menu';
|
||||
import {
|
||||
animate,
|
||||
AnimatePresence,
|
||||
LayoutGroup,
|
||||
motion,
|
||||
useAnimate,
|
||||
Variant,
|
||||
} from 'framer-motion';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
} from "../ui/dropdown-menu";
|
||||
import { useState } from "react";
|
||||
import { Variant, useAnimate, motion } from "framer-motion";
|
||||
|
||||
export function MemoriesBar({ spaces }: { spaces: CollectedSpaces[] }) {
|
||||
const [parent, enableAnimations] = useAutoAnimate();
|
||||
const [currentSpaces, setCurrentSpaces] = useState(spaces);
|
||||
|
||||
console.log('currentSpaces: ', currentSpaces);
|
||||
|
||||
return (
|
||||
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
|
||||
<div className="w-full px-8">
|
||||
|
|
@ -67,8 +57,8 @@ export function MemoriesBar({ spaces }: { spaces: CollectedSpaces[] }) {
|
|||
const SpaceExitVariant: Variant = {
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
borderRadius: '50%',
|
||||
background: 'var(--gray-1)',
|
||||
borderRadius: "50%",
|
||||
background: "var(--gray-1)",
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
},
|
||||
|
|
@ -93,22 +83,22 @@ export function SpaceItem({
|
|||
<SpaceMoreButton
|
||||
onDelete={() => {
|
||||
if (!itemRef.current) return;
|
||||
const trash = document.querySelector('#trash')! as HTMLDivElement;
|
||||
const trashBin = document.querySelector('#trash-button')!;
|
||||
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 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,
|
||||
|
|
@ -133,35 +123,35 @@ export function SpaceItem({
|
|||
animateItem(itemRef.current, SpaceExitVariant, {
|
||||
duration: 0.2,
|
||||
}).then(() => {
|
||||
itemRef.current.style.scale = '0';
|
||||
itemRef.current.style.scale = "0";
|
||||
onDelete();
|
||||
});
|
||||
document.body.appendChild(el);
|
||||
el.animate(
|
||||
{
|
||||
transform: ['scale(5)', 'scale(1)'],
|
||||
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',
|
||||
easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
fill: "forwards",
|
||||
},
|
||||
);
|
||||
el.animate(
|
||||
{
|
||||
offsetDistance: ['0%', '100%'],
|
||||
offsetDistance: ["0%", "100%"],
|
||||
},
|
||||
{
|
||||
duration: 2000,
|
||||
easing: 'cubic-bezier(0.64, 0.57, 0.67, 1.53)',
|
||||
fill: 'forwards',
|
||||
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' },
|
||||
{ transform: "scale(0)", opacity: 0 },
|
||||
{ duration: 200, fill: "forwards" },
|
||||
).onfinish = () => {
|
||||
el.remove();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
"use client";
|
||||
import { cleanUrl } from "@/lib/utils";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { Label } from "../ui/label";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
MoreHorizontal,
|
||||
Edit3,
|
||||
Trash2,
|
||||
Save,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerClose,
|
||||
} from "../ui/drawer";
|
||||
import { Input } from "../ui/input";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
|
||||
export const PageItem: React.FC<{ item: StoredContent }> = ({ item }) => {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="hover:bg-rgray-5 has-[a:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-icon]]:scale-125 [&:hover>a>div>[data-icon]]:opacity-100 [&:hover>a>div>[data-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100">
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
className="flex w-[90%] items-center gap-2 focus-visible:outline-none"
|
||||
>
|
||||
<div className="relative h-4 min-w-4">
|
||||
<img
|
||||
src={item.image ?? "/brain.png"}
|
||||
alt={item.title ?? "Untitiled website"}
|
||||
className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150"
|
||||
/>
|
||||
<ArrowUpRight
|
||||
data-icon
|
||||
className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="w-full truncate text-nowrap">
|
||||
{item.title ?? "Untitled website"}
|
||||
</span>
|
||||
</a>
|
||||
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus-visible:opacity-100 focus-visible:outline-none">
|
||||
<MoreHorizontal className="h-4 w-4 min-w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-5">
|
||||
<DropdownMenuItem onClick={() => window.open(item.url)}>
|
||||
<ArrowUpRight
|
||||
className="mr-2 h-4 w-4 scale-125"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setIsDropdownOpen(false);
|
||||
setIsEditDrawerOpen(true);
|
||||
}}
|
||||
>
|
||||
<Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="focus-visible:bg-red-100 focus-visible:text-red-400 dark:focus-visible:bg-red-100/10">
|
||||
<Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Drawer
|
||||
shouldScaleBackground
|
||||
open={isEditDrawerOpen}
|
||||
onOpenChange={setIsEditDrawerOpen}
|
||||
>
|
||||
<DrawerContent className="pb-10 lg:px-[25vw]">
|
||||
<DrawerHeader className="relative mt-10 px-0">
|
||||
<DrawerTitle className=" flex w-full justify-between">
|
||||
Edit Page Details
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>Change the page details</DrawerDescription>
|
||||
<a
|
||||
target="_blank"
|
||||
href={item.url}
|
||||
className="text-rgray-11/90 bg-rgray-3 text-md absolute right-0 top-0 flex w-min translate-y-1/2 items-center justify-center gap-1 rounded-full px-5 py-1"
|
||||
>
|
||||
<img src={item.image ?? "/brain.png"} className="h-4 w-4" />
|
||||
{cleanUrl(item.url)}
|
||||
</a>
|
||||
</DrawerHeader>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label>Title</Label>
|
||||
<Input
|
||||
className=""
|
||||
required
|
||||
value={item.title ?? ""}
|
||||
placeholder={item.title ?? "Enter the title for the page"}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Additional Context</Label>
|
||||
<Textarea
|
||||
className=""
|
||||
value={item.content ?? ""}
|
||||
placeholder={"Enter additional context for this page"}
|
||||
/>
|
||||
</div>
|
||||
<DrawerFooter className="flex flex-row-reverse items-center justify-end px-0 pt-5">
|
||||
<DrawerClose className="flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition hover:bg-blue-100 hover:text-blue-400 focus-visible:bg-blue-100 focus-visible:text-blue-400 focus-visible:outline-none focus-visible:ring-blue-200 dark:hover:bg-blue-100/10 dark:focus-visible:bg-blue-100/10 dark:focus-visible:ring-blue-200/30">
|
||||
<Save className="mr-2 h-4 w-4 " strokeWidth={1.5} />
|
||||
Save
|
||||
</DrawerClose>
|
||||
<DrawerClose className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition focus-visible:outline-none">
|
||||
Cancel
|
||||
</DrawerClose>
|
||||
<DrawerClose className="mr-auto flex items-center justify-center rounded-md bg-red-100 px-3 py-2 text-red-400 ring-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-red-200 dark:bg-red-100/10 dark:focus-visible:ring-red-200/30">
|
||||
<Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} />
|
||||
Delete
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddNewPagePopover: React.FC<{
|
||||
addNewUrl?: (url: string) => Promise<void>;
|
||||
}> = ({ addNewUrl }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [url, setUrl] = useState("");
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="focus-visible:ring-rgray-7 ring-offset-rgray-3 ml-auto rounded-sm ring-2 ring-transparent ring-offset-2 focus-visible:outline-none">
|
||||
<Plus className="h-4 w-4 min-w-4" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" side="top">
|
||||
<h1 className="mb-2 flex items-center justify-between ">
|
||||
Add a new page
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
addNewUrl?.(url);
|
||||
}}
|
||||
className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 ring-offset-rgray-3 rounded-sm ring-2 ring-transparent ring-offset-2 transition focus-visible:outline-none"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</button>
|
||||
</h1>
|
||||
<Input
|
||||
className="w-full"
|
||||
autoFocus
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setIsOpen(false);
|
||||
addNewUrl?.(url);
|
||||
}
|
||||
}}
|
||||
placeholder="Enter the URL of the page"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,49 +1,54 @@
|
|||
'use client';
|
||||
import { StoredContent } from '@/server/db/schema';
|
||||
import { MemoryIcon } from '../../assets/Memories';
|
||||
import { Trash2, User2 } from 'lucide-react';
|
||||
import React, { ElementType, useEffect, useState } from 'react';
|
||||
import { MemoriesBar } from './MemoriesBar';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Bin } from '@/assets/Bin';
|
||||
import { CollectedSpaces } from '../../../types/memory';
|
||||
"use client";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
import { MemoryIcon } from "../../assets/Memories";
|
||||
import { Trash2, User2 } from "lucide-react";
|
||||
import React, { ElementType, useEffect, useState } from "react";
|
||||
import { MemoriesBar } from "./MemoriesBar";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Bin } from "@/assets/Bin";
|
||||
import { CollectedSpaces } from "../../../types/memory";
|
||||
import { useMemory } from "@/contexts/MemoryContext";
|
||||
|
||||
export type MenuItem = {
|
||||
icon: React.ReactNode | React.ReactNode[];
|
||||
label: string;
|
||||
content?: React.ReactElement;
|
||||
content?: React.ReactNode;
|
||||
};
|
||||
|
||||
const menuItemsBottom: Array<MenuItem> = [
|
||||
{
|
||||
icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />,
|
||||
label: 'Trash',
|
||||
label: "Trash",
|
||||
},
|
||||
{
|
||||
icon: <User2 strokeWidth={1.3} className="h-6 w-6" />,
|
||||
label: 'Profile',
|
||||
label: "Profile",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Sidebar({
|
||||
selectChange,
|
||||
spaces,
|
||||
spaces
|
||||
}: {
|
||||
selectChange?: (selectedItem: string | null) => Promise<void>;
|
||||
selectChange?: (selectedItem: string | null) => void;
|
||||
spaces: CollectedSpaces[];
|
||||
}) {
|
||||
// TODO: @yxshv, put spaces in context here
|
||||
// const { spaces } = useMemory();
|
||||
|
||||
const menuItemsTop: Array<MenuItem> = [
|
||||
{
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
label: 'Memories',
|
||||
label: "Memories",
|
||||
content: <MemoriesBar spaces={spaces} />,
|
||||
},
|
||||
];
|
||||
const menuItems = [...menuItemsTop, ...menuItemsBottom];
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>(null);
|
||||
|
||||
const Subbar =
|
||||
menuItems.find((i) => i.label === selectedItem)?.content ?? (() => <></>);
|
||||
const Subbar = menuItems.find((i) => i.label === selectedItem)?.content ?? (
|
||||
<></>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void selectChange?.(selectedItem);
|
||||
|
|
@ -55,7 +60,7 @@ export default function Sidebar({
|
|||
<div className="bg-rgray-2 border-r-rgray-6 relative z-[50] flex h-full w-full flex-col items-center justify-center border-r px-2 py-5 ">
|
||||
<MenuItem
|
||||
item={{
|
||||
label: 'Memories',
|
||||
label: "Memories",
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
content: <MemoriesBar spaces={spaces} />,
|
||||
}}
|
||||
|
|
@ -67,7 +72,7 @@ export default function Sidebar({
|
|||
|
||||
<MenuItem
|
||||
item={{
|
||||
label: 'Trash',
|
||||
label: "Trash",
|
||||
icon: <Bin id="trash" className="z-[300] h-7 w-7" />,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
|
|
@ -76,7 +81,7 @@ export default function Sidebar({
|
|||
/>
|
||||
<MenuItem
|
||||
item={{
|
||||
label: 'Profile',
|
||||
label: "Profile",
|
||||
icon: <User2 strokeWidth={1.3} className="h-7 w-7" />,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
|
|
@ -84,10 +89,6 @@ export default function Sidebar({
|
|||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{/* @yxshv idk why this is giving typeerror
|
||||
when used as <Subbar/> it says it's not valid element type
|
||||
*/}
|
||||
{/* @ts-ignore */}
|
||||
{selectedItem && <SubSidebar>{Subbar}</SubSidebar>}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
|
@ -119,11 +120,11 @@ const MenuItem = ({
|
|||
export function SubSidebar({ children }: { children?: React.ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: '-100%' }}
|
||||
initial={{ opacity: 0, x: "-100%" }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
x: '-100%',
|
||||
x: "-100%",
|
||||
transition: { delay: 0.2 },
|
||||
}}
|
||||
transition={{
|
||||
|
|
|
|||
39
apps/web/src/contexts/MemoryContext.tsx
Normal file
39
apps/web/src/contexts/MemoryContext.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"use client";
|
||||
import React, { useCallback } from "react";
|
||||
import { CollectedSpaces } from "../../types/memory";
|
||||
|
||||
// temperory (will change)
|
||||
export const MemoryContext = React.createContext<{
|
||||
spaces: CollectedSpaces[];
|
||||
addSpace: (space: CollectedSpaces) => Promise<void>;
|
||||
}>({
|
||||
spaces: [],
|
||||
addSpace: async (space) => {},
|
||||
});
|
||||
|
||||
export const MemoryProvider: React.FC<
|
||||
{ spaces: CollectedSpaces[] } & React.PropsWithChildren
|
||||
> = ({ children, spaces: initalSpaces }) => {
|
||||
const [spaces, setSpaces] = React.useState<CollectedSpaces[]>(initalSpaces);
|
||||
|
||||
const addSpace = useCallback(
|
||||
async (space: CollectedSpaces) => {
|
||||
setSpaces((prev) => [...prev, space]);
|
||||
},
|
||||
[spaces],
|
||||
);
|
||||
|
||||
return (
|
||||
<MemoryContext.Provider value={{ spaces, addSpace }}>
|
||||
{children}
|
||||
</MemoryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMemory = () => {
|
||||
const context = React.useContext(MemoryContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useMemory must be used within a MemoryProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue