diff --git a/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx b/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx index d474d0d4..34bdcf14 100644 --- a/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx +++ b/mirror-2/app/space/[spaceId]/build/(controlBar)/assets.tsx @@ -1,84 +1,96 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { XIcon } from 'lucide-react'; +import { z } from "zod"; +import { useForm } from 'react-hook-form'; +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' -const imagesData = [ - // Mock data for images and text (30 items for 10 rows and 3 columns) - { src: 'https://via.placeholder.com/150', text: 'Image 1' }, - { src: 'https://via.placeholder.com/150', text: 'Image 2' }, - { src: 'https://via.placeholder.com/150', text: 'Image 3' }, - { src: 'https://via.placeholder.com/150', text: 'Image 4' }, - { src: 'https://via.placeholder.com/150', text: 'Image 5' }, - { src: 'https://via.placeholder.com/150', text: 'Image 6' }, - { src: 'https://via.placeholder.com/150', text: 'Image 7' }, - { src: 'https://via.placeholder.com/150', text: 'Image 8' }, - { src: 'https://via.placeholder.com/150', text: 'Image 9' }, - { src: 'https://via.placeholder.com/150', text: 'Image 10' }, - { src: 'https://via.placeholder.com/150', text: 'Image 11' }, - { src: 'https://via.placeholder.com/150', text: 'Image 12' }, - { src: 'https://via.placeholder.com/150', text: 'Image 13' }, - { src: 'https://via.placeholder.com/150', text: 'Image 14' }, - { src: 'https://via.placeholder.com/150', text: 'Image 15' }, - { src: 'https://via.placeholder.com/150', text: 'Image 16' }, - { src: 'https://via.placeholder.com/150', text: 'Image 17' }, - { src: 'https://via.placeholder.com/150', text: 'Image 18' }, - { src: 'https://via.placeholder.com/150', text: 'Image 19' }, - { src: 'https://via.placeholder.com/150', text: 'Image 20' }, - { src: 'https://via.placeholder.com/150', text: 'Image 21' }, - { src: 'https://via.placeholder.com/150', text: 'Image 22' }, - { src: 'https://via.placeholder.com/150', text: 'Image 23' }, - { src: 'https://via.placeholder.com/150', text: 'Image 24' }, - { src: 'https://via.placeholder.com/150', text: 'Image 25' }, - { src: 'https://via.placeholder.com/150', text: 'Image 26' }, - { src: 'https://via.placeholder.com/150', text: 'Image 27' }, - { src: 'https://via.placeholder.com/150', text: 'Image 28' }, - { src: 'https://via.placeholder.com/150', text: 'Image 29' }, - { src: 'https://via.placeholder.com/150', text: 'Image 30' }, - { src: 'https://via.placeholder.com/150', text: 'Image 11' }, - { src: 'https://via.placeholder.com/150', text: 'Image 12' }, - { src: 'https://via.placeholder.com/150', text: 'Image 13' }, - { src: 'https://via.placeholder.com/150', text: 'Image 14' }, - { src: 'https://via.placeholder.com/150', text: 'Image 15' }, - { src: 'https://via.placeholder.com/150', text: 'Image 16' }, - { src: 'https://via.placeholder.com/150', text: 'Image 17' }, - { src: 'https://via.placeholder.com/150', text: 'Image 18' }, - { src: 'https://via.placeholder.com/150', text: 'Image 19' }, - { src: 'https://via.placeholder.com/150', text: 'Image 20' }, - { src: 'https://via.placeholder.com/150', text: 'Image 21' }, - { src: 'https://via.placeholder.com/150', text: 'Image 22' }, - { src: 'https://via.placeholder.com/150', text: 'Image 23' }, - { src: 'https://via.placeholder.com/150', text: 'Image 24' }, - { src: 'https://via.placeholder.com/150', text: 'Image 25' }, - { src: 'https://via.placeholder.com/150', text: 'Image 26' }, - { src: 'https://via.placeholder.com/150', text: 'Image 27' }, - { src: 'https://via.placeholder.com/150', text: 'Image 28' }, - { src: 'https://via.placeholder.com/150', text: 'Image 29' }, - { src: 'https://via.placeholder.com/150', text: 'Image 30' } - -]; +const formSchema = z.object({ + text: z.string().min(3) +}) export default function Assets() { - const [searchTerm, setSearchTerm] = useState(''); + // 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 filteredImages = imagesData.filter((image) => - image.text.toLowerCase().includes(searchTerm.toLowerCase()) - ); + const throttledSubmit = useThrottleCallback(() => { + triggerSearch({ text: form.getValues("text") }) + }, 3, true) // the 4 if 4 FPS + // 2. Define a submit handler. + async function onSubmit(values: z.infer) { + throttledSubmit() + } + + // Reset the form values when the space data is fetched + useEffect(() => { + if (assets && isSuccess) { + form.reset({ + text: "", // Set the form value once space.name is available + }); + } + }, [isSuccess, form]); // Only run this effect when space or isLoading changes return (
{/* Search bar */} - setSearchTerm(e.target.value)} - className="mb-4 w-full" - /> + {/* */} +
+ + ( + + + +
+ + +
+
+ {/* TODO add better styling for this so it doesn't shift the input field */} + +
+ )} + /> + + + {/*
*/} + {/* Scrollable area that takes up remaining space */}
- {filteredImages.map((image, index) => ( + {assets?.map((image, index) => (
-
+
-
+
diff --git a/mirror-2/app/space/[spaceId]/build/space-viewport.tsx b/mirror-2/app/space/[spaceId]/build/space-viewport.tsx index bee4d069..8c972193 100644 --- a/mirror-2/app/space/[spaceId]/build/space-viewport.tsx +++ b/mirror-2/app/space/[spaceId]/build/space-viewport.tsx @@ -1,9 +1,9 @@ export default function SpaceViewport() { - return
+ return
-

+

3D Viewport

diff --git a/mirror-2/hooks/auth.tsx b/mirror-2/hooks/auth.tsx index c340f09d..408d8c3e 100644 --- a/mirror-2/hooks/auth.tsx +++ b/mirror-2/hooks/auth.tsx @@ -1,9 +1,9 @@ +"use client" import { useAppDispatch, useAppSelector } from "@/hooks/hooks"; import { updateLocalUserState, clearLocalUserState } from "@/state/local"; import { store } from "@/state/store"; import { createSupabaseBrowserClient } from "@/utils/supabase/client"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; export const signOut = async () => { const supabase = createSupabaseBrowserClient(); diff --git a/mirror-2/package.json b/mirror-2/package.json index 38cd6855..69f0e233 100644 --- a/mirror-2/package.json +++ b/mirror-2/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.2", + "@react-hook/throttle": "^2.2.0", "@reduxjs/toolkit": "^2.2.7", "@supabase/ssr": "latest", "@supabase/supabase-js": "latest", diff --git a/mirror-2/state/supabase.tsx b/mirror-2/state/supabase.tsx index b3c277c2..84024f38 100644 --- a/mirror-2/state/supabase.tsx +++ b/mirror-2/state/supabase.tsx @@ -8,6 +8,9 @@ export const supabaseApi = createApi({ baseQuery: fakeBaseQuery(), endpoints: (builder) => ({ + /** + * Spaces + */ createSpace: builder.mutation({ queryFn: async () => { const supabase = createSupabaseBrowserClient(); @@ -66,10 +69,107 @@ export const supabaseApi = createApi({ } }), + /** + * Assets + */ + // createAsset: builder.mutation({ + // queryFn: async () => { + // const supabase = createSupabaseBrowserClient(); + // const { data: { user } } = await supabase.auth.getUser() + // if (!user) { + // throw new Error('User not found') + // } + // /** + // * Upload the file + // */ + + + // /** + // * Add to DB + // */ + // const { data, error } = await supabase + // .from("assets") + // .insert([{ + // name: await generateSpaceName(), + // creator_user_id: user?.id, + // owner_user_id: user.id + // }]) + // .select('*') + // .single() + + // if (error) { + // return { error: error.message }; + // } + // return { data }; + // } + // }), + + getSingleAsset: builder.query({ + queryFn: async (assetId) => { + const supabase = createSupabaseBrowserClient(); + + const { data, error } = await supabase + .from("assets") + .select("*") + .eq("id", assetId) + .single() + + if (error) { + return { error: error.message }; + } + return { data }; + } + }), + + searchAssets: builder.query({ + queryFn: async ({ text }) => { + const supabase = createSupabaseBrowserClient(); + + const { data, error } = await supabase + .from("assets") + .select("*") + .eq("name", text) + .single() + + if (error) { + return { error: error.message }; + } + return { data }; + } + }), + + updateAsset: builder.mutation }>({ + queryFn: async ({ assetId, updateData }) => { + const supabase = createSupabaseBrowserClient(); + + const { data, error } = await supabase + .from("assets") + .update(updateData) + .eq("id", assetId) + .single() + + if (error) { + return { error: error.message }; + } + return { data }; + } + }), + }), }) // Export hooks for usage in functional components, which are // auto-generated based on the defined endpoints -export const { useGetSingleSpaceQuery, useUpdateSpaceMutation, useCreateSpaceMutation } = supabaseApi +export const { + /** + * Spaces + */ + useGetSingleSpaceQuery, useUpdateSpaceMutation, useCreateSpaceMutation, + + + /** + * Assets + */ + useSearchAssetsQuery, useLazySearchAssetsQuery, useGetSingleAssetQuery, useUpdateAssetMutation +} = supabaseApi diff --git a/mirror-2/yarn.lock b/mirror-2/yarn.lock index e1e20651..4a103548 100644 --- a/mirror-2/yarn.lock +++ b/mirror-2/yarn.lock @@ -595,6 +595,18 @@ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== +"@react-hook/latest@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@react-hook/latest/-/latest-1.0.3.tgz#c2d1d0b0af8b69ec6e2b3a2412ba0768ac82db80" + integrity sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg== + +"@react-hook/throttle@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@react-hook/throttle/-/throttle-2.2.0.tgz#d0402714a06e1ba0bc1da1fdf5c3c5cd0e08d45a" + integrity sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg== + dependencies: + "@react-hook/latest" "^1.0.2" + "@reduxjs/toolkit@^2.2.7": version "2.2.7" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.7.tgz#199e3d10ccb39267cb5aee92c0262fd9da7fdfb2"