diff --git a/mirror-2/app/globals.css b/mirror-2/app/globals.css index 68fb5f0d..e0e1120c 100644 --- a/mirror-2/app/globals.css +++ b/mirror-2/app/globals.css @@ -58,3 +58,9 @@ --chart-5: 340 75% 55%; } } +.ant-tree-switcher_close { + margin-top: 0.5rem !important; +} +.ant-tree-switcher_open { + margin-top: 0.5rem !important; +} diff --git a/mirror-2/app/home/page.tsx b/mirror-2/app/home/page.tsx index 2accc2d3..5f12a254 100644 --- a/mirror-2/app/home/page.tsx +++ b/mirror-2/app/home/page.tsx @@ -14,9 +14,11 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" import Link from "next/link" import { appDescription, appName } from "@/lib/theme-service" import { Metadata } from "next" +import AccountDropdownMenu from "@/components/ui/account-dropdown-menu" export default function MusicPage() { + return ( <>
@@ -63,7 +65,9 @@ export default function MusicPage() { +
+
diff --git a/mirror-2/app/layout.client.tsx b/mirror-2/app/layout.client.tsx index b1a948ca..51435a2b 100644 --- a/mirror-2/app/layout.client.tsx +++ b/mirror-2/app/layout.client.tsx @@ -23,7 +23,7 @@ export default function ClientLayout({ children }) { export function AuthLayout({ children }) { useSetupAuthEvents() return ( -
+
{children}
diff --git a/mirror-2/app/space/[spaceId]/build/(controlBar)/NodeService.tsx b/mirror-2/app/space/[spaceId]/build/(controlBar)/NodeService.tsx new file mode 100644 index 00000000..3d60aee5 --- /dev/null +++ b/mirror-2/app/space/[spaceId]/build/(controlBar)/NodeService.tsx @@ -0,0 +1,437 @@ +export const NodeService = { + getTreeNodesData() { + return [ + { + key: 'documents-id', + label: 'Documents', + data: 'Documents Folder', + icon: 'pi pi-fw pi-inbox', + children: [ + { + key: 'work-d', + label: 'Work', + data: 'Work Folder', + icon: 'pi pi-fw pi-cog', + children: [ + { key: '0-0-0', label: 'Expenses.doc', icon: 'pi pi-fw pi-file', data: 'Expenses Document' }, + { key: '0-0-1', label: 'Resume.doc', icon: 'pi pi-fw pi-file', data: 'Resume Document' } + ] + }, + { + key: 'home-id', + label: 'Home', + data: 'Home Folder', + icon: 'pi pi-fw pi-home', + children: [ + { + key: 'invoices-id', label: 'Invoices.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month', children: [ + { key: 'invoices-id2', label: 'Invoices2.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month' } + ] + } + ] + } + ] + }, + { + key: 'events-id', + label: 'Events', + data: 'Events Folder', + icon: 'pi pi-fw pi-calendar', + children: [ + { key: 'meeting-d', label: 'Meeting', icon: 'pi pi-fw pi-calendar-plus', data: 'Meeting' }, + { key: 'product-launch-id', label: 'Product Launch', icon: 'pi pi-fw pi-calendar-plus', data: 'Product Launch' }, + { key: 'report-review-id', label: 'Report Review', icon: 'pi pi-fw pi-calendar-plus', data: 'Report Review' } + ] + }, + { + key: 'movies-id', + label: 'Movies', + data: 'Movies Folder', + icon: 'pi pi-fw pi-star-fill', + children: [ + { + key: 'al-pacino-id', + icon: 'pi pi-fw pi-star-fill', + label: 'Al Pacino', + data: 'Pacino Movies', + children: [ + { key: 'scarface-id', label: 'Scarface', icon: 'pi pi-fw pi-video', data: 'Scarface Movie' }, + { key: 'serpico-id', label: 'Serpico', icon: 'pi pi-fw pi-video', data: 'Serpico Movie' } + ] + }, + { + key: 'robert-de-niro-id', + label: 'Robert De Niro', + icon: 'pi pi-fw pi-star-fill', + data: 'De Niro Movies', + children: [ + { key: 'goodfellas-id', label: 'Goodfellas', icon: 'pi pi-fw pi-video', data: 'Goodfellas Movie' }, + { key: 'untouchables-id', label: 'Untouchables', icon: 'pi pi-fw pi-video', data: 'Untouchables Movie' } + ] + } + ] + } + ]; + }, + + getTreeTableNodesData() { + return [ + { + key: '0', + data: { + name: 'Applications', + size: '100kb', + type: 'Folder' + }, + children: [ + { + key: '0-0', + data: { + name: 'React', + size: '25kb', + type: 'Folder' + }, + children: [ + { + key: '0-0-0', + data: { + name: 'react.app', + size: '10kb', + type: 'Application' + } + }, + { + key: '0-0-1', + data: { + name: 'native.app', + size: '10kb', + type: 'Application' + } + }, + { + key: '0-0-2', + data: { + name: 'mobile.app', + size: '5kb', + type: 'Application' + } + } + ] + }, + { + key: '0-1', + data: { + name: 'editor.app', + size: '25kb', + type: 'Application' + } + }, + { + key: '0-2', + data: { + name: 'settings.app', + size: '50kb', + type: 'Application' + } + } + ] + }, + { + key: '1', + data: { + name: 'Cloud', + size: '20kb', + type: 'Folder' + }, + children: [ + { + key: '1-0', + data: { + name: 'backup-1.zip', + size: '10kb', + type: 'Zip' + } + }, + { + key: '1-1', + data: { + name: 'backup-2.zip', + size: '10kb', + type: 'Zip' + } + } + ] + }, + { + key: '2', + data: { + name: 'Desktop', + size: '150kb', + type: 'Folder' + }, + children: [ + { + key: '2-0', + data: { + name: 'note-meeting.txt', + size: '50kb', + type: 'Text' + } + }, + { + key: '2-1', + data: { + name: 'note-todo.txt', + size: '100kb', + type: 'Text' + } + } + ] + }, + { + key: '3', + data: { + name: 'Documents', + size: '75kb', + type: 'Folder' + }, + children: [ + { + key: '3-0', + data: { + name: 'Work', + size: '55kb', + type: 'Folder' + }, + children: [ + { + key: '3-0-0', + data: { + name: 'Expenses.doc', + size: '30kb', + type: 'Document' + } + }, + { + key: '3-0-1', + data: { + name: 'Resume.doc', + size: '25kb', + type: 'Resume' + } + } + ] + }, + { + key: '3-1', + data: { + name: 'Home', + size: '20kb', + type: 'Folder' + }, + children: [ + { + key: '3-1-0', + data: { + name: 'Invoices', + size: '20kb', + type: 'Text' + } + } + ] + } + ] + }, + { + key: '4', + data: { + name: 'Downloads', + size: '25kb', + type: 'Folder' + }, + children: [ + { + key: '4-0', + data: { + name: 'Spanish', + size: '10kb', + type: 'Folder' + }, + children: [ + { + key: '4-0-0', + data: { + name: 'tutorial-a1.txt', + size: '5kb', + type: 'Text' + } + }, + { + key: '4-0-1', + data: { + name: 'tutorial-a2.txt', + size: '5kb', + type: 'Text' + } + } + ] + }, + { + key: '4-1', + data: { + name: 'Travel', + size: '15kb', + type: 'Text' + }, + children: [ + { + key: '4-1-0', + data: { + name: 'Hotel.pdf', + size: '10kb', + type: 'PDF' + } + }, + { + key: '4-1-1', + data: { + name: 'Flight.pdf', + size: '5kb', + type: 'PDF' + } + } + ] + } + ] + }, + { + key: '5', + data: { + name: 'Main', + size: '50kb', + type: 'Folder' + }, + children: [ + { + key: '5-0', + data: { + name: 'bin', + size: '50kb', + type: 'Link' + } + }, + { + key: '5-1', + data: { + name: 'etc', + size: '100kb', + type: 'Link' + } + }, + { + key: '5-2', + data: { + name: 'var', + size: '100kb', + type: 'Link' + } + } + ] + }, + { + key: '6', + data: { + name: 'Other', + size: '5kb', + type: 'Folder' + }, + children: [ + { + key: '6-0', + data: { + name: 'todo.txt', + size: '3kb', + type: 'Text' + } + }, + { + key: '6-1', + data: { + name: 'logo.png', + size: '2kb', + type: 'Picture' + } + } + ] + }, + { + key: '7', + data: { + name: 'Pictures', + size: '150kb', + type: 'Folder' + }, + children: [ + { + key: '7-0', + data: { + name: 'barcelona.jpg', + size: '90kb', + type: 'Picture' + } + }, + { + key: '7-1', + data: { + name: 'primeng.png', + size: '30kb', + type: 'Picture' + } + }, + { + key: '7-2', + data: { + name: 'prime.jpg', + size: '30kb', + type: 'Picture' + } + } + ] + }, + { + key: '8', + data: { + name: 'Videos', + size: '1500kb', + type: 'Folder' + }, + children: [ + { + key: '8-0', + data: { + name: 'primefaces.mkv', + size: '1000kb', + type: 'Video' + } + }, + { + key: '8-1', + data: { + name: 'intro.avi', + size: '500kb', + type: 'Video' + } + } + ] + } + ]; + }, + + getTreeTableNodes() { + return Promise.resolve(this.getTreeTableNodesData()); + }, + + getTreeNodes() { + return Promise.resolve(this.getTreeNodesData()); + } +}; diff --git a/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx b/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx index 34bdcf14..89484a35 100644 --- a/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx +++ b/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx @@ -1,61 +1,90 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { Input } from '@/components/ui/input'; +import AssetThumbnail from '@/components/ui/asset-thumbnail'; import { Button } from '@/components/ui/button'; -import { XIcon } from 'lucide-react'; -import { z } from "zod"; -import { useForm } from 'react-hook-form'; +import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu'; +import AssetUploadButton from '@/components/ui/custom-buttons/asset-upload.button'; +import { Form, FormControl, FormField, FormItem, FormMessage, FormSuccessMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useLazySearchAssetsQuery, useLazyGetUserMostRecentlyUpdatedAssetsQuery, useLazyDownloadAssetQuery } from '@/state/assets'; + +import downloadFile from '@/utils/download-file'; +import { createSupabaseBrowserClient } from '@/utils/supabase/client'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Form, FormField, FormItem, FormControl, FormMessage } from '@/components/ui/form'; -import { useLazySearchAssetsQuery } from '@/state/supabase'; -import { useThrottleCallback } from '@react-hook/throttle' +import { ScrollArea } from '@radix-ui/react-scroll-area'; +import { useThrottleCallback } from '@react-hook/throttle'; +import { FileDown, XIcon } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from "zod"; const formSchema = z.object({ text: z.string().min(3) }) export default function Assets() { - // define the form + const [selectedAssetId, setSelectedAssetId] = useState(null); + + // Define the form const form = useForm>({ resolver: zodResolver(formSchema), mode: "onChange", defaultValues: { text: "", }, - // errors: error TODO add error handling here - }) - const [triggerSearch, { data: assets, isLoading, isSuccess, error }] = useLazySearchAssetsQuery() + }); + + const [triggerSearch, { data: assets, isLoading, isSuccess, error }] = useLazySearchAssetsQuery(); + const [triggerGetUserMostRecentlyUpdatedAssets, { data: recentAssets }] = useLazyGetUserMostRecentlyUpdatedAssetsQuery({}); + const [triggerDownloadAsset] = useLazyDownloadAssetQuery(); const throttledSubmit = useThrottleCallback(() => { - triggerSearch({ text: form.getValues("text") }) - }, 3, true) // the 4 if 4 FPS - // 2. Define a submit handler. + triggerSearch({ text: form.getValues("text")?.trim() }); + }, 3, true); + async function onSubmit(values: z.infer) { - throttledSubmit() + throttledSubmit(); } - // Reset the form values when the space data is fetched + // Watch the text input value useEffect(() => { - if (assets && isSuccess) { - form.reset({ - text: "", // Set the form value once space.name is available - }); + const subscription = form.watch(({ text }, { name, type }) => { + if (text && text?.length >= 3) { + form.handleSubmit(onSubmit)(); + } + }); + return () => subscription.unsubscribe(); + }, []); + + useEffect(() => { + triggerGetUserMostRecentlyUpdatedAssets({}); + }, [triggerGetUserMostRecentlyUpdatedAssets]); + + // Handle download action from the context menu + const handleDownload = async (assetId: string) => { + // const { data, error } = await triggerDownloadAsset({ assetId }); + const supabase = createSupabaseBrowserClient(); + + const { data, error } = await supabase.storage + .from('assets') // Use your actual bucket name + .download(`users/${assetId}`); + + if (data) { + // Use the helper function to trigger the file download + downloadFile(data, `${assetId}.jpg`); // Customize the filename as needed } - }, [isSuccess, form]); // Only run this effect when space or isLoading changes + }; return ( -
+
{/* Search bar */} - {/* */} -
- + + ( -
- + { + form.formState.isDirty && + }
- {/* TODO add better styling for this so it doesn't shift the input field */} + {assets?.length} Results
)} /> - - {/*
*/} + + {/* Asset Upload Button */} + {/* Scrollable area that takes up remaining space */}
-
- {assets?.map((image, index) => ( -
- {image.text} -

{image.text}

-
- ))} -
+ +
+ {form.formState.isSubmitted ? assets?.map((asset) => ( + + setSelectedAssetId(asset.id)}> +
+ +
+
+ + selectedAssetId && handleDownload(selectedAssetId)}> + + Download + + +
+ )) : recentAssets?.map((asset) => ( + + setSelectedAssetId(asset.id)}> +
+ +
+
+ + selectedAssetId && handleDownload(selectedAssetId)}> + + Download + + +
+ ))} +
+
); diff --git a/mirror-2/app/space/[spaceId]/build/(controlBar)/control-bar.tsx b/mirror-2/app/space/[spaceId]/build/(controlBar)/control-bar.tsx index e2edae6a..242b450e 100644 --- a/mirror-2/app/space/[spaceId]/build/(controlBar)/control-bar.tsx +++ b/mirror-2/app/space/[spaceId]/build/(controlBar)/control-bar.tsx @@ -25,10 +25,11 @@ export default function ControlBar() { return currentView === view ? "default" : "ghost"; }; + return ( - -