+
+
+
-
-
-
-
+
+ {/* only loading children for separate data fetching to keep this as server component */}
+ {children}
)
diff --git a/mirror-2/app/space/[spaceId]/build/page.tsx b/mirror-2/app/space/[spaceId]/build/page.tsx
index e164033a..f6408623 100644
--- a/mirror-2/app/space/[spaceId]/build/page.tsx
+++ b/mirror-2/app/space/[spaceId]/build/page.tsx
@@ -1,12 +1,28 @@
"use client"
-import { useGetSingleSpaceQuery } from "@/state/supabase"
+import { useAppDispatch } from "@/hooks/hooks"
+import { setCurrentScene } from "@/state/local"
+import { useGetAllScenesQuery } from "@/state/scenes"
+import { useGetSingleSpaceQuery } from "@/state/spaces"
+
import { useParams } from "next/navigation"
+import { useEffect } from "react"
// blank page since we're using the parallel routes for spaceViewport, controlBar, etc.
export default function Page() {
const params = useParams<{ spaceId: string }>()
- const space = useGetSingleSpaceQuery(params.spaceId)
+ const { data: space, error } = useGetSingleSpaceQuery(params.spaceId)
+ const { data: scenes, isLoading: isScenesLoading } = useGetAllScenesQuery(params.spaceId)
+ // after successful query, update the current scene to the first in the space.scenes array
+ const dispatch = useAppDispatch();
+ useEffect(() => {
+ // if no current Scene, set it to the first scene
+ if (scenes?.length > 0 && scenes[0]) {
+ console.log("setting current scene to first scene", scenes[0])
+ dispatch(setCurrentScene(scenes[0].id))
+ }
+ }, [space])
+
return null
}
diff --git a/mirror-2/app/space/[spaceId]/build/space-viewport.tsx b/mirror-2/app/space/[spaceId]/build/space-viewport.tsx
index 8c972193..90583cbe 100644
--- a/mirror-2/app/space/[spaceId]/build/space-viewport.tsx
+++ b/mirror-2/app/space/[spaceId]/build/space-viewport.tsx
@@ -1,11 +1,26 @@
+"use client"
+
+import { useGetAllEntitiesQuery } from "@/state/entities";
+import { useGetAllScenesQuery } from "@/state/scenes";
+
+import { useParams } from "next/navigation";
+
export default function SpaceViewport() {
- return
-
-
- 3D Viewport
-
-
-
+ // // get all entities for the scene. may move this to a loader in the future
+ const params = useParams<{ spaceId: string }>()
+ // const { data: scenes } = useGetAllScenesQuery(params.spaceId);
+ // const { data: entities } = useGetAllEntitiesQuery(params.spaceId);
+
+
+ return (
+
+
+
+ 3D Viewport
+
+
+
+ );
}
diff --git a/mirror-2/app/space/[spaceId]/build/top-navbar.tsx b/mirror-2/app/space/[spaceId]/build/top-navbar.tsx
index 6cb05e00..a68e5e01 100644
--- a/mirror-2/app/space/[spaceId]/build/top-navbar.tsx
+++ b/mirror-2/app/space/[spaceId]/build/top-navbar.tsx
@@ -1,6 +1,7 @@
"use client"
import { EditableSpaceName } from "@/components/editable-space-name";
import { ThemeSwitcher } from "@/components/theme-switcher";
+import AccountDropdownMenu from "@/components/ui/account-dropdown-menu";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { signOut } from "@/hooks/auth";
@@ -21,7 +22,7 @@ export function TopNavbar() {
}, []);
return (
-
+
@@ -38,23 +39,7 @@ export function TopNavbar() {
>
Create Account
}
-
-
-
-
-
- {localUserState?.email || "Welcome"}
-
- {process.env.NEXT_PUBLIC_DISCORD_INVITE_URL && Chat on Discord}
-
- signOut()} className="cursor-pointer">
- Logout
-
-
-
+
);
}
diff --git a/mirror-2/app/space/new/page.tsx b/mirror-2/app/space/new/page.tsx
index 8ec94c1a..62070167 100644
--- a/mirror-2/app/space/new/page.tsx
+++ b/mirror-2/app/space/new/page.tsx
@@ -3,9 +3,9 @@
import { ProgressIndeterminate } from "@/components/ui/progress-indeterminate";
import { Skeleton } from "@/components/ui/skeleton";
import { useAppDispatch } from "@/hooks/hooks";
-import { useCreateSpaceMutation } from "@/state/supabase";
import { useEffect } from "react";
import { useRouter } from 'next/navigation'
+import { useCreateSpaceMutation } from "@/state/spaces";
export default function NewSpacePage() {
const [createSpace] = useCreateSpaceMutation()
@@ -23,7 +23,7 @@ export default function NewSpacePage() {
create()
}, [router])
return (
-
+
{/* Top Menu Bar */}
diff --git a/mirror-2/components/editable-space-name.tsx b/mirror-2/components/editable-space-name.tsx
index e343b2c6..02b5c535 100644
--- a/mirror-2/components/editable-space-name.tsx
+++ b/mirror-2/components/editable-space-name.tsx
@@ -2,7 +2,7 @@
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Skeleton } from "@/components/ui/skeleton";
-import { useGetSingleSpaceQuery, useUpdateSpaceMutation } from "@/state/supabase";
+import { useGetSingleSpaceQuery, useUpdateSpaceMutation } from "@/state/spaces";
import { zodResolver } from "@hookform/resolvers/zod";
import { useParams } from "next/navigation";
import { useEffect } from "react";
@@ -31,7 +31,7 @@ export function EditableSpaceName() {
// 2. Define a submit handler.
async function onSubmit(values: z.infer
) {
// update the space name
- await updateSpace({ spaceId: space.id, updateData: { name: values.name } })
+ await updateSpace({ id: space.id, updateData: { name: values.name } })
}
// Reset the form values when the space data is fetched
diff --git a/mirror-2/components/entity-tree/entity-tree.tsx b/mirror-2/components/entity-tree/entity-tree.tsx
new file mode 100644
index 00000000..6933add1
--- /dev/null
+++ b/mirror-2/components/entity-tree/entity-tree.tsx
@@ -0,0 +1,307 @@
+import React, { useEffect, useState } from 'react';
+import { ConfigProvider, Tree } from 'antd';
+import type { TreeDataNode, TreeProps } from 'antd';
+import { useAppSelector } from '@/hooks/hooks';
+import { useParams } from 'next/navigation';
+import { useGetSingleSpaceQuery } from '@/state/spaces';
+import { useGetAllScenesQuery } from '@/state/scenes';
+import { useUpsertEntityMutation, useGetAllEntitiesQuery, useGetSingleEntityQuery, useUpdateEntityMutation, useBatchUpdateEntitiesMutation } from '@/state/entities';
+import { getCurrentScene } from '@/state/local';
+import { skipToken } from '@reduxjs/toolkit/query';
+import { Database } from '@/utils/database.types';
+import { DataNode } from 'antd/es/tree';
+import { TwoWayInput } from '@/components/two-way-input';
+import { z } from 'zod';
+import { useUpdate } from 'ahooks';
+
+
+type TreeDataNodeWithEntityData = TreeDataNode & { name: string, id: string, order_under_parent: number }
+
+function transformDbEntityStructureToTree(entities): TreeDataNodeWithEntityData[] {
+ const entityMap: any = {}; // Map to hold entities by their ID for quick lookup
+
+ // Initialize the map and add a 'children' array to each entity
+ entities.forEach(entity => {
+ entityMap[entity.id] = { ...entity, children: [], key: entity.id };
+ });
+
+ //disable the root node
+ entities.find(entity => {
+ if (entity.parent_id === null) {
+ entityMap[entity.id].disabled = true;
+ }
+ });
+
+ const tree: any[] = [];
+
+ // Build the tree by linking parent and child entities
+ entities.forEach(entity => {
+ if (entity.parent_id) {
+ // If the entity has a parent, add it to the parent's 'children' array
+ const parentEntity = entityMap[entity.parent_id];
+ if (parentEntity) {
+ parentEntity.children.push(entityMap[entity.id]);
+ } else {
+ // Handle case where parent_id does not exist in the entityMap
+ console.warn(`Parent entity with ID ${entity.parent_id} not found.`);
+ tree.push(entityMap[entity.id]); // Treat as root if parent not found
+ }
+ } else {
+ // If the entity has no parent, it's a root entity
+ tree.push(entityMap[entity.id]);
+ }
+ });
+
+ // Function to recursively sort children based on 'order_under_parent'
+ function sortChildren(entity) {
+ if (entity.children && entity.children.length > 0) {
+ // Sort the children array based on 'order_under_parent'
+ entity.children.sort((a, b) => {
+ const orderA = a.order_under_parent ?? 0;
+ const orderB = b.order_under_parent ?? 0;
+ return orderA - orderB;
+ });
+
+ // Recursively sort the children's children
+ entity.children.forEach(child => sortChildren(child));
+ }
+ }
+
+ // Sort the root-level entities if needed
+ tree.sort((a, b) => {
+ const orderA = a.order_under_parent ?? 0;
+ const orderB = b.order_under_parent ?? 0;
+ return orderA - orderB;
+ });
+
+ // Recursively sort the entire tree
+ tree.forEach(rootEntity => sortChildren(rootEntity));
+
+ return tree;
+}
+
+const EntityTree: React.FC = () => {
+ const [treeData, setTreeData] = useState([]);
+
+ const currentScene = useAppSelector(getCurrentScene)
+ const params = useParams<{ spaceId: string }>()
+ const { data: space } = useGetSingleSpaceQuery(params.spaceId)
+ const { data: scenes, isLoading: isScenesLoading } = useGetAllScenesQuery(params.spaceId)
+ const { data: entities, isFetching: isEntitiesFetching } = useGetAllEntitiesQuery(
+ scenes && scenes.length > 0 ? scenes.map(scene => scene.id) : skipToken // Conditional query
+ );
+
+ const [updateEntity] = useUpdateEntityMutation();
+ const [batchUpdateEntity] = useBatchUpdateEntitiesMutation();
+
+ // useEffect(() => {
+ // debugger
+ // if (currentScene) {
+ // getSingleSpace(currentScene);
+ // }
+ // }, [currentScene]);
+ useEffect(() => {
+ if (entities && entities.length > 0) {
+ const data = transformDbEntityStructureToTree(entities)
+ // debugger
+ setTreeData(data)
+ // updateState({ entities, type: 'set-tree', itemId: '' });
+ }
+ }, [entities]); // Re-run effect when 'entities' changes
+
+
+ const onDragEnter: TreeProps['onDragEnter'] = (info) => {
+ console.log(info);
+ };
+
+ const onDrop: TreeProps['onDrop'] = (info) => {
+ console.log(info);
+
+ // check to ensure not creating new root. stop if so
+ if (info.node['parent_id'] === null && info.dropToGap === true) {
+ return
+ }
+
+ const dropKey = info.node.key;
+ const dragKey = info.dragNode.key;
+ const dropPos = info.node.pos.split('-');
+ const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1
+
+ const loop = (
+ data: TreeDataNode[],
+ key: React.Key,
+ callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void,
+ ) => {
+ for (let i = 0; i < data.length; i++) {
+ if (data[i].key === key) {
+ return callback(data[i], i, data);
+ }
+ if (data[i].children) {
+ loop(data[i].children!, key, callback);
+ }
+ }
+ };
+
+ const data = [...treeData];
+
+ // Find dragObject
+ let dragObj: TreeDataNode;
+ loop(data, dragKey, (item, index, arr) => {
+ arr.splice(index, 1);
+ dragObj = item;
+ });
+
+ if (!info.dropToGap) {
+
+ const entitiesUpdateArray: any[] = []
+ const mainEntityId = info.dragNode['id']
+ console.log('content drop to new parent', data)
+ // Drop on the content
+ let newPosition = 0
+ loop(data, dropKey, (item) => {
+ item.children = item.children || [];
+ // where to insert. New item was inserted to the start of the array in this example, but can be anywhere
+ item.children.unshift(dragObj);
+
+ item.children.forEach((child, index) => {
+ const order_under_parent = index
+ let updateData = { id: child['id'], order_under_parent, name: child['name'] }
+ if (child['id'] === mainEntityId) {
+ updateData = Object.assign({}, updateData, {
+ parent_id: info.node['id'] // should be info.node here, which is the node the dragNode is getting dropped under, so we want the ID of that node (note: different from getting the parent_id of it, below)
+ })
+ }
+ entitiesUpdateArray.push(updateData)
+ })
+ });
+
+ // do the same for data.children: this updates the order_under_parent of the list that the dragNode is being moved OUT of
+ if (data && data[0] && data[0].children) {
+ data[0].children.forEach((child, index) => {
+ const order_under_parent = index
+ let updateData = { id: child['id'], order_under_parent, name: child['name'] }
+ if (child['id'] === mainEntityId) {
+ updateData = Object.assign({}, updateData, {
+ parent_id: info.node['id'] // should be info.node here, which is the node the dragNode is getting dropped under, so we want the ID of that node (note: different from getting the parent_id of it, below)
+ })
+ }
+ entitiesUpdateArray.push(updateData)
+ })
+ }
+
+ batchUpdateEntity({
+ entities: entitiesUpdateArray
+ })
+
+ // update the node and dragnode in DB
+ // if (info.node && info.node['id'] && info.node.children) {
+ // const childIds = info.node.children.map(child => child['id'])
+ // // debugger
+ // updateEntity({ id: info.node['id'], updateData: { children: childIds } })
+ // }
+ // if (info.dragNode && info.dragNode['id'] && info.dragNode.children) {
+ // const childIds = info.dragNode.children.map(child => child['id'])
+ // // debugger
+ // updateEntity({ id: info.dragNode['id'], updateData: { children: childIds } })
+ // }
+
+ } else {
+ console.log('gap drop', data)
+
+ const entitiesUpdateArray: any[] = []
+ const mainEntityId = info.dragNode['id']
+ let ar: TreeDataNode[] = [];
+ let i: number;
+ loop(data, dropKey, (_item, index, arr) => {
+ ar = arr;
+ i = index;
+ });
+ if (dropPosition === -1) {
+ // Drop on the top of the drop node
+ ar.splice(i!, 0, dragObj!);
+ } else {
+ // Drop on the bottom of the drop node
+ ar.splice(i! + 1, 0, dragObj!);
+ }
+ // get the new position from ar
+ // updates order_under_parent of each
+ ar.forEach((child, index) => {
+ const order_under_parent = index
+ let updateData = { id: child['id'], order_under_parent, name: child['name'] }
+ if (child['id'] === mainEntityId) {
+ updateData = Object.assign({}, updateData, {
+ parent_id: info.node['parent_id'] // should be info.node here, which is the node the dragNode is getting dropped under, so we want the parent_id of THAT node
+ })
+ }
+ entitiesUpdateArray.push(updateData)
+ })
+
+ batchUpdateEntity({
+ entities: entitiesUpdateArray
+ })
+
+ // debugger // not yet
+ // update the node and dragnode in DB
+ // if (info.node && info.node['id'] && info.node.children) {
+ // const childIds = info.node.children.map(child => child['id'])
+ // updateEntity({ id: info.node['id'], updateData: { children: childIds } })
+ // }
+ // if (info.dragNode && info.dragNode['id'] && info.dragNode.children) {
+ // const childIds = info.dragNode.children.map(child => child['id'])
+ // updateEntity({ id: info.dragNode['id'], updateData: { children: childIds } })
+ // }
+ }
+
+ setTreeData(data);
+
+ };
+
+ return (
+
+ (
+ <>
+
+ {nodeData.order_under_parent}
+ >
+ )}
+ />
+
+ );
+};
+
+export default EntityTree;
diff --git a/mirror-2/components/two-way-input.tsx b/mirror-2/components/two-way-input.tsx
new file mode 100644
index 00000000..b241d46d
--- /dev/null
+++ b/mirror-2/components/two-way-input.tsx
@@ -0,0 +1,95 @@
+"use client";
+import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Skeleton } from "@/components/ui/skeleton";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { z, ZodSchema } from "zod";
+import clsx from "clsx"; // Utility to merge class names
+import { cn } from "@/lib/utils";
+
+interface TwoWayInputProps {
+ id: string;
+ generalEntity: any;
+ fieldName: keyof T;
+ formSchema: ZodSchema;
+ defaultValue: string;
+ // "General" entity bc not referring our proper Entity, but anything
+ useGeneralGetEntityQuery: (id: string) => { data?: T; isLoading: boolean; isSuccess: boolean; error?: any };
+ // "General" entity bc not referring our proper Entity, but anything
+ useGeneralUpdateEntityMutation: () => readonly [
+ (args: { id: string;[fieldName: string]: any }) => any, // The mutation trigger function
+ { isLoading: boolean; isSuccess: boolean; error?: any }
+ ];
+ className?: string; // Optional className prop
+}
+// TODO fix and ensure deduping works correctly to not fire a ton of network requests
+export function TwoWayInput({
+ id: generalEntityId,
+ generalEntity,
+ fieldName,
+ formSchema,
+ defaultValue,
+ useGeneralGetEntityQuery, // "General" entity bc not referring our proper Entity, but anything
+ useGeneralUpdateEntityMutation, // "General" entity bc not referring our proper Entity, but anything
+ className, // Destructure the className prop
+}: TwoWayInputProps) {
+ const { data: entity, isLoading, isSuccess } = useGeneralGetEntityQuery(generalEntityId);
+
+ // Destructure the mutation trigger function and its state from the readonly tuple
+ const [updateGeneralEntity, { isLoading: isUpdating, isSuccess: isUpdated, error }] = useGeneralUpdateEntityMutation();
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ mode: "onBlur",
+ defaultValues: {
+ [fieldName]: entity?.[fieldName] ?? defaultValue,
+ },
+ });
+
+ // Handle form submission
+ async function onSubmit(values: z.infer) {
+ // check if values changed
+ if (entity && isSuccess && entity[fieldName] === values[fieldName]) {
+ return;
+ }
+ await updateGeneralEntity({ id: generalEntityId, ...generalEntity, [fieldName]: values[fieldName] });
+ }
+
+ // Reset form when entity data is fetched
+ useEffect(() => {
+ if (entity && isSuccess) {
+ form.reset({
+ [fieldName]: entity[fieldName] ?? defaultValue,
+ });
+ }
+ }, [entity, isSuccess, form]);
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+
+ );
+}
diff --git a/mirror-2/components/ui/account-dropdown-menu.tsx b/mirror-2/components/ui/account-dropdown-menu.tsx
new file mode 100644
index 00000000..e6625034
--- /dev/null
+++ b/mirror-2/components/ui/account-dropdown-menu.tsx
@@ -0,0 +1,30 @@
+"use client"
+import { Button } from "@/components/ui/button";
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
+import { signOut } from "@/hooks/auth";
+import { useAppSelector } from "@/hooks/hooks";
+import { selectLocalUserState } from "@/state/local";
+import { CircleUser } from "lucide-react";
+import Link from "next/link";
+
+export default function AccountDropdownMenu() {
+ const localUserState = useAppSelector(selectLocalUserState)
+
+ return (
+
+
+
+
+ {localUserState?.email || "Welcome"}
+
+ {process.env.NEXT_PUBLIC_DISCORD_INVITE_URL && Chat on Discord}
+
+ signOut()} className="cursor-pointer">
+ Logout
+
+
+ )
+}
diff --git a/mirror-2/components/ui/asset-thumbnail.tsx b/mirror-2/components/ui/asset-thumbnail.tsx
new file mode 100644
index 00000000..f8d2bce0
--- /dev/null
+++ b/mirror-2/components/ui/asset-thumbnail.tsx
@@ -0,0 +1,36 @@
+import React, { useEffect, useState } from 'react';
+import Image from 'next/image';
+
+interface AssetThumbnailProps {
+ imageUrl: string;
+ name: string;
+ size?: number;
+}
+
+const AssetThumbnail: React.FC = ({ imageUrl, name, size = 64 }) => {
+ // Primarily used for dev since haven't uploaded storage on seed data
+ const [devImgSrc, setDevImgSrc] = useState(imageUrl);
+
+ useEffect(() => {
+ if (process.env.NODE_ENV === 'development') {
+ setDevImgSrc("/dev/150.jpg");
+ }
+ }, []);
+
+ return (
+
+ );
+};
+
+export default AssetThumbnail;
diff --git a/mirror-2/components/ui/context-menu.tsx b/mirror-2/components/ui/context-menu.tsx
index c323601d..68b43467 100644
--- a/mirror-2/components/ui/context-menu.tsx
+++ b/mirror-2/components/ui/context-menu.tsx
@@ -80,7 +80,7 @@ const ContextMenuItem = React.forwardRef<
+
+
+ >
+ )
+}
diff --git a/mirror-2/components/ui/form.tsx b/mirror-2/components/ui/form.tsx
index e7ef2d8e..4808c7ec 100644
--- a/mirror-2/components/ui/form.tsx
+++ b/mirror-2/components/ui/form.tsx
@@ -10,6 +10,7 @@ import {
FieldValues,
FormProvider,
useFormContext,
+ useFormState,
} from "react-hook-form"
import { cn } from "@/lib/utils"
@@ -166,6 +167,24 @@ const FormMessage = React.forwardRef<
})
FormMessage.displayName = "FormMessage"
+const FormSuccessMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { isSubmitSuccessful } = useFormState()
+
+ return (
+ (isSubmitSuccessful &&
+ {children}
+
)
+ )
+})
+FormSuccessMessage.displayName = "FormSuccessMessage"
+
export {
useFormField,
Form,
@@ -174,5 +193,6 @@ export {
FormControl,
FormDescription,
FormMessage,
+ FormSuccessMessage,
FormField,
}
diff --git a/mirror-2/hooks/auth.tsx b/mirror-2/hooks/auth.tsx
index 408d8c3e..67ae6aa8 100644
--- a/mirror-2/hooks/auth.tsx
+++ b/mirror-2/hooks/auth.tsx
@@ -32,8 +32,8 @@ export function useSetupAuthEvents() {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
function handleLogin() {
if (session?.user) {
- console.log('auth: DID fire')
- console.log("updateLocalUserState", event, session?.user)
+ // console.log('auth: DID fire')
+ // console.log("updateLocalUserState", event, session?.user)
const { id, email, is_anonymous } = session.user
dispatch(updateLocalUserState({ id, email, is_anonymous }))
} else {
@@ -45,7 +45,7 @@ export function useSetupAuthEvents() {
dispatch(clearLocalUserState())
router.push("/login")
}
- console.log("auth", event, session)
+ // console.log("auth", event, session)
if (event === 'INITIAL_SESSION') {
// handle initial session
if (session?.user) {
diff --git a/mirror-2/hooks/file-upload.tsx b/mirror-2/hooks/file-upload.tsx
new file mode 100644
index 00000000..4f37924f
--- /dev/null
+++ b/mirror-2/hooks/file-upload.tsx
@@ -0,0 +1,32 @@
+import { useCallback } from "react";
+
+import { Database } from "@/utils/database.types";
+import { useCreateAssetMutation } from "@/state/assets";
+
+
+export function useGetFileUpload() {
+ // Get the mutation hook
+ const [createAsset] = useCreateAssetMutation();
+
+ return useCallback((acceptedFiles: File[]) => {
+ acceptedFiles.forEach((file) => {
+ // Create the asset record for each file uploaded
+ const assetData = {
+ name: file.name, // Use the file name as the asset name
+ // description: 'Auto-generated description', // TODO add description support
+ };
+
+ createAsset({
+ assetData, // Asset data with name and description
+ file, // File to upload
+ })
+ .unwrap() // Unwraps the result to handle promise resolution
+ .then((response) => {
+ console.log('File uploaded and asset created successfully:', response);
+ })
+ .catch((error) => {
+ console.error('Error uploading file or creating asset:', error);
+ });
+ });
+ }, [createAsset]);
+}
diff --git a/mirror-2/next.config.js b/mirror-2/next.config.js
index 4b10fa4d..c61491f3 100644
--- a/mirror-2/next.config.js
+++ b/mirror-2/next.config.js
@@ -13,7 +13,7 @@ const nextConfig = {
}
],
images: {
- domains: ['images.unsplash.com'],
+ domains: ['images.unsplash.com', '127.0.0.1', 'localhost', 'picsum.photos'],
},
};
diff --git a/mirror-2/package.json b/mirror-2/package.json
index 69f0e233..eafd203f 100644
--- a/mirror-2/package.json
+++ b/mirror-2/package.json
@@ -5,6 +5,7 @@
"types": "npx supabase gen types --lang=typescript --local > ./utils/database.types.ts",
"reset": "supabase db reset && yarn types",
"build": "next build",
+ "m": "supabase migration new",
"start": "next start"
},
"dependencies": {
@@ -26,6 +27,8 @@
"@reduxjs/toolkit": "^2.2.7",
"@supabase/ssr": "latest",
"@supabase/supabase-js": "latest",
+ "ahooks": "^3.8.1",
+ "antd": "^5.21.2",
"autoprefixer": "10.4.17",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@@ -33,13 +36,18 @@
"lucide-react": "^0.446.0",
"next": "latest",
"next-themes": "^0.3.0",
+ "playcanvas": "^2.0.0",
"prettier": "^3.3.3",
+ "primereact": "^10.8.3",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-dropzone": "^14.2.9",
"react-hook-form": "^7.53.0",
"react-redux": "^9.1.2",
"react-resizable-panels": "^2.1.4",
+ "redux-undo": "^1.1.0",
"server-only": "^0.0.1",
+ "tiny-invariant": "^1.3.3",
"unique-names-generator": "^4.7.1",
"use-sound": "^4.0.3",
"zod": "^3.23.8"
diff --git a/mirror-2/public/dev/150.jpg b/mirror-2/public/dev/150.jpg
new file mode 100644
index 00000000..745c7d0d
Binary files /dev/null and b/mirror-2/public/dev/150.jpg differ
diff --git a/mirror-2/state/assets.tsx b/mirror-2/state/assets.tsx
new file mode 100644
index 00000000..c64bd5f4
--- /dev/null
+++ b/mirror-2/state/assets.tsx
@@ -0,0 +1,218 @@
+import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit';
+import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
+import { createSupabaseBrowserClient } from '@/utils/supabase/client';
+import { Database } from '@/utils/database.types';
+
+export const ASSETS_BUCKET_USERS_FOLDER = 'users' // used for the assets bucket
+export const ASSETS_BUCKET_VERSIONED_ASSETS_FOLDER = 'versioned' // generally immutable, used for space_versions (published Spaces/games)
+const TABLE_NAME = "assets"
+
+export interface CreateAssetMutation {
+ name: string
+}
+export const TAG_NAME_FOR_GENERAL_ENTITY = 'Assets'
+
+// Supabase API for spaces
+export const assetsApi = createApi({
+ reducerPath: 'assetsApi',
+ baseQuery: fakeBaseQuery(),
+ tagTypes: [TAG_NAME_FOR_GENERAL_ENTITY, 'LIST'],
+ endpoints: (builder) => ({
+ createAsset: builder.mutation({
+ queryFn: async ({ assetData, file }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ // Get the authenticated user
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
+ if (!user) {
+ return { error: 'User not found' };
+ }
+
+ // Prepare the data to insert, without file_url and thumbnail_url yet
+ const assetInsertData: Database['public']['Tables']['assets']['Insert'] = {
+ ...assetData,
+ creator_user_id: user.id,
+ owner_user_id: user.id,
+ file_url: '', // Placeholder, will update after file upload
+ thumbnail_url: '', // Placeholder, will update after file upload
+ };
+
+ // Insert the asset (without file URL and thumbnail URL for now)
+ const { data: insertedAsset, error: insertError }: {
+ data: Database['public']['Tables']['assets']['Row'] | null,
+ error: any
+ } = await supabase
+ .from(TABLE_NAME)
+ .insert([assetInsertData])
+ .select('*')
+ .single();
+
+ if (insertError || !insertedAsset) {
+ return { error: insertError.message };
+ }
+
+ // Variable to store the file path (if file exists)
+ let filePath = '';
+
+ // Check if a file is passed for upload
+ if (file) {
+ // Generate a unique file name for Supabase Storage
+ filePath = `${ASSETS_BUCKET_USERS_FOLDER}/${insertedAsset.id}`;
+
+ // Upload the file to Supabase Storage
+ const { error: uploadError } = await supabase.storage
+ .from('assets') // Replace with your bucket name
+ .upload(filePath, file);
+
+ // Handle file upload error
+ if (uploadError) {
+ return { error: uploadError.message };
+ }
+
+ // Get the public URL of the uploaded file
+ const { data: fileUrlData } = supabase.storage.from('assets').getPublicUrl(filePath);
+ const fileUrl = fileUrlData?.publicUrl;
+
+ // Create a thumbnail URL using Supabase transform (resize)
+ const { data: thumbnailUrlData } = supabase.storage.from('assets').getPublicUrl(filePath, {
+ transform: {
+ width: 150,
+ height: 150,
+ }
+ });
+ const thumbnailUrl = thumbnailUrlData?.publicUrl;
+
+ // Update the asset with the file URL and thumbnail URL
+ const { error: updateError } = await supabase
+ .from(TABLE_NAME)
+ .update({
+ file_url: fileUrl,
+ thumbnail_url: thumbnailUrl,
+ })
+ .eq('id', insertedAsset.id) // Use the inserted asset's ID for the update
+ .single();
+
+ if (updateError) {
+ return { error: updateError.message };
+ }
+ }
+
+ return { data: insertedAsset };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ getSingleAsset: builder.query({
+ queryFn: async (assetId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .select("*")
+ .eq("id", assetId)
+ .single()
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result, error, id) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id }],
+ }),
+
+
+ getUserMostRecentlyUpdatedAssets: builder.query({
+ queryFn: async () => {
+ const supabase = createSupabaseBrowserClient();
+ const { data: { user } } = await supabase.auth.getUser()
+ if (!user) {
+ throw new Error('User not found')
+ }
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .select("*")
+ .eq("owner_user_id", user.id)
+ .order("updated_at", { ascending: false })
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result) =>
+ result
+ ? [
+ ...result.map(({ id }) => ({ type: TAG_NAME_FOR_GENERAL_ENTITY, id })),
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' },
+ ]
+ : [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ },),
+
+
+ searchAssets: builder.query({
+ queryFn: async ({ text }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ // replace spaces with +
+ const friendlyText = text?.trim().replaceAll(" ", "&")
+ const { data, error } = await supabase
+ .rpc("search_assets_by_name_prefix", { 'prefix': friendlyText })
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result) =>
+ result
+ ? [
+ ...result.map(({ id }) => ({ type: TAG_NAME_FOR_GENERAL_ENTITY, id })),
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' },
+ ]
+ : [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ updateAsset: builder.mutation }>({
+ queryFn: async ({ id: assetId, updateData }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .update(updateData)
+ .eq("id", assetId)
+ .single()
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, { id: assetId }) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: assetId }],
+ }),
+
+
+ downloadAsset: builder.query({
+ queryFn: async ({ assetId }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ // Return the public URL for the file to allow download
+ const { data, error } = await supabase.storage
+ .from('assets') // Use your actual bucket name
+ .download(`users/${assetId}`);
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ }
+ }),
+
+
+ }),
+});
+
+
+// Export the API hooks
+export const {
+ useCreateAssetMutation, useSearchAssetsQuery, useLazySearchAssetsQuery, useGetSingleAssetQuery, useLazyGetUserMostRecentlyUpdatedAssetsQuery, useUpdateAssetMutation, useLazyDownloadAssetQuery,
+} = assetsApi;
diff --git a/mirror-2/state/components.tsx b/mirror-2/state/components.tsx
new file mode 100644
index 00000000..bea121f1
--- /dev/null
+++ b/mirror-2/state/components.tsx
@@ -0,0 +1,131 @@
+import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit';
+import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
+import { createSupabaseBrowserClient } from '@/utils/supabase/client';
+import { Database } from '@/utils/database.types';
+
+export const TAG_NAME_FOR_GENERAL_ENTITY = 'Components'
+const TABLE_NAME = "components"
+export type ComponentKey = Database['public']['Enums']['component_key'];
+// Supabase API for spaces
+export const componentsApi = createApi({
+ reducerPath: 'componentsApi',
+ baseQuery: fakeBaseQuery(),
+ tagTypes: [TAG_NAME_FOR_GENERAL_ENTITY, 'LIST'],
+ endpoints: (builder) => ({
+ createComponent: builder.mutation({
+ queryFn: async ({ entity_id, component_key }) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
+
+ if (!user) {
+ return { error: 'User not found' };
+ }
+
+ if (!entity_id) {
+ return { error: 'No entity_id provided' };
+ }
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .insert([{
+ entity_id,
+ component_key,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString()
+ }])
+ .select('*')
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+
+ }),
+
+ getAllComponents: builder.query({
+ queryFn: async (entityId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .select("*")
+ .eq("entity_id", entityId);
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result) =>
+ result
+ ? [
+ ...result.map(({ id }) => ({ type: TAG_NAME_FOR_GENERAL_ENTITY, id })),
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' },
+ ]
+ : [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ getSingleComponent: builder.query({
+ queryFn: async (id) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .select("*")
+ .eq("id", id)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result, error, id) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id }],
+ }),
+
+ updateComponent: builder.mutation }>({
+ queryFn: async ({ id, updateData }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .update(updateData)
+ .eq("id", id)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, { id }) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id }],
+ }),
+
+ deleteComponent: builder.mutation({
+ queryFn: async (id) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from(TABLE_NAME)
+ .delete()
+ .eq("id", id)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, id) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id }]
+ }),
+
+ }),
+});
+
+// Export the API hooks
+export const {
+ useCreateComponentMutation, useGetAllComponentsQuery, useUpdateComponentMutation, useGetSingleComponentQuery, useLazyGetAllComponentsQuery, useDeleteComponentMutation
+} = componentsApi;
diff --git a/mirror-2/state/entities.tsx b/mirror-2/state/entities.tsx
new file mode 100644
index 00000000..eb109d01
--- /dev/null
+++ b/mirror-2/state/entities.tsx
@@ -0,0 +1,267 @@
+import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit';
+import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
+import { createSupabaseBrowserClient } from '@/utils/supabase/client';
+import { Database } from '@/utils/database.types';
+import { TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY } from '@/state/shared-cache-tags';
+
+
+export const TAG_NAME_FOR_GENERAL_ENTITY = 'Entities'
+
+// Supabase API for spaces
+export const entitiesApi = createApi({
+ reducerPath: 'entitiesApi',
+ baseQuery: fakeBaseQuery(),
+ tagTypes: [TAG_NAME_FOR_GENERAL_ENTITY, 'LIST', TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY],
+ endpoints: (builder) => ({
+ upsertEntity: builder.mutation({
+ queryFn: async ({ id, name, scene_id, parent_id, order_under_parent, isRootEntity }) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
+
+ if (!user) {
+ return { error: 'User not found' };
+ }
+
+ // if no parent_id and not the root entity upon creation, find the root entity
+ if (!parent_id && !isRootEntity) {
+ const { data: rootEntity, error: rootEntityError } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("scene_id", scene_id)
+ .is("parent_id", null)
+ .single();
+
+ if (rootEntityError) {
+ return { error: rootEntityError.message };
+ }
+
+ parent_id = rootEntity.id;
+ }
+
+ if ((parent_id && order_under_parent === undefined) || (parent_id && order_under_parent) === null) {
+ // need to find the order_under_parent to use for the new entity
+ const { data: entitiesWithSameParent, error: parentEntityError } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("parent_id", parent_id)
+
+ if (parentEntityError) {
+ return { error: parentEntityError.message };
+ }
+ // find the highest order_under_parent
+ const highestOrderUnderParent = entitiesWithSameParent.reduce((max, entity) => {
+ return entity.order_under_parent > max ? entity.order_under_parent : max;
+ }, -1);
+ order_under_parent = highestOrderUnderParent + 1;
+ }
+
+
+ const { data, error } = await supabase
+ .from("entities")
+ .upsert({
+ id,
+ name,
+ scene_id,
+ parent_id,
+ order_under_parent,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString()
+ })
+ .select('*')
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+
+ return { data };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+
+ }),
+
+ getAllEntities: builder.query({
+ queryFn: async (sceneId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ if (!sceneId) {
+ return { error: 'No scene_id provided' };
+ }
+
+ const { data, error } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("scene_id", sceneId);
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result) =>
+ result
+ ? [
+ ...result.map(({ id }) => ({ type: TAG_NAME_FOR_GENERAL_ENTITY, id })),
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' },
+ ]
+ : [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ getSingleEntity: builder.query({
+ queryFn: async (entityId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("id", entityId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result, error, entityId) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: entityId }], // Provide the entity tag based on entityId
+ }),
+
+ updateEntity: builder.mutation({
+ queryFn: async ({ id, name, parent_id, order_under_parent, scene_id }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ // if no parent_id, find the root entity
+ if (!parent_id) {
+ if (!scene_id) {
+ // must have scene_id to find the root entity
+ return { error: 'No scene_id provided' };
+ }
+ const { data: rootEntity, error: rootEntityError } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("scene_id", scene_id)
+ .is("parent_id", null)
+ .single();
+
+ if (rootEntityError) {
+ return { error: rootEntityError.message };
+ }
+
+ parent_id = rootEntity.id;
+ }
+
+ // case: parent_id exists but no order_under_parent
+ if ((parent_id && order_under_parent === undefined) || (parent_id && order_under_parent === null)) {
+ // need to find the order_under_parent to use for the new entity
+ const { data: entitiesWithSameParent, error: parentEntityError } = await supabase
+ .from("entities")
+ .select("*")
+ .eq("parent_id", parent_id)
+
+ if (parentEntityError) {
+ return { error: parentEntityError.message };
+ }
+ // find the highest order_under_parent
+ const highestOrderUnderParent = entitiesWithSameParent.reduce((max, entity) => {
+ return entity.order_under_parent > max ? entity.order_under_parent : max;
+ }, -1);
+ order_under_parent = highestOrderUnderParent + 1;
+ }
+
+ // case: parent_id and order_under_parent exist
+ // if (parent_id && order_under_parent) {
+ // // increment all order_under_parents below the new entity since it's being inserted
+ // const { data, error }: { data: any, error: any } = await supabase
+ // // @ts-ignore
+ // .rpc('increment_and_resequence_order_under_parent', { p_scene_id: scene_id, p_entity_id: id })
+ // .single();
+ // }
+
+
+ const { data, error } = await supabase
+ .from("entities")
+ .update(
+ { name, parent_id, order_under_parent, scene_id }
+ )
+ .eq("id", id)
+ .single();
+
+ if (error) {
+ debugger
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, { id: entityId }) => [
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: entityId },
+ TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY
+ ], // Invalidate tag for entityId
+ }),
+
+ batchUpdateEntities: builder.mutation({
+ queryFn: async ({ entities }) => {
+ const supabase = createSupabaseBrowserClient();
+ const entityIds = entities.map(entity => entity.id);
+
+ // Fetch all current entities by the provided IDs. This is needed since supabase batch upsert requires ALL properties to be passed in or else it overwrites the existing data.
+ const { data: existingEntities, error: fetchError } = await supabase
+ .from('entities')
+ .select('*')
+ .in('id', entityIds);
+
+ if (fetchError) {
+ return { error: fetchError.message };
+ }
+
+ // Merge each entity's new properties with existing data
+ const entitiesToUpsert = entities.map(newEntity => {
+ const existingEntity = existingEntities.find(e => e.id === newEntity.id);
+
+ // Merge existing entity fields with new updates
+ return {
+ ...existingEntity, // Existing entity data
+ ...newEntity, // New updates, this will override fields like name, scene_id, etc.
+ updated_at: new Date().toISOString(), // Update timestamp
+ };
+ });
+
+ // Perform the batch upsert
+ const { data: upsertData, error: upsertError } = await supabase
+ .from('entities')
+ .upsert(entitiesToUpsert)
+ .select('*'); // You can select specific fields if you want
+
+ if (upsertError) {
+ return { error: upsertError.message };
+ }
+
+ return { data: upsertData };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }], // Invalidates the cache
+ }),
+
+ deleteEntity: builder.mutation({
+ queryFn: async (entityId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("entities")
+ .delete()
+ .eq("id", entityId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, entityId) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: entityId }]
+ }),
+
+ }),
+});
+
+
+// Export the API hooks
+export const {
+ useUpsertEntityMutation, useBatchUpdateEntitiesMutation, useGetAllEntitiesQuery, useUpdateEntityMutation, useGetSingleEntityQuery, useLazyGetAllEntitiesQuery, useDeleteEntityMutation
+} = entitiesApi;
diff --git a/mirror-2/state/local.tsx b/mirror-2/state/local.tsx
index 4a3d971a..72a3cfaf 100644
--- a/mirror-2/state/local.tsx
+++ b/mirror-2/state/local.tsx
@@ -1,6 +1,7 @@
import { RootState } from '@/state/store'
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
+import { Scene } from 'playcanvas'
export type ControlBarView = "assets" | "hierarchy" | "scenes" | "code" | "database" | "versions" | "settings"
@@ -12,19 +13,23 @@ interface LocalUserState {
interface LocalState {
uiSoundsCanPlay: boolean
controlBarCurrentView: ControlBarView,
- user?: LocalUserState
+ user?: LocalUserState,
+
+ // Viewport et al.
+ currentScene: string
}
// Define the initial state using that type
const initialState: LocalState = {
uiSoundsCanPlay: true,
- controlBarCurrentView: "assets",
+ controlBarCurrentView: "hierarchy",
user: {
// this dummy state will be removed by the logic in auth.tsx, but we need initial data for SSR purposes
email: "",
id: "",
is_anonymous: false
- }
+ },
+ currentScene: ''
}
export const localSlice = createSlice({
@@ -45,19 +50,26 @@ export const localSlice = createSlice({
state.user = action.payload
},
clearLocalUserState: (state) => {
- console.log('auth clearLocalUserState')
state.user = undefined
+ },
+
+ /**
+ * Viewport et al.
+ */
+ setCurrentScene: (state, action: PayloadAction) => {
+ state.currentScene = action.payload
}
},
})
-export const { turnOffUiSounds, turnOnUiSounds, setControlBarCurrentView, updateLocalUserState, clearLocalUserState } = localSlice.actions
+export const { turnOffUiSounds, turnOnUiSounds, setControlBarCurrentView, updateLocalUserState, clearLocalUserState, setCurrentScene } = localSlice.actions
// Other code such as selectors can use the imported `RootState` type
export const selectUiSoundsCanPlay = (state: RootState) => state.local.uiSoundsCanPlay
export const selectControlBarCurrentView = (state: RootState) => state.local.controlBarCurrentView
export const selectLocalUserState = (state: RootState) => state.local.user
+export const getCurrentScene = (state: RootState) => state.local.currentScene
export default localSlice.reducer
diff --git a/mirror-2/state/scenes.tsx b/mirror-2/state/scenes.tsx
new file mode 100644
index 00000000..140d5f3e
--- /dev/null
+++ b/mirror-2/state/scenes.tsx
@@ -0,0 +1,146 @@
+import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit';
+import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
+import { createSupabaseBrowserClient } from '@/utils/supabase/client';
+import { Database } from '@/utils/database.types';
+
+export const TAG_NAME_FOR_GENERAL_ENTITY = 'Scenes'
+
+// Supabase API for spaces
+export const scenesApi = createApi({
+ reducerPath: 'scenesApi',
+ baseQuery: fakeBaseQuery(),
+ tagTypes: [TAG_NAME_FOR_GENERAL_ENTITY, 'LIST'],
+ endpoints: (builder) => ({
+
+ /**
+ * Scenes
+ */
+ createScene: builder.mutation({
+ queryFn: async ({ name, space_id }) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
+
+ if (!user) {
+ return { error: 'User not found' };
+ }
+
+ if (!space_id) {
+ return { error: 'No spaceId provided' };
+ }
+
+ const { data, error } = await supabase
+ .from("scenes")
+ .insert([{
+ name,
+ space_id,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString()
+ }])
+ .select('*')
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ /**
+ * Get a single Scene by its ID
+ */
+ getSingleScene: builder.query({
+ queryFn: async (sceneId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("scenes")
+ .select("*")
+ .eq("id", sceneId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result, error, sceneId) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: sceneId }], // Provide the scene tag based on sceneId
+ }),
+
+ /**
+ * Get all Scenes for a given space
+ */
+ getAllScenes: builder.query({
+ queryFn: async (spaceId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("scenes")
+ .select("*")
+ .eq("space_id", spaceId);
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result) =>
+ result
+ ? [
+ ...result.map(({ id }) => ({ type: TAG_NAME_FOR_GENERAL_ENTITY, id })),
+ { type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' },
+ ]
+ : [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ /**
+ * Update a Scene by its ID
+ */
+ updateScene: builder.mutation }>({
+ queryFn: async ({ id: sceneId, updateData }) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("scenes")
+ .update(updateData)
+ .eq("id", sceneId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, { id: sceneId }) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: sceneId }], // Invalidate tag for sceneId
+ }),
+
+ /**
+ * Delete a Scene by its ID
+ */
+ deleteScene: builder.mutation({
+ queryFn: async (sceneId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("scenes")
+ .delete()
+ .eq("id", sceneId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ invalidatesTags: (result, error, sceneId) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: sceneId }]
+ }),
+
+
+ }),
+});
+
+// Export the API hooks
+export const {
+ useCreateSceneMutation, useGetAllScenesQuery, useLazyGetAllScenesQuery, useUpdateSceneMutation, useGetSingleSceneQuery, useLazyGetSingleSceneQuery, useDeleteSceneMutation,
+} = scenesApi;
diff --git a/mirror-2/state/shared-cache-tags.tsx b/mirror-2/state/shared-cache-tags.tsx
new file mode 100644
index 00000000..e65a769c
--- /dev/null
+++ b/mirror-2/state/shared-cache-tags.tsx
@@ -0,0 +1 @@
+export const TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY = 'BUILD_MODE_SPACE_QUERY'
diff --git a/mirror-2/state/spaces.tsx b/mirror-2/state/spaces.tsx
new file mode 100644
index 00000000..5f0f0c4a
--- /dev/null
+++ b/mirror-2/state/spaces.tsx
@@ -0,0 +1,125 @@
+import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
+import { createSupabaseBrowserClient } from '@/utils/supabase/client';
+import { Database } from '@/utils/database.types';
+import { generateSpaceName } from '@/actions/name-generator';
+import { scenesApi, TAG_NAME_FOR_GENERAL_ENTITY as SCENES_TAG_NAME_FOR_GENERAL_ENTITY } from '@/state/scenes';
+import { TAG_NAME_FOR_GENERAL_ENTITY as ENTITIES_TAG_NAME_FOR_GENERAL_ENTITY, entitiesApi } from '@/state/entities';
+import { TAG_NAME_FOR_GENERAL_ENTITY as COMPONENTS_TAG_NAME_FOR_GENERAL_ENTITY } from '@/state/components';
+import { TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY } from '@/state/shared-cache-tags';
+
+export const TAG_NAME_FOR_GENERAL_ENTITY = 'Spaces'
+
+// Supabase API for spaces
+export const spacesApi = createApi({
+ reducerPath: 'spacesApi',
+ baseQuery: fakeBaseQuery(),
+ tagTypes: [TAG_NAME_FOR_GENERAL_ENTITY, SCENES_TAG_NAME_FOR_GENERAL_ENTITY, ENTITIES_TAG_NAME_FOR_GENERAL_ENTITY, TAG_NAME_FOR_BUILD_MODE_SPACE_QUERY, 'LIST'],
+ endpoints: (builder) => ({
+ createSpace: builder.mutation({
+ queryFn: async (_, { dispatch }) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data: { user } } = await supabase.auth.getUser()
+ if (!user) {
+ throw new Error('User not found')
+ }
+ const name = await generateSpaceName()
+ const { data, error } = await supabase
+ .from("spaces")
+ .insert([{
+ name,
+ creator_user_id: user?.id,
+ owner_user_id: user.id
+ }])
+ .select('*')
+ .single()
+
+ if (error) {
+ return { error: error.message };
+ }
+
+ // Now that the space is created, dispatch the `createScene` mutation
+ const { data: createSceneData, error: createSceneError } = await dispatch(
+ scenesApi.endpoints.createScene.initiate({ name: "Main", space_id: data.id })
+ )
+
+ if (createSceneError) {
+ return { error: createSceneError };
+ }
+ // // create root entity
+ const { data: createEntityData, error: createEntityError } = await dispatch(
+ entitiesApi.endpoints.upsertEntity.initiate({ name: "Root", scene_id: createSceneData.id, isRootEntity: true })
+ )
+
+ if (createEntityError) {
+ return { error: createEntityError };
+ }
+ return { data };
+ },
+ invalidatesTags: [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: 'LIST' }],
+ }),
+
+ getSingleSpace: builder.query({
+ queryFn: async (spaceId) => {
+ const supabase = createSupabaseBrowserClient();
+
+ const { data, error } = await supabase
+ .from("spaces")
+ .select("*")
+ .eq("id", spaceId)
+ .single()
+
+ if (error) {
+ return { error: error.message };
+ }
+ return { data };
+ },
+ providesTags: (result, error, id) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id }],
+ }),
+
+ updateSpace: builder.mutation }>({
+ queryFn: async ({ id: spaceId, updateData }) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data, error } = await supabase
+ .from("spaces")
+ .update(updateData)
+ .eq("id", spaceId)
+ .select("*")
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+
+ return { data };
+ },
+ invalidatesTags: (result, error, { id: spaceId }) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: spaceId }],
+ }),
+
+ deleteSpace: builder.mutation({
+ queryFn: async (spaceId) => {
+ const supabase = createSupabaseBrowserClient();
+ const { data, error } = await supabase
+ .from("spaces")
+ .delete()
+ .eq("id", spaceId)
+ .single();
+
+ if (error) {
+ return { error: error.message };
+ }
+
+ return { data };
+ },
+ invalidatesTags: (result, error, spaceId) => [{ type: TAG_NAME_FOR_GENERAL_ENTITY, id: spaceId }],
+ }),
+ }),
+});
+
+// Export the API hooks
+export const {
+ useGetSingleSpaceQuery,
+ useCreateSpaceMutation,
+ useUpdateSpaceMutation,
+ useDeleteSpaceMutation,
+} = spacesApi;
+
diff --git a/mirror-2/state/store.tsx b/mirror-2/state/store.tsx
index a0b1749f..3b6b1111 100644
--- a/mirror-2/state/store.tsx
+++ b/mirror-2/state/store.tsx
@@ -2,18 +2,31 @@
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import { localSlice } from '@/state/local'
-import { supabaseApi } from '@/state/supabase'
+import { spacesApi } from '@/state/spaces'
+import { scenesApi } from '@/state/scenes'
+import { entitiesApi } from '@/state/entities'
+import { assetsApi } from '@/state/assets'
+import { componentsApi } from '@/state/components'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[localSlice.reducerPath]: localSlice.reducer,
- [supabaseApi.reducerPath]: supabaseApi.reducer,
+ [assetsApi.reducerPath]: assetsApi.reducer,
+ [spacesApi.reducerPath]: spacesApi.reducer,
+ [scenesApi.reducerPath]: scenesApi.reducer,
+ [entitiesApi.reducerPath]: entitiesApi.reducer,
+ [componentsApi.reducerPath]: componentsApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware().concat(supabaseApi.middleware),
+ getDefaultMiddleware()
+ .concat(assetsApi.middleware)
+ .concat(spacesApi.middleware)
+ .concat(scenesApi.middleware)
+ .concat(entitiesApi.middleware)
+ .concat(componentsApi.middleware)
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
diff --git a/mirror-2/state/supabase.tsx b/mirror-2/state/supabase.tsx
deleted file mode 100644
index 84024f38..00000000
--- a/mirror-2/state/supabase.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-"use client"; // we want to use client-side only because supabase tracks auth clientside
-import { generateSpaceName } from "@/actions/name-generator";
-import { createSupabaseBrowserClient } from "@/utils/supabase/client";
-import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
-
-export const supabaseApi = createApi({
- reducerPath: 'supabaseApi',
- baseQuery: fakeBaseQuery(),
- endpoints: (builder) => ({
-
- /**
- * Spaces
- */
- createSpace: builder.mutation({
- queryFn: async () => {
- const supabase = createSupabaseBrowserClient();
- const { data: { user } } = await supabase.auth.getUser()
- if (!user) {
- throw new Error('User not found')
- }
- const { data, error } = await supabase
- .from("spaces")
- .insert([{
- name: await generateSpaceName(),
- creator_user_id: user?.id,
- owner_user_id: user.id
- }])
- .select('*')
- .single()
-
- if (error) {
- return { error: error.message };
- }
- return { data };
- }
- }),
-
- getSingleSpace: builder.query({
- queryFn: async (spaceId) => {
- const supabase = createSupabaseBrowserClient();
-
- const { data, error } = await supabase
- .from("spaces")
- .select("*")
- .eq("id", spaceId)
- .single()
-
- if (error) {
- return { error: error.message };
- }
- return { data };
- }
- }),
-
- updateSpace: builder.mutation }>({
- queryFn: async ({ spaceId, updateData }) => {
- const supabase = createSupabaseBrowserClient();
-
- const { data, error } = await supabase
- .from("spaces")
- .update(updateData)
- .eq("id", spaceId)
- .single()
-
- if (error) {
- return { error: error.message };
- }
- return { data };
- }
- }),
-
- /**
- * 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 {
- /**
- * Spaces
- */
- useGetSingleSpaceQuery, useUpdateSpaceMutation, useCreateSpaceMutation,
-
-
- /**
- * Assets
- */
- useSearchAssetsQuery, useLazySearchAssetsQuery, useGetSingleAssetQuery, useUpdateAssetMutation
-} = supabaseApi
-
diff --git a/mirror-2/supabase/migrations/20240929043655_spaces_assets.sql b/mirror-2/supabase/migrations/20240929043655_spaces.sql
similarity index 59%
rename from mirror-2/supabase/migrations/20240929043655_spaces_assets.sql
rename to mirror-2/supabase/migrations/20240929043655_spaces.sql
index da2ceab6..333fd256 100644
--- a/mirror-2/supabase/migrations/20240929043655_spaces_assets.sql
+++ b/mirror-2/supabase/migrations/20240929043655_spaces.sql
@@ -1,6 +1,8 @@
create table spaces (
id uuid not null primary key default uuid_generate_v4(),
name text not null,
+ description text,
+ public_page_image_urls text[] default '{}',
owner_user_id uuid references auth.users(id) not null, -- owner is different from creator. Spaces can be transferred and we want to retain the creator
creator_user_id uuid references auth.users(id) not null,
created_at timestamp with time zone not null default now(),
@@ -12,11 +14,6 @@ create table spaces (
alter table spaces
enable row level security;
--- Policy for space owners
-create policy "Owners can view their own spaces"
-on spaces
-for select
-using (owner_user_id = auth.uid());
-- Policy for creating spaces
create policy "Users can create their own spaces"
@@ -46,24 +43,3 @@ create policy "Users can delete their own spaces"
on spaces
for delete
using (owner_user_id = auth.uid());
-
--- assets
-create table assets (
- id uuid not null primary key default uuid_generate_v4(),
- owner_user_id uuid references auth.users(id) not null, -- owner is different from creator. Assets can be transferred and we want to retain the creator
- creator_user_id uuid references auth.users(id) not null,
- name text not null,
- asset_url text not null,
- created_at timestamp with time zone not null default now(),
- updated_at timestamp with time zone not null default now()
- );
-
--- add RLS
-alter table assets
- enable row level security;
-
--- set up storage for assets
-insert into
- storage.buckets (id, name, public)
-values
- ('assets', 'assets', true);
diff --git a/mirror-2/supabase/migrations/20240929043656_assets.sql b/mirror-2/supabase/migrations/20240929043656_assets.sql
new file mode 100644
index 00000000..83dbe447
--- /dev/null
+++ b/mirror-2/supabase/migrations/20240929043656_assets.sql
@@ -0,0 +1,88 @@
+-- assets
+create table assets (
+ id uuid not null primary key default uuid_generate_v4(),
+ owner_user_id uuid references auth.users(id) not null, -- owner is different from creator. Assets can be transferred and we want to retain the creator
+ creator_user_id uuid references auth.users(id) not null,
+ name text not null,
+ description text,
+ file_url text not null,
+ thumbnail_url text not null,
+ created_at timestamp with time zone not null default now(),
+ updated_at timestamp with time zone not null default now()
+ );
+
+-- add RLS
+alter table assets
+ enable row level security;
+
+ -- Policy for creating assets
+create policy "Users can create their own assets"
+on assets
+for insert
+with check (
+ owner_user_id = auth.uid()
+ and creator_user_id = auth.uid()
+);
+
+-- Policy for selecting assets
+create policy "Users can view their own assets"
+on assets
+for select
+using (
+ owner_user_id = auth.uid()
+);
+
+-- Policy for updating assets
+create policy "Users can update their own assets"
+on assets
+for update
+using (owner_user_id = auth.uid());
+
+-- Policy for deleting assets
+create policy "Users can delete their own assets"
+on assets
+for delete
+using (owner_user_id = auth.uid());
+
+
+-- set up storage for assets
+insert into
+ storage.buckets (id, name, public)
+values
+ ('assets', 'assets', true);
+
+
+create policy "User can insert their own objects"
+on storage.objects
+for insert
+to authenticated
+with check (
+ bucket_id = 'assets' and
+ owner_id = (select auth.uid()::text) -- Ensure the owner is the current authenticated user
+);
+
+create policy "Anyone can read assets"
+on storage.objects
+for select
+to authenticated
+using (
+ bucket_id = 'assets'
+);
+
+create policy "User can update their own objects"
+on storage.objects
+for update
+to authenticated
+using (
+ bucket_id = 'assets' and
+ owner_id = (select auth.uid()::text) -- Ensure the user owns the object they want to update
+);
+
+create policy "User can delete their own objects"
+on storage.objects
+for delete
+to authenticated
+using (
+ bucket_id = 'assets' and
+ owner_id = (select auth.uid()::text) -- Ensure the user owns the object they want to delete
+);
diff --git a/mirror-2/supabase/migrations/20241001231149_entities.sql b/mirror-2/supabase/migrations/20241001231149_entities.sql
index 68f974eb..b5a4d7fc 100644
--- a/mirror-2/supabase/migrations/20241001231149_entities.sql
+++ b/mirror-2/supabase/migrations/20241001231149_entities.sql
@@ -1,11 +1,51 @@
-create table entities (
- id uuid not null primary key default uuid_generate_v4(),
- name text not null,
- scene_id uuid references scenes on delete cascade not null, -- delete the entity if scene is deleted
- created_at timestamp with time zone not null default now(),
- updated_at timestamp with time zone not null default now(),
- constraint name_length check (char_length(name) >= 0)
- );
+CREATE TABLE entities (
+ id uuid NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
+ name text NOT NULL,
+ enabled boolean NOT NULL DEFAULT true,
+ parent_id uuid REFERENCES entities(id) ON DELETE CASCADE,
+ order_under_parent int,
+ scene_id uuid REFERENCES scenes ON DELETE CASCADE NOT NULL, -- delete entity if scene is deleted
+ position float8[] NOT NULL DEFAULT ARRAY[0, 0, 0], -- storing position as an array of 3 floats
+ scale float8[] NOT NULL DEFAULT ARRAY[1, 1, 1], -- storing scale as an array of 3 floats
+ rotation float8[] NOT NULL DEFAULT ARRAY[0, 0, 0], -- storing rotation as an array of 3 floats
+ tags text[] DEFAULT ARRAY[]::text[], -- storing tags as an empty array of text
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ updated_at timestamp with time zone NOT NULL DEFAULT now(),
+ CONSTRAINT name_length CHECK (char_length(name) >= 0),
+ CONSTRAINT order_not_null_if_parent_id_exists CHECK (
+ (parent_id IS NOT NULL AND order_under_parent IS NOT NULL) OR parent_id IS NULL
+ ),
+ CONSTRAINT parent_id_not_self CHECK (parent_id IS DISTINCT FROM id) -- Ensures parent_id is not the same as id
+);
+
+CREATE OR REPLACE FUNCTION check_circular_reference()
+RETURNS TRIGGER AS $$
+DECLARE
+ current_parent uuid;
+BEGIN
+ -- Set the current parent to the newly inserted/updated parent_id
+ current_parent := NEW.parent_id;
+
+ -- Traverse up the hierarchy
+ WHILE current_parent IS NOT NULL LOOP
+ -- If at any point, the current parent is the same as the entity's id, we have a cycle
+ IF current_parent = NEW.id THEN
+ RAISE EXCEPTION 'Circular reference detected: Entity % is an ancestor of itself.', NEW.id;
+ END IF;
+
+ -- Move to the next parent in the hierarchy
+ SELECT parent_id INTO current_parent FROM entities WHERE id = current_parent;
+ END LOOP;
+
+ -- If no circular reference is found, return and allow the insert/update
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER prevent_circular_reference
+BEFORE INSERT OR UPDATE ON entities
+FOR EACH ROW
+EXECUTE FUNCTION check_circular_reference();
-- add RLS
alter table entities
@@ -62,3 +102,53 @@ using (
and spaces.owner_user_id = auth.uid()
)
);
+
+CREATE POLICY "Disallow Root entity deletion"
+ON entities
+FOR DELETE
+USING (
+ parent_id = null
+);
+
+
+CREATE OR REPLACE FUNCTION increment_and_resequence_order_under_parent(p_scene_id uuid, p_entity_id uuid)
+RETURNS void AS $$
+DECLARE
+ target_order int;
+ target_parent_id uuid;
+BEGIN
+ -- 1. Find the target entity by entity_id
+ SELECT order_under_parent, parent_id
+ INTO target_order, target_parent_id
+ FROM entities
+ WHERE id = p_entity_id;
+
+ -- Check if the entity exists
+ IF target_order IS NULL THEN
+ RAISE EXCEPTION 'Entity with id % does not exist.', p_entity_id;
+ END IF;
+
+ -- 2. Increment order_under_parent for entities with greater order
+ UPDATE entities
+ SET order_under_parent = order_under_parent + 1
+ WHERE scene_id = p_scene_id
+ AND parent_id IS NOT DISTINCT FROM target_parent_id
+ AND order_under_parent > target_order;
+
+ -- 3. Resequence order_under_parent to eliminate gaps
+ WITH ordered_entities AS (
+ SELECT id,
+ ROW_NUMBER() OVER (ORDER BY order_under_parent) AS new_order
+ FROM entities
+ WHERE scene_id = p_scene_id
+ AND parent_id IS NOT DISTINCT FROM target_parent_id
+ )
+ UPDATE entities e
+ SET order_under_parent = oe.new_order
+ FROM ordered_entities oe
+ WHERE e.id = oe.id;
+
+ -- Optionally, update the updated_at timestamp
+ -- UPDATE entities SET updated_at = NOW() WHERE id = ANY(SELECT id FROM ordered_entities);
+END;
+$$ LANGUAGE plpgsql;
diff --git a/mirror-2/supabase/migrations/20241001234418_components.sql b/mirror-2/supabase/migrations/20241001234418_components.sql
index c8bd6acb..ed382420 100644
--- a/mirror-2/supabase/migrations/20241001234418_components.sql
+++ b/mirror-2/supabase/migrations/20241001234418_components.sql
@@ -1,11 +1,33 @@
-create table components (
- id uuid not null primary key default uuid_generate_v4(),
- name text not null,
- entity_id uuid references entities on delete cascade not null, -- delete the component if entity is deleted
- created_at timestamp with time zone not null default now(),
- updated_at timestamp with time zone not null default now(),
- constraint name_length check (char_length(name) >= 0)
- );
+CREATE TYPE component_key AS ENUM (
+ 'script',
+ 'render',
+ 'collision',
+ 'rigidbody',
+ 'camera',
+ 'light',
+ 'anim',
+ 'sprite',
+ 'screen',
+ 'element',
+ 'button',
+ 'particlesystem',
+ 'gsplat',
+ 'audiolistener',
+ 'sound',
+ 'scrollbar',
+ 'scrollview',
+ 'layoutgroup',
+ 'layoutchild'
+);
+
+CREATE TABLE components (
+ id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
+ entity_id UUID REFERENCES entities(id) ON DELETE CASCADE NOT NULL, -- delete the component if entity is deleted
+ component_key component_key NOT NULL,
+ attributes JSONB DEFAULT '{}'::jsonb, -- New column with default empty JSON object
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
+);
-- add RLS
alter table components
diff --git a/mirror-2/supabase/migrations/20241005025417_searches_assets_spaces.sql b/mirror-2/supabase/migrations/20241005025417_searches_assets_spaces.sql
new file mode 100644
index 00000000..6de4c564
--- /dev/null
+++ b/mirror-2/supabase/migrations/20241005025417_searches_assets_spaces.sql
@@ -0,0 +1,15 @@
+create or replace function search_assets_by_name_prefix(prefix text)
+returns setof assets AS $$
+begin
+ return query
+ select * from assets where to_tsvector('english', name) @@ to_tsquery(prefix || ':*');
+end;
+$$ language plpgsql;
+
+create or replace function search_spaces_by_name_prefix(prefix text)
+returns setof spaces AS $$
+begin
+ return query
+ select * from spaces where to_tsvector('english', name) @@ to_tsquery(prefix || ':*');
+end;
+$$ language plpgsql;
diff --git a/mirror-2/supabase/migrations/20241007195537_get_space_all.sql b/mirror-2/supabase/migrations/20241007195537_get_space_all.sql
new file mode 100644
index 00000000..34dcd2e7
--- /dev/null
+++ b/mirror-2/supabase/migrations/20241007195537_get_space_all.sql
@@ -0,0 +1,52 @@
+CREATE OR REPLACE FUNCTION get_space_with_children(space_id UUID)
+RETURNS JSONB
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN (
+ SELECT jsonb_build_object(
+ 'id', s.id,
+ 'name', s.name,
+ 'scenes', (
+ SELECT COALESCE(jsonb_agg(
+ jsonb_build_object(
+ 'id', sc.id,
+ 'name', sc.name,
+ 'entities', (
+ SELECT COALESCE(jsonb_agg(
+ jsonb_build_object(
+ 'id', e.id,
+ 'name', e.name,
+ 'parent_id', e.parent_id,
+ 'children', (
+ SELECT COALESCE(jsonb_agg(child.id), '[]'::jsonb)
+ FROM entities AS child
+ WHERE child.parent_id = e.id
+ ),
+ 'components', (
+ SELECT COALESCE(jsonb_agg(
+ jsonb_build_object(
+ 'id', c.id,
+ 'component_key', c.component_key,
+ 'attributes', c.attributes
+ )
+ ), '[]'::jsonb)
+ FROM components c
+ WHERE c.entity_id = e.id
+ )
+ )
+ ), '[]'::jsonb)
+ FROM entities e
+ WHERE e.scene_id = sc.id
+ )
+ )
+ ), '[]'::jsonb)
+ FROM scenes sc
+ WHERE sc.space_id = s.id
+ )
+ )
+ FROM spaces s
+ WHERE s.id = space_id
+ );
+END;
+$$;
diff --git a/mirror-2/supabase/seed.sql b/mirror-2/supabase/seed.sql
index 6bfc0c20..ab855592 100644
--- a/mirror-2/supabase/seed.sql
+++ b/mirror-2/supabase/seed.sql
@@ -34,12 +34,14 @@ DECLARE
user_ids uuid[] := ARRAY[]::uuid[]; -- Array to store user IDs
user_id uuid;
profile_url TEXT;
- asset_url TEXT;
+ file_url TEXT;
+ thumbnail_url TEXT;
space_id uuid;
scene_id uuid;
entity_name TEXT;
entity_id uuid;
component_name TEXT;
+ public_page_image_urls text[];
BEGIN
-- Loop to insert 15 users
FOR i IN 1..15 LOOP
@@ -59,25 +61,33 @@ BEGIN
VALUES
(user_id, format('User %s', i), format('This is the bio of user %s.', i), format('https://picsum.photos/seed/picsum/300/300', i));
- -- Insert 30 assets for each user, now including creator_user_id and owner_user_id
+ -- Insert 30 assets for each user, now including creator_user_id, owner_user_id, and thumbnail_url
FOR j IN 1..30 LOOP
- asset_url := format('https://picsum.photos/seed/picsum/800/600', i, j); -- Use %s for numbers
+ file_url := format('https://picsum.photos/seed/picsum/800/600', i, j); -- Use %s for numbers
+ thumbnail_url := format('https://picsum.photos/seed/picsum/200/150', i, j); -- Use %s for thumbnails
INSERT INTO public.assets
- (id, name, asset_url, creator_user_id, owner_user_id, created_at, updated_at)
+ (id, name, description, file_url, thumbnail_url, creator_user_id, owner_user_id, created_at, updated_at)
VALUES
- (gen_random_uuid(), format('Asset %s', j), asset_url, user_id, user_id, now(), now()); -- Initially, owner_user_id is the same as creator_user_id
+ (gen_random_uuid(), format('Asset %s', j), 'This is a placeholder description for the asset.', file_url, thumbnail_url, user_id, user_id, now(), now()); -- Added thumbnail_url and description
END LOOP;
END LOOP;
-- Insert spaces, using user_ids from the array and setting both owner_user_id and creator_user_id
FOR i IN 1..45 LOOP
+ -- Create an array of image URLs for the space's public_page_image_urls
+ public_page_image_urls := ARRAY[
+ format('https://picsum.photos/seed/picsum/%s/800', i),
+ format('https://picsum.photos/seed/picsum/%s/801', i),
+ format('https://picsum.photos/seed/picsum/%s/802', i)
+ ];
+
-- Create a new space
INSERT INTO public.spaces
- (id, name, creator_user_id, owner_user_id, created_at, updated_at)
+ (id, name, description, public_page_image_urls, creator_user_id, owner_user_id, created_at, updated_at)
VALUES
- (gen_random_uuid(), format('Space %s', i), user_ids[((i - 1) % 15) + 1], user_ids[((i - 1) % 15) + 1], now(), now())
+ (gen_random_uuid(), format('Space %s', i), 'This is a placeholder description for the space.', public_page_image_urls, user_ids[((i - 1) % 15) + 1], user_ids[((i - 1) % 15) + 1], now(), now()) -- Added public_page_image_urls and description
RETURNING id INTO space_id; -- Capture the newly created space ID
-- Insert 3 space_versions for each space
@@ -108,14 +118,13 @@ BEGIN
(gen_random_uuid(), entity_name, scene_id, now(), now())
RETURNING id INTO entity_id; -- Capture the newly created entity ID
- -- Insert 3 components for each entity
+ -- Insert 3 components for each entity, including component_key and attributes
FOR c IN 1..3 LOOP
- component_name := format('Component %s-%s-%s-%s', i, j, k, c); -- Create unique component names
INSERT INTO public.components
- (id, name, entity_id, created_at, updated_at)
+ (id, entity_id, component_key, attributes, created_at, updated_at)
VALUES
- (gen_random_uuid(), component_name, entity_id, now(), now());
+ (gen_random_uuid(), entity_id, 'script', '{"attribute": "value"}'::jsonb, now(), now()); -- Add component_key and attributes
END LOOP;
END LOOP;
diff --git a/mirror-2/utils/database.types.ts b/mirror-2/utils/database.types.ts
index bdb4f97e..9733701d 100644
--- a/mirror-2/utils/database.types.ts
+++ b/mirror-2/utils/database.types.ts
@@ -1,429 +1,3 @@
-export type Json =
- | string
- | number
- | boolean
- | null
- | { [key: string]: Json | undefined }
- | Json[]
-
-export type Database = {
- graphql_public: {
- Tables: {
- [_ in never]: never
- }
- Views: {
- [_ in never]: never
- }
- Functions: {
- graphql: {
- Args: {
- operationName?: string
- query?: string
- variables?: Json
- extensions?: Json
- }
- Returns: Json
- }
- }
- Enums: {
- [_ in never]: never
- }
- CompositeTypes: {
- [_ in never]: never
- }
- }
- public: {
- Tables: {
- assets: {
- Row: {
- asset_url: string
- created_at: string
- creator_user_id: string
- id: string
- name: string
- owner_user_id: string
- updated_at: string
- }
- Insert: {
- asset_url: string
- created_at?: string
- creator_user_id: string
- id?: string
- name: string
- owner_user_id: string
- updated_at?: string
- }
- Update: {
- asset_url?: string
- created_at?: string
- creator_user_id?: string
- id?: string
- name?: string
- owner_user_id?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "assets_creator_user_id_fkey"
- columns: ["creator_user_id"]
- isOneToOne: false
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- {
- foreignKeyName: "assets_owner_user_id_fkey"
- columns: ["owner_user_id"]
- isOneToOne: false
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- ]
- }
- components: {
- Row: {
- created_at: string
- entity_id: string
- id: string
- name: string
- updated_at: string
- }
- Insert: {
- created_at?: string
- entity_id: string
- id?: string
- name: string
- updated_at?: string
- }
- Update: {
- created_at?: string
- entity_id?: string
- id?: string
- name?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "components_entity_id_fkey"
- columns: ["entity_id"]
- isOneToOne: false
- referencedRelation: "entities"
- referencedColumns: ["id"]
- },
- ]
- }
- entities: {
- Row: {
- created_at: string
- id: string
- name: string
- scene_id: string
- updated_at: string
- }
- Insert: {
- created_at?: string
- id?: string
- name: string
- scene_id: string
- updated_at?: string
- }
- Update: {
- created_at?: string
- id?: string
- name?: string
- scene_id?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "entities_scene_id_fkey"
- columns: ["scene_id"]
- isOneToOne: false
- referencedRelation: "scenes"
- referencedColumns: ["id"]
- },
- ]
- }
- scenes: {
- Row: {
- created_at: string
- id: string
- name: string
- space_id: string
- updated_at: string
- }
- Insert: {
- created_at?: string
- id?: string
- name: string
- space_id: string
- updated_at?: string
- }
- Update: {
- created_at?: string
- id?: string
- name?: string
- space_id?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "scenes_space_id_fkey"
- columns: ["space_id"]
- isOneToOne: false
- referencedRelation: "spaces"
- referencedColumns: ["id"]
- },
- ]
- }
- space_user_collaborators: {
- Row: {
- created_at: string
- id: string
- space_id: string
- user_id: string
- }
- Insert: {
- created_at?: string
- id?: string
- space_id: string
- user_id: string
- }
- Update: {
- created_at?: string
- id?: string
- space_id?: string
- user_id?: string
- }
- Relationships: [
- {
- foreignKeyName: "space_user_collaborators_space_id_fkey"
- columns: ["space_id"]
- isOneToOne: false
- referencedRelation: "spaces"
- referencedColumns: ["id"]
- },
- {
- foreignKeyName: "space_user_collaborators_user_id_fkey"
- columns: ["user_id"]
- isOneToOne: false
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- ]
- }
- space_versions: {
- Row: {
- created_at: string
- id: string
- name: string
- space_id: string
- updated_at: string
- }
- Insert: {
- created_at?: string
- id?: string
- name: string
- space_id: string
- updated_at?: string
- }
- Update: {
- created_at?: string
- id?: string
- name?: string
- space_id?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "space_versions_space_id_fkey"
- columns: ["space_id"]
- isOneToOne: false
- referencedRelation: "spaces"
- referencedColumns: ["id"]
- },
- ]
- }
- spaces: {
- Row: {
- created_at: string
- creator_user_id: string
- id: string
- name: string
- owner_user_id: string
- updated_at: string
- }
- Insert: {
- created_at?: string
- creator_user_id: string
- id?: string
- name: string
- owner_user_id: string
- updated_at?: string
- }
- Update: {
- created_at?: string
- creator_user_id?: string
- id?: string
- name?: string
- owner_user_id?: string
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "spaces_creator_user_id_fkey"
- columns: ["creator_user_id"]
- isOneToOne: false
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- {
- foreignKeyName: "spaces_owner_user_id_fkey"
- columns: ["owner_user_id"]
- isOneToOne: false
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- ]
- }
- user_profiles: {
- Row: {
- avatar_type: Database["public"]["Enums"]["avatar_type"] | null
- created_at: string
- display_name: string
- id: string
- public_bio: string | null
- ready_player_me_url_glb: string | null
- updated_at: string
- }
- Insert: {
- avatar_type?: Database["public"]["Enums"]["avatar_type"] | null
- created_at?: string
- display_name: string
- id?: string
- public_bio?: string | null
- ready_player_me_url_glb?: string | null
- updated_at?: string
- }
- Update: {
- avatar_type?: Database["public"]["Enums"]["avatar_type"] | null
- created_at?: string
- display_name?: string
- id?: string
- public_bio?: string | null
- ready_player_me_url_glb?: string | null
- updated_at?: string
- }
- Relationships: [
- {
- foreignKeyName: "user_profiles_id_fkey"
- columns: ["id"]
- isOneToOne: true
- referencedRelation: "users"
- referencedColumns: ["id"]
- },
- ]
- }
- }
- Views: {
- [_ in never]: never
- }
- Functions: {
- create_user: {
- Args: {
- email: string
- password: string
- }
- Returns: string
- }
- }
- Enums: {
- avatar_type: "ready_player_me"
- }
- CompositeTypes: {
- [_ in never]: never
- }
- }
-}
-
-type PublicSchema = Database[Extract]
-
-export type Tables<
- PublicTableNameOrOptions extends
- | keyof (PublicSchema["Tables"] & PublicSchema["Views"])
- | { schema: keyof Database },
- TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
- ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
- Database[PublicTableNameOrOptions["schema"]]["Views"])
- : never = never,
-> = PublicTableNameOrOptions extends { schema: keyof Database }
- ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
- Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
- Row: infer R
- }
- ? R
- : never
- : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
- PublicSchema["Views"])
- ? (PublicSchema["Tables"] &
- PublicSchema["Views"])[PublicTableNameOrOptions] extends {
- Row: infer R
- }
- ? R
- : never
- : never
-
-export type TablesInsert<
- PublicTableNameOrOptions extends
- | keyof PublicSchema["Tables"]
- | { schema: keyof Database },
- TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
- ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
- : never = never,
-> = PublicTableNameOrOptions extends { schema: keyof Database }
- ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
- Insert: infer I
- }
- ? I
- : never
- : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
- ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
- Insert: infer I
- }
- ? I
- : never
- : never
-
-export type TablesUpdate<
- PublicTableNameOrOptions extends
- | keyof PublicSchema["Tables"]
- | { schema: keyof Database },
- TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
- ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
- : never = never,
-> = PublicTableNameOrOptions extends { schema: keyof Database }
- ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
- Update: infer U
- }
- ? U
- : never
- : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
- ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
- Update: infer U
- }
- ? U
- : never
- : never
-
-export type Enums<
- PublicEnumNameOrOptions extends
- | keyof PublicSchema["Enums"]
- | { schema: keyof Database },
- EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
- ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
- : never = never,
-> = PublicEnumNameOrOptions extends { schema: keyof Database }
- ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
- : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
- ? PublicSchema["Enums"][PublicEnumNameOrOptions]
- : never
-
+Need to install the following packages:
+supabase@1.203.0
+Ok to proceed? (y)
\ No newline at end of file
diff --git a/mirror-2/utils/download-file.ts b/mirror-2/utils/download-file.ts
new file mode 100644
index 00000000..f4e99205
--- /dev/null
+++ b/mirror-2/utils/download-file.ts
@@ -0,0 +1,12 @@
+export default function downloadFile(data: Blob, filename: string) {
+ const url = window.URL.createObjectURL(data); // Create a Blob URL
+ const link = document.createElement('a'); // Create an anchor element
+ link.href = url; // Set the Blob URL as the href attribute
+ link.setAttribute('download', filename); // Set the filename for download
+
+ document.body.appendChild(link); // Append the anchor to the DOM
+ link.click(); // Simulate a click event
+
+ document.body.removeChild(link); // Clean up the anchor
+ window.URL.revokeObjectURL(url); // Release the Blob URL
+}
diff --git a/mirror-2/yarn.lock b/mirror-2/yarn.lock
index 4a103548..8b20e695 100644
--- a/mirror-2/yarn.lock
+++ b/mirror-2/yarn.lock
@@ -7,11 +7,96 @@
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
+"@ant-design/colors@^7.0.0", "@ant-design/colors@^7.1.0":
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-7.1.0.tgz#60eadfa2e21871d8948dac5d50b9f056062f8af3"
+ integrity sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==
+ dependencies:
+ "@ctrl/tinycolor" "^3.6.1"
+
+"@ant-design/cssinjs-utils@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.0.tgz#5e85c16c1695e692f550b3e4b156cf70cac0b873"
+ integrity sha512-E9nOWObXx7Dy7hdyuYlOFaer/LtPO7oyZVxZphh0CYEslr5EmhJPM3WI0Q2RBHRtYg6dSNqeSK73kvZjPN3IMQ==
+ dependencies:
+ "@ant-design/cssinjs" "^1.21.0"
+ "@babel/runtime" "^7.23.2"
+ rc-util "^5.38.0"
+
+"@ant-design/cssinjs@^1.21.0", "@ant-design/cssinjs@^1.21.1":
+ version "1.21.1"
+ resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.21.1.tgz#7320813c5f747e0cde52c388eff5198d78d57230"
+ integrity sha512-tyWnlK+XH7Bumd0byfbCiZNK43HEubMoCcu9VxwsAwiHdHTgWa+tMN0/yvxa+e8EzuFP1WdUNNPclRpVtD33lg==
+ dependencies:
+ "@babel/runtime" "^7.11.1"
+ "@emotion/hash" "^0.8.0"
+ "@emotion/unitless" "^0.7.5"
+ classnames "^2.3.1"
+ csstype "^3.1.3"
+ rc-util "^5.35.0"
+ stylis "^4.3.3"
+
+"@ant-design/fast-color@^2.0.6":
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz#ab4d4455c1542c9017d367c2fa8ca3e4215d0ba2"
+ integrity sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==
+ dependencies:
+ "@babel/runtime" "^7.24.7"
+
+"@ant-design/icons-svg@^4.4.0":
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz#ed2be7fb4d82ac7e1d45a54a5b06d6cecf8be6f6"
+ integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
+
+"@ant-design/icons@^5.5.1":
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.5.1.tgz#4ff57b2a0d3bafae3d990c2781fd857ead36c935"
+ integrity sha512-0UrM02MA2iDIgvLatWrj6YTCYe0F/cwXvVE0E2SqGrL7PZireQwgEKTKBisWpZyal5eXZLvuM98kju6YtYne8w==
+ dependencies:
+ "@ant-design/colors" "^7.0.0"
+ "@ant-design/icons-svg" "^4.4.0"
+ "@babel/runtime" "^7.24.8"
+ classnames "^2.2.6"
+ rc-util "^5.31.1"
+
+"@ant-design/react-slick@~1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz#f84ce3e4d0dc941f02b16f1d1d6d7a371ffbb4f1"
+ integrity sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==
+ dependencies:
+ "@babel/runtime" "^7.10.4"
+ classnames "^2.2.5"
+ json2mq "^0.2.0"
+ resize-observer-polyfill "^1.5.1"
+ throttle-debounce "^5.0.0"
+
+"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.24.8", "@babel/runtime@^7.25.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
+ integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@ctrl/tinycolor@^3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
+ integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
+
"@discoveryjs/json-ext@0.5.7":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+"@emotion/hash@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+ integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
+"@emotion/unitless@^0.7.5":
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
+ integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
+
"@floating-ui/core@^1.6.0":
version "1.6.8"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12"
@@ -595,6 +680,88 @@
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
+"@rc-component/async-validator@^5.0.3":
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz#5291ad92f00a14b6766fc81735c234277f83e948"
+ integrity sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==
+ dependencies:
+ "@babel/runtime" "^7.24.4"
+
+"@rc-component/color-picker@~2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz#6b9b96152466a9d4475cbe72b40b594bfda164be"
+ integrity sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==
+ dependencies:
+ "@ant-design/fast-color" "^2.0.6"
+ "@babel/runtime" "^7.23.6"
+ classnames "^2.2.6"
+ rc-util "^5.38.1"
+
+"@rc-component/context@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@rc-component/context/-/context-1.4.0.tgz#dc6fb021d6773546af8f016ae4ce9aea088395e8"
+ integrity sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ rc-util "^5.27.0"
+
+"@rc-component/mini-decimal@^1.0.1":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz#7b7a362b14a0a54cb5bc6fd2b82731f29f11d9b0"
+ integrity sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==
+ dependencies:
+ "@babel/runtime" "^7.18.0"
+
+"@rc-component/mutate-observer@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz#ee53cc88b78aade3cd0653609215a44779386fd8"
+ integrity sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==
+ dependencies:
+ "@babel/runtime" "^7.18.0"
+ classnames "^2.3.2"
+ rc-util "^5.24.4"
+
+"@rc-component/portal@^1.0.0-8", "@rc-component/portal@^1.0.0-9", "@rc-component/portal@^1.0.2", "@rc-component/portal@^1.1.0", "@rc-component/portal@^1.1.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71"
+ integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==
+ dependencies:
+ "@babel/runtime" "^7.18.0"
+ classnames "^2.3.2"
+ rc-util "^5.24.4"
+
+"@rc-component/qrcode@~1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz#48a8de5eb11d0e65926f1377c4b1ef4c888997f5"
+ integrity sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==
+ dependencies:
+ "@babel/runtime" "^7.24.7"
+ classnames "^2.3.2"
+ rc-util "^5.38.0"
+
+"@rc-component/tour@~1.15.1":
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/@rc-component/tour/-/tour-1.15.1.tgz#9b79808254185fc19e964172d99e25e8c6800ded"
+ integrity sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==
+ dependencies:
+ "@babel/runtime" "^7.18.0"
+ "@rc-component/portal" "^1.0.0-9"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "^2.3.2"
+ rc-util "^5.24.4"
+
+"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1", "@rc-component/trigger@^2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.2.3.tgz#b47e945115e2d0a7f7e067dbb9ed76c91c1b4385"
+ integrity sha512-X1oFIpKoXAMXNDYCviOmTfuNuYxE4h5laBsyCqVAVMjNHxoF3/uiyA7XdegK1XbCvBbCZ6P6byWrEoDRpKL8+A==
+ dependencies:
+ "@babel/runtime" "^7.23.2"
+ "@rc-component/portal" "^1.1.0"
+ classnames "^2.3.2"
+ rc-motion "^2.0.0"
+ rc-resize-observer "^1.3.1"
+ rc-util "^5.38.0"
+
"@react-hook/latest@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@react-hook/latest/-/latest-1.0.3.tgz#c2d1d0b0af8b69ec6e2b3a2412ba0768ac82db80"
@@ -725,6 +892,13 @@
dependencies:
"@types/react" "*"
+"@types/react-transition-group@^4.4.1":
+ version "4.4.11"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5"
+ integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*":
version "18.3.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.10.tgz#6edc26dc22ff8c9c226d3c7bf8357b013c842219"
@@ -752,6 +926,11 @@
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
+"@types/webxr@^0.5.16":
+ version "0.5.20"
+ resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.20.tgz#b16b681af314ec011b2e8221b0a072d691c04953"
+ integrity sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==
+
"@types/ws@^8.5.10":
version "8.5.12"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
@@ -759,6 +938,11 @@
dependencies:
"@types/node" "*"
+"@webgpu/types@^0.1.40":
+ version "0.1.48"
+ resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.48.tgz#8ab741852283118bd633345c20e218faa7211e9c"
+ integrity sha512-e3zmDEPih4Rle+JrP5cT8nCCtDizoUpEaN72OuD1clbhXGERtn0wwuMdxOrBymu3kMLWKDd8hd+ERhSheLuLTg==
+
acorn-walk@^8.0.0:
version "8.3.4"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7"
@@ -771,6 +955,21 @@ acorn@^8.0.4, acorn@^8.11.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+ahooks@^3.8.1:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.8.1.tgz#3a19d0e4085618a7a38a22a34b568c8d3fd974c0"
+ integrity sha512-JoP9+/RWO7MnI/uSKdvQ8WB10Y3oo1PjLv+4Sv4Vpm19Z86VUMdXh+RhWvMGxZZs06sq2p0xVtFk8Oh5ZObsoA==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ dayjs "^1.9.1"
+ intersection-observer "^0.12.0"
+ js-cookie "^3.0.5"
+ lodash "^4.17.21"
+ react-fast-compare "^3.2.2"
+ resize-observer-polyfill "^1.5.1"
+ screenfull "^5.0.0"
+ tslib "^2.4.1"
+
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@@ -793,6 +992,61 @@ ansi-styles@^6.1.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+antd@^5.21.2:
+ version "5.21.2"
+ resolved "https://registry.yarnpkg.com/antd/-/antd-5.21.2.tgz#6933c508d885d4be8f38b8aec874d0bd96b66600"
+ integrity sha512-ZcyykH58xRkNp5oxFqn255VxrjLj6qG6czHJQPoDHqRdR9roSfWQGTiqYVb4Omg77WZHNgu17QrWzvL4Hp6/FA==
+ dependencies:
+ "@ant-design/colors" "^7.1.0"
+ "@ant-design/cssinjs" "^1.21.1"
+ "@ant-design/cssinjs-utils" "^1.1.0"
+ "@ant-design/icons" "^5.5.1"
+ "@ant-design/react-slick" "~1.1.2"
+ "@babel/runtime" "^7.25.6"
+ "@ctrl/tinycolor" "^3.6.1"
+ "@rc-component/color-picker" "~2.0.1"
+ "@rc-component/mutate-observer" "^1.1.0"
+ "@rc-component/qrcode" "~1.0.0"
+ "@rc-component/tour" "~1.15.1"
+ "@rc-component/trigger" "^2.2.3"
+ classnames "^2.5.1"
+ copy-to-clipboard "^3.3.3"
+ dayjs "^1.11.11"
+ rc-cascader "~3.28.1"
+ rc-checkbox "~3.3.0"
+ rc-collapse "~3.8.0"
+ rc-dialog "~9.6.0"
+ rc-drawer "~7.2.0"
+ rc-dropdown "~4.2.0"
+ rc-field-form "~2.4.0"
+ rc-image "~7.11.0"
+ rc-input "~1.6.3"
+ rc-input-number "~9.2.0"
+ rc-mentions "~2.16.1"
+ rc-menu "~9.15.1"
+ rc-motion "^2.9.3"
+ rc-notification "~5.6.2"
+ rc-pagination "~4.3.0"
+ rc-picker "~4.6.15"
+ rc-progress "~4.0.0"
+ rc-rate "~2.13.0"
+ rc-resize-observer "^1.4.0"
+ rc-segmented "~2.5.0"
+ rc-select "~14.15.2"
+ rc-slider "~11.1.6"
+ rc-steps "~6.0.1"
+ rc-switch "~4.1.0"
+ rc-table "~7.47.5"
+ rc-tabs "~15.3.0"
+ rc-textarea "~1.8.2"
+ rc-tooltip "~6.2.1"
+ rc-tree "~5.9.0"
+ rc-tree-select "~5.23.0"
+ rc-upload "~4.8.1"
+ rc-util "^5.43.0"
+ scroll-into-view-if-needed "^3.1.0"
+ throttle-debounce "^5.0.2"
+
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -818,6 +1072,16 @@ aria-hidden@^1.1.1:
dependencies:
tslib "^2.0.0"
+array-tree-filter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190"
+ integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==
+
+attr-accept@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
+ integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
+
autoprefixer@10.4.17:
version "10.4.17"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be"
@@ -903,6 +1167,11 @@ class-variance-authority@^0.7.0:
dependencies:
clsx "2.0.0"
+classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
+ integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
+
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
@@ -940,11 +1209,23 @@ commander@^7.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+compute-scroll-into-view@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz#753f11d972596558d8fe7c6bcbc8497690ab4c87"
+ integrity sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==
+
cookie@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+copy-to-clipboard@^3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
+ integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
+ dependencies:
+ toggle-selection "^1.0.6"
+
cross-spawn@^7.0.0:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -959,11 +1240,16 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-csstype@^3.0.2:
+csstype@^3.0.2, csstype@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+dayjs@^1.11.11, dayjs@^1.9.1:
+ version "1.11.13"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
+ integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
+
debounce@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
@@ -984,6 +1270,14 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
duplexer@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@@ -1037,6 +1331,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+file-selector@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc"
+ integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==
+ dependencies:
+ tslib "^2.4.0"
+
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -1137,6 +1438,11 @@ immer@^10.0.3:
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
+intersection-observer@^0.12.0:
+ version "0.12.2"
+ resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375"
+ integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==
+
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -1204,11 +1510,23 @@ jiti@^1.19.1:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
+js-cookie@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
+ integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
+
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+json2mq@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
+ integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==
+ dependencies:
+ string-convert "^0.2.0"
+
lilconfig@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@@ -1224,7 +1542,12 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
-loose-envify@^1.0.0, loose-envify@^1.1.0:
+lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -1328,7 +1651,7 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
-object-assign@^4.0.1:
+object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -1386,6 +1709,14 @@ pirates@^4.0.1:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
+playcanvas@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/playcanvas/-/playcanvas-2.0.0.tgz#ef298a2609c0e1c551709ed4e00d64c13932bd2b"
+ integrity sha512-hEbIgL3ZkB8GyawN3gHpxYl+ZDO+lE3vEFs2TKe8jnT6b9CcF6yzITLorTkRbkd7dyMz1J0+633qH4HeD7EZpQ==
+ dependencies:
+ "@types/webxr" "^0.5.16"
+ "@webgpu/types" "^0.1.40"
+
postcss-import@^15.1.0:
version "15.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
@@ -1462,11 +1793,379 @@ prettier@^3.3.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
+primereact@^10.8.3:
+ version "10.8.3"
+ resolved "https://registry.yarnpkg.com/primereact/-/primereact-10.8.3.tgz#124696b4fd6de45f1aa11a249b01e6c74ad0bae2"
+ integrity sha512-LYa7DL1TDmWWrPCeh3CMsx89LXgcf4+rYhJ6YiA7z164WsdzJK388Bp1Qdv5cfpyL/Nm0eIWxIApxwWBv8kwuA==
+ dependencies:
+ "@types/react-transition-group" "^4.4.1"
+ react-transition-group "^4.4.1"
+
+prop-types@^15.6.2, prop-types@^15.8.1:
+ version "15.8.1"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+rc-cascader@~3.28.1:
+ version "3.28.1"
+ resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.28.1.tgz#ea8a3de60521290096bab7e3fbe8ca097b976468"
+ integrity sha512-9+8oHIMWVLHxuaapDiqFNmD9KSyKN/P4bo9x/MBuDbyTqP8f2/POmmZxdXWBO3yq/uE3pKyQCXYNUxrNfHRv2A==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ array-tree-filter "^2.1.0"
+ classnames "^2.3.1"
+ rc-select "~14.15.0"
+ rc-tree "~5.9.0"
+ rc-util "^5.37.0"
+
+rc-checkbox@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-3.3.0.tgz#0ffcb65ab78c7d2fcd1a0d6554af36786516bd02"
+ integrity sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.3.2"
+ rc-util "^5.25.2"
+
+rc-collapse@~3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.8.0.tgz#02bcf81e1601aa185cd3b9fab0ceefd8dc11aefb"
+ integrity sha512-YVBkssrKPBG09TGfcWWGj8zJBYD9G3XuTy89t5iUmSXrIXEAnO1M+qjUxRW6b4Qi0+wNWG6MHJF/+US+nmIlzA==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-motion "^2.3.4"
+ rc-util "^5.27.0"
+
+rc-dialog@~9.6.0:
+ version "9.6.0"
+ resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-9.6.0.tgz#dc7a255c6ad1cb56021c3a61c7de86ee88c7c371"
+ integrity sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ "@rc-component/portal" "^1.0.0-8"
+ classnames "^2.2.6"
+ rc-motion "^2.3.0"
+ rc-util "^5.21.0"
+
+rc-drawer@~7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-7.2.0.tgz#8d7de2f1fd52f3ac5a25f54afbb8ac14c62e5663"
+ integrity sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ "@rc-component/portal" "^1.1.1"
+ classnames "^2.2.6"
+ rc-motion "^2.6.1"
+ rc-util "^5.38.1"
+
+rc-dropdown@~4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-4.2.0.tgz#c6052fcfe9c701487b141e411cdc277dc7c6f061"
+ integrity sha512-odM8Ove+gSh0zU27DUj5cG1gNKg7mLWBYzB5E4nNLrLwBmYEgYP43vHKDGOVZcJSVElQBI0+jTQgjnq0NfLjng==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "^2.2.6"
+ rc-util "^5.17.0"
+
+rc-field-form@~2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-2.4.0.tgz#26997160d12ae43a94c356c1290bfc011c69b3ca"
+ integrity sha512-XZ/lF9iqf9HXApIHQHqzJK5v2w4mkUMsVqAzOyWVzoiwwXEavY6Tpuw7HavgzIoD+huVff4JghSGcgEfX6eycg==
+ dependencies:
+ "@babel/runtime" "^7.18.0"
+ "@rc-component/async-validator" "^5.0.3"
+ rc-util "^5.32.2"
+
+rc-image@~7.11.0:
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-7.11.0.tgz#18c77ea557a6fdbe26856c688a9aace1505c0e77"
+ integrity sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==
+ dependencies:
+ "@babel/runtime" "^7.11.2"
+ "@rc-component/portal" "^1.0.2"
+ classnames "^2.2.6"
+ rc-dialog "~9.6.0"
+ rc-motion "^2.6.2"
+ rc-util "^5.34.1"
+
+rc-input-number@~9.2.0:
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-9.2.0.tgz#7e9344ff054421d2bfff0eebd7c1b8ef22d12220"
+ integrity sha512-5XZFhBCV5f9UQ62AZ2hFbEY8iZT/dm23Q1kAg0H8EvOgD3UDbYYJAayoVIkM3lQaCqYAW5gV0yV3vjw1XtzWHg==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ "@rc-component/mini-decimal" "^1.0.1"
+ classnames "^2.2.5"
+ rc-input "~1.6.0"
+ rc-util "^5.40.1"
+
+rc-input@~1.6.0, rc-input@~1.6.3:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.6.3.tgz#f1708fc3d5e68f95cb20faeb3eed1df8543cd444"
+ integrity sha512-wI4NzuqBS8vvKr8cljsvnTUqItMfG1QbJoxovCgL+DX4eVUcHIjVwharwevIxyy7H/jbLryh+K7ysnJr23aWIA==
+ dependencies:
+ "@babel/runtime" "^7.11.1"
+ classnames "^2.2.1"
+ rc-util "^5.18.1"
+
+rc-mentions@~2.16.1:
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-2.16.1.tgz#5e54ebe3ce6cd79838846ff1c8cfaf2e7aa15cec"
+ integrity sha512-GnhSTGP9Mtv6pqFFGQze44LlrtWOjHNrUUAcsdo9DnNAhN4pwVPEWy4z+2jpjkiGlJ3VoXdvMHcNDQdfI9fEaw==
+ dependencies:
+ "@babel/runtime" "^7.22.5"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "^2.2.6"
+ rc-input "~1.6.0"
+ rc-menu "~9.15.1"
+ rc-textarea "~1.8.0"
+ rc-util "^5.34.1"
+
+rc-menu@~9.15.1:
+ version "9.15.1"
+ resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.15.1.tgz#d8b38ea534a7f596a8da063881519e7eaafca698"
+ integrity sha512-UKporqU6LPfHnpPmtP6hdEK4iO5Q+b7BRv/uRpxdIyDGplZy9jwUjsnpev5bs3PQKB0H0n34WAPDfjAfn3kAPA==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "2.x"
+ rc-motion "^2.4.3"
+ rc-overflow "^1.3.1"
+ rc-util "^5.27.0"
+
+rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2, rc-motion@^2.9.0, rc-motion@^2.9.3:
+ version "2.9.3"
+ resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.9.3.tgz#b1bdaf816f1ccb3e4b3b0c531c3037a59286379e"
+ integrity sha512-rkW47ABVkic7WEB0EKJqzySpvDqwl60/tdkY7hWP7dYnh5pm0SzJpo54oW3TDUGXV5wfxXFmMkxrzRRbotQ0+w==
+ dependencies:
+ "@babel/runtime" "^7.11.1"
+ classnames "^2.2.1"
+ rc-util "^5.43.0"
+
+rc-notification@~5.6.2:
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.6.2.tgz#8525b32d49dd96ec974acae61d1d1eabde61463a"
+ integrity sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-motion "^2.9.0"
+ rc-util "^5.20.1"
+
+rc-overflow@^1.3.1, rc-overflow@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.3.2.tgz#72ee49e85a1308d8d4e3bd53285dc1f3e0bcce2c"
+ integrity sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==
+ dependencies:
+ "@babel/runtime" "^7.11.1"
+ classnames "^2.2.1"
+ rc-resize-observer "^1.0.0"
+ rc-util "^5.37.0"
+
+rc-pagination@~4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-4.3.0.tgz#c6022f820aa3a45fd734ae33a2915d39597dce1d"
+ integrity sha512-UubEWA0ShnroQ1tDa291Fzw6kj0iOeF26IsUObxYTpimgj4/qPCWVFl18RLZE+0Up1IZg0IK4pMn6nB3mjvB7g==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.3.2"
+ rc-util "^5.38.0"
+
+rc-picker@~4.6.15:
+ version "4.6.15"
+ resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-4.6.15.tgz#1531c9c382a295e2d1f1f38440d6678b09cd0468"
+ integrity sha512-OWZ1yrMie+KN2uEUfYCfS4b2Vu6RC1FWwNI0s+qypsc3wRt7g+peuZKVIzXCTaJwyyZruo80+akPg2+GmyiJjw==
+ dependencies:
+ "@babel/runtime" "^7.24.7"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "^2.2.1"
+ rc-overflow "^1.3.2"
+ rc-resize-observer "^1.4.0"
+ rc-util "^5.43.0"
+
+rc-progress@~4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-4.0.0.tgz#5382147d9add33d3a5fbd264001373df6440e126"
+ integrity sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.2.6"
+ rc-util "^5.16.1"
+
+rc-rate@~2.13.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.13.0.tgz#642f591ccf55c3a5d84d8d212caf1f7951d203a8"
+ integrity sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.2.5"
+ rc-util "^5.0.1"
+
+rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.3.1, rc-resize-observer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz#7bba61e6b3c604834980647cce6451914750d0cc"
+ integrity sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==
+ dependencies:
+ "@babel/runtime" "^7.20.7"
+ classnames "^2.2.1"
+ rc-util "^5.38.0"
+ resize-observer-polyfill "^1.5.1"
+
+rc-segmented@~2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.5.0.tgz#3b5423adf57459345c77c39c7581fde786a16c11"
+ integrity sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==
+ dependencies:
+ "@babel/runtime" "^7.11.1"
+ classnames "^2.2.1"
+ rc-motion "^2.4.4"
+ rc-util "^5.17.0"
+
+rc-select@~14.15.0, rc-select@~14.15.2:
+ version "14.15.2"
+ resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.15.2.tgz#d85fcf3a708bdf837b003feeed653347b8980ad0"
+ integrity sha512-oNoXlaFmpqXYcQDzcPVLrEqS2J9c+/+oJuGrlXeVVX/gVgrbHa5YcyiRUXRydFjyuA7GP3elRuLF7Y3Tfwltlw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ "@rc-component/trigger" "^2.1.1"
+ classnames "2.x"
+ rc-motion "^2.0.1"
+ rc-overflow "^1.3.1"
+ rc-util "^5.16.1"
+ rc-virtual-list "^3.5.2"
+
+rc-slider@~11.1.6:
+ version "11.1.7"
+ resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-11.1.7.tgz#3de333b1ec84d53a7bda2f816bb4779423628f09"
+ integrity sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.2.5"
+ rc-util "^5.36.0"
+
+rc-steps@~6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-6.0.1.tgz#c2136cd0087733f6d509209a84a5c80dc29a274d"
+ integrity sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==
+ dependencies:
+ "@babel/runtime" "^7.16.7"
+ classnames "^2.2.3"
+ rc-util "^5.16.1"
+
+rc-switch@~4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-4.1.0.tgz#f37d81b4e0c5afd1274fd85367b17306bf25e7d7"
+ integrity sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ classnames "^2.2.1"
+ rc-util "^5.30.0"
+
+rc-table@~7.47.5:
+ version "7.47.5"
+ resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.47.5.tgz#3c530200baa82346c7e72fe9b1dbd47d4aa15838"
+ integrity sha512-fzq+V9j/atbPIcvs3emuclaEoXulwQpIiJA6/7ey52j8+9cJ4P8DGmp4YzfUVDrb3qhgedcVeD6eRgUrokwVEQ==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ "@rc-component/context" "^1.4.0"
+ classnames "^2.2.5"
+ rc-resize-observer "^1.1.0"
+ rc-util "^5.41.0"
+ rc-virtual-list "^3.14.2"
+
+rc-tabs@~15.3.0:
+ version "15.3.0"
+ resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-15.3.0.tgz#3fcc332fbb9307d5eb147e0404daca871fb92a89"
+ integrity sha512-lzE18r+zppT/jZWOAWS6ntdkDUKHOLJzqMi5UAij1LeKwOaQaupupAoI9Srn73GRzVpmGznkECMRrzkRusC40A==
+ dependencies:
+ "@babel/runtime" "^7.11.2"
+ classnames "2.x"
+ rc-dropdown "~4.2.0"
+ rc-menu "~9.15.1"
+ rc-motion "^2.6.2"
+ rc-resize-observer "^1.0.0"
+ rc-util "^5.34.1"
+
+rc-textarea@~1.8.0, rc-textarea@~1.8.2:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.8.2.tgz#57a6847304551c1883fc3fb0c5076d587f70bf7f"
+ integrity sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "^2.2.1"
+ rc-input "~1.6.0"
+ rc-resize-observer "^1.0.0"
+ rc-util "^5.27.0"
+
+rc-tooltip@~6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-6.2.1.tgz#9a8f0335c86443a0c20c2557933205f645a381b7"
+ integrity sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==
+ dependencies:
+ "@babel/runtime" "^7.11.2"
+ "@rc-component/trigger" "^2.0.0"
+ classnames "^2.3.1"
+
+rc-tree-select@~5.23.0:
+ version "5.23.0"
+ resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.23.0.tgz#e56da0923c7c11dea98d4e14bb76969283c94468"
+ integrity sha512-aQGi2tFSRw1WbXv0UVXPzHm09E0cSvUVZMLxQtMv3rnZZpNmdRXWrnd9QkLNlVH31F+X5rgghmdSFF3yZW0N9A==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-select "~14.15.0"
+ rc-tree "~5.9.0"
+ rc-util "^5.16.1"
+
+rc-tree@~5.9.0:
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-5.9.0.tgz#1835b2bef36cfeb4ec15d62e0319fc503aa485f1"
+ integrity sha512-CPrgOvm9d/9E+izTONKSngNzQdIEjMox2PBufWjS1wf7vxtvmCWzK1SlpHbRY6IaBfJIeZ+88RkcIevf729cRg==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-motion "^2.0.1"
+ rc-util "^5.16.1"
+ rc-virtual-list "^3.5.1"
+
+rc-upload@~4.8.1:
+ version "4.8.1"
+ resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.8.1.tgz#ac55f2bc101b95b52a6e47f3c18f0f55b54e16d2"
+ integrity sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ classnames "^2.2.5"
+ rc-util "^5.2.0"
+
+rc-util@^5.0.1, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.27.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.2, rc-util@^5.34.1, rc-util@^5.35.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.38.1, rc-util@^5.40.1, rc-util@^5.41.0, rc-util@^5.43.0:
+ version "5.43.0"
+ resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.43.0.tgz#bba91fbef2c3e30ea2c236893746f3e9b05ecc4c"
+ integrity sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==
+ dependencies:
+ "@babel/runtime" "^7.18.3"
+ react-is "^18.2.0"
+
+rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
+ version "3.14.8"
+ resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.14.8.tgz#abf6e8809b7f5c955aa7f59c2a9d57443e9942fd"
+ integrity sha512-8D0KfzpRYi6YZvlOWIxiOm9BGt4Wf2hQyEaM6RXlDDiY2NhLheuYI+RA+7ZaZj1lq+XQqy3KHlaeeXQfzI5fGg==
+ dependencies:
+ "@babel/runtime" "^7.20.0"
+ classnames "^2.2.6"
+ rc-resize-observer "^1.0.0"
+ rc-util "^5.36.0"
+
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -1475,11 +2174,35 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-dropzone@^14.2.9:
+ version "14.2.9"
+ resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.9.tgz#193a33f9035e29fc91abf24e50de5d66cfa7c8c0"
+ integrity sha512-jRZsMC7h48WONsOLHcmhyn3cRWJoIPQjPApvt/sJVfnYaB3Qltn025AoRTTJaj4WdmmgmLl6tUQg1s0wOhpodQ==
+ dependencies:
+ attr-accept "^2.2.2"
+ file-selector "^0.6.0"
+ prop-types "^15.8.1"
+
+react-fast-compare@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
+ integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
+
react-hook-form@^7.53.0:
version "7.53.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab"
integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==
+react-is@^16.13.1:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-is@^18.2.0:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+
react-redux@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b"
@@ -1532,6 +2255,16 @@ react-style-singleton@^2.2.1:
invariant "^2.2.4"
tslib "^2.0.0"
+react-transition-group@^4.4.1:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -1558,16 +2291,31 @@ redux-thunk@^3.1.0:
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3"
integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
+redux-undo@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/redux-undo/-/redux-undo-1.1.0.tgz#ae28e517797b1aa5521d55da561f7214d310f8a5"
+ integrity sha512-zzLFh2qeF0MTIlzDhDLm9NtkfBqCllQJ3OCuIl5RKlG/ayHw6GUdIFdMhzMS9NnrnWdBX5u//ExMOHpfudGGOg==
+
redux@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
reselect@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e"
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
+resize-observer-polyfill@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
+ integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+
resolve@^1.1.7, resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
@@ -1596,6 +2344,18 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
+screenfull@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
+ integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
+
+scroll-into-view-if-needed@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz#fa9524518c799b45a2ef6bbffb92bcad0296d01f"
+ integrity sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==
+ dependencies:
+ compute-scroll-into-view "^3.0.2"
+
server-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e"
@@ -1637,6 +2397,11 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+string-convert@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
+ integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
+
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
name string-width-cjs
version "4.2.3"
@@ -1678,6 +2443,11 @@ styled-jsx@5.1.1:
dependencies:
client-only "0.0.1"
+stylis@^4.3.3:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4"
+ integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==
+
sucrase@^3.32.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
@@ -1748,6 +2518,16 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
+throttle-debounce@^5.0.0, throttle-debounce@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz#ec5549d84e053f043c9fd0f2a6dd892ff84456b1"
+ integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==
+
+tiny-invariant@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
+ integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -1755,6 +2535,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
+toggle-selection@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+ integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
+
totalist@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
@@ -1770,7 +2555,7 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
-tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0:
+tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==