From 41d909a9bcc7cea768a362d83309d4718b5adfb9 Mon Sep 17 00:00:00 2001 From: Lean Date: Tue, 18 Jul 2023 18:50:36 -0400 Subject: [PATCH 01/33] set metadata base --- apps/client/app/layout.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/client/app/layout.tsx b/apps/client/app/layout.tsx index dbe97992a..761079ce0 100644 --- a/apps/client/app/layout.tsx +++ b/apps/client/app/layout.tsx @@ -4,6 +4,8 @@ import { Metadata } from "next"; import { Nunito } from "next/font/google"; import { Suspense } from "react"; +import { env } from "@/src/env.mjs"; + import ClientWrapper from "./ClientWrapper"; import Toast from "./Toast"; @@ -39,6 +41,7 @@ export const metadata: Metadata = { }, keywords: ["Metaverse", "WebXR", "Gaming"], manifest: "/manifest.json", + metadataBase: new URL(env.NEXT_PUBLIC_DEPLOYED_URL), openGraph: { description: DESCRIPTION, images: [ From 48574ab1aaf72c1e59388248a3967bd985e6fcfd Mon Sep 17 00:00:00 2001 From: Lean Date: Tue, 18 Jul 2023 18:57:57 -0400 Subject: [PATCH 02/33] button wording --- apps/client/app/(navbar)/SignInButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/app/(navbar)/SignInButton.tsx b/apps/client/app/(navbar)/SignInButton.tsx index b0281ea0b..883e30566 100644 --- a/apps/client/app/(navbar)/SignInButton.tsx +++ b/apps/client/app/(navbar)/SignInButton.tsx @@ -63,7 +63,7 @@ export function SignInPage({ beforeOpen }: { beforeOpen?: () => void }) { className="flex h-11 w-full items-center justify-center space-x-2 rounded-lg border border-neutral-400 px-2 transition hover:bg-neutral-100" > - Sign in with Google + Google ) : null} From d74f9e20ea87f409d4c114c401b7348134c677b1 Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 11:25:06 -0400 Subject: [PATCH 03/33] create scene tree ui --- apps/client/app/(navbar)/SignInButton.tsx | 3 +- apps/client/app/play/App.tsx | 2 +- apps/client/app/play/Overlay.tsx | 2 +- apps/client/app/play/PlayOverlay.tsx | 2 +- .../app/play/{store.ts => playStore.ts} | 2 +- apps/client/src/play/hooks/useHotkeys.ts | 2 +- apps/client/src/play/hooks/useLoadUser.ts | 2 +- apps/client/src/play/hooks/useSave.ts | 2 +- apps/client/src/play/ui/ChatBox.tsx | 2 +- apps/client/src/play/ui/ChatMessage.tsx | 2 +- .../src/play/ui/Settings/AvatarSettings.tsx | 2 +- .../src/play/ui/Settings/NameSettings.tsx | 2 +- .../src/play/ui/Settings/SettingsDialog.tsx | 2 +- .../src/play/ui/editor/EditModeButton.tsx | 10 +- apps/client/src/play/ui/editor/Left.tsx | 2 +- apps/client/src/play/ui/editor/PanelPage.tsx | 2 +- apps/client/src/play/ui/editor/Right.tsx | 2 +- apps/client/src/play/ui/editor/ScenePage.tsx | 5 +- apps/client/src/play/ui/editor/SceneTree.tsx | 11 +++ apps/client/src/play/ui/editor/Tools.tsx | 2 +- apps/client/src/play/ui/editor/TreeItem.tsx | 38 ++++++++ apps/client/src/play/ui/editor/WorldPage.tsx | 2 +- .../src/play/ui/editor/hooks/useTreeItem.ts | 13 +++ apps/client/src/play/utils/setAvatar.ts | 2 +- .../src/{store.ts => clientStore.ts} | 0 .../react-client/src/components/Client.tsx | 2 +- .../src/editor/classes/SceneTree.ts | 25 +++++ .../src/editor/classes/TreeItem.ts | 94 +++++++++++++++++++ packages/react-client/src/editor/events.ts | 26 +++++ packages/react-client/src/editor/index.ts | 3 + packages/react-client/src/editor/plugin.ts | 17 ++++ .../react-client/src/editor/sceneStore.ts | 17 ++++ .../editor => editor/systems}/addMeshes.ts | 2 +- .../editor => editor/systems}/addNodes.ts | 2 +- .../src/editor/systems/createTreeItems.ts | 85 +++++++++++++++++ .../systems}/enterEditMode.ts | 0 .../editor => editor/systems}/exitEditMode.ts | 0 .../systems}/sendExportEvent.ts | 0 packages/react-client/src/events.ts | 25 ----- packages/react-client/src/index.ts | 3 +- packages/react-client/src/plugin.ts | 12 +-- .../react-client/src/systems/connectToHost.ts | 2 +- .../react-client/src/systems/joinWorld.ts | 2 +- .../react-client/src/systems/movePlayers.ts | 2 +- .../src/systems/publishLocation.ts | 2 +- .../react-client/src/systems/saveExport.ts | 2 +- .../react-client/src/systems/sendEvents.ts | 5 +- .../src/systems/setLocationUpdateTime.ts | 2 +- .../src/systems/setPlayersAirTime.ts | 2 +- .../src/systems/setPlayersAvatars.ts | 2 +- .../react-client/src/systems/setRootName.ts | 2 +- .../react-client/src/systems/setSkybox.ts | 2 +- .../react-client/src/systems/setUserAvatar.ts | 2 +- .../react-client/src/systems/spawnPlayers.ts | 2 +- 54 files changed, 382 insertions(+), 76 deletions(-) rename apps/client/app/play/{store.ts => playStore.ts} (95%) create mode 100644 apps/client/src/play/ui/editor/SceneTree.tsx create mode 100644 apps/client/src/play/ui/editor/TreeItem.tsx create mode 100644 apps/client/src/play/ui/editor/hooks/useTreeItem.ts rename packages/react-client/src/{store.ts => clientStore.ts} (100%) create mode 100644 packages/react-client/src/editor/classes/SceneTree.ts create mode 100644 packages/react-client/src/editor/classes/TreeItem.ts create mode 100644 packages/react-client/src/editor/events.ts create mode 100644 packages/react-client/src/editor/index.ts create mode 100644 packages/react-client/src/editor/plugin.ts create mode 100644 packages/react-client/src/editor/sceneStore.ts rename packages/react-client/src/{systems/editor => editor/systems}/addMeshes.ts (97%) rename packages/react-client/src/{systems/editor => editor/systems}/addNodes.ts (96%) create mode 100644 packages/react-client/src/editor/systems/createTreeItems.ts rename packages/react-client/src/{systems/editor => editor/systems}/enterEditMode.ts (100%) rename packages/react-client/src/{systems/editor => editor/systems}/exitEditMode.ts (100%) rename packages/react-client/src/{systems/editor => editor/systems}/sendExportEvent.ts (100%) diff --git a/apps/client/app/(navbar)/SignInButton.tsx b/apps/client/app/(navbar)/SignInButton.tsx index 883e30566..8dc930a41 100644 --- a/apps/client/app/(navbar)/SignInButton.tsx +++ b/apps/client/app/(navbar)/SignInButton.tsx @@ -60,10 +60,9 @@ export function SignInPage({ beforeOpen }: { beforeOpen?: () => void }) { - Google ) : null} diff --git a/apps/client/app/play/App.tsx b/apps/client/app/play/App.tsx index 9877cc0c2..6300dc9af 100644 --- a/apps/client/app/play/App.tsx +++ b/apps/client/app/play/App.tsx @@ -11,7 +11,7 @@ import { useHotkeys } from "@/src/play/hooks/useHotkeys"; import { useLoadUser } from "@/src/play/hooks/useLoadUser"; import LoadingScreen from "./LoadingScreen"; -import { usePlayStore } from "./store"; +import { usePlayStore } from "./playStore"; import { WorldUriId } from "./types"; const Client = dynamic( diff --git a/apps/client/app/play/Overlay.tsx b/apps/client/app/play/Overlay.tsx index 23c22c4a8..43b305d99 100644 --- a/apps/client/app/play/Overlay.tsx +++ b/apps/client/app/play/Overlay.tsx @@ -9,7 +9,7 @@ import MobileChatBox from "../../src/play/ui/MobileChatBox"; import { useIsMobile } from "../../src/utils/useIsMobile"; import BuildOverlay from "./BuildOverlay"; import PlayOverlay from "./PlayOverlay"; -import { usePlayStore } from "./store"; +import { usePlayStore } from "./playStore"; import { PlayMode, WorldUriId } from "./types"; interface Props { diff --git a/apps/client/app/play/PlayOverlay.tsx b/apps/client/app/play/PlayOverlay.tsx index 5f0ba2f7e..da4506386 100644 --- a/apps/client/app/play/PlayOverlay.tsx +++ b/apps/client/app/play/PlayOverlay.tsx @@ -7,7 +7,7 @@ import Logo from "@/public/images/Logo.png"; import { usePointerLocked } from "@/src/play/hooks/usePointerLocked"; import SettingsDialog from "@/src/play/ui/Settings/SettingsDialog"; -import { usePlayStore } from "./store"; +import { usePlayStore } from "./playStore"; import { WorldUriId } from "./types"; interface Props { diff --git a/apps/client/app/play/store.ts b/apps/client/app/play/playStore.ts similarity index 95% rename from apps/client/app/play/store.ts rename to apps/client/app/play/playStore.ts index 0f59fbc12..b6bf928e0 100644 --- a/apps/client/app/play/store.ts +++ b/apps/client/app/play/playStore.ts @@ -23,7 +23,7 @@ export interface PlayStore { export const usePlayStore = create(() => ({ chatBoxFocused: false, - leftPage: LeftPanelPage.Add, + leftPage: LeftPanelPage.Scene, metadata: { model: "" }, mode: PlayMode.Play, rightPage: RightPanelPage.World, diff --git a/apps/client/src/play/hooks/useHotkeys.ts b/apps/client/src/play/hooks/useHotkeys.ts index a1c19e1e4..0d571ed2c 100644 --- a/apps/client/src/play/hooks/useHotkeys.ts +++ b/apps/client/src/play/hooks/useHotkeys.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { PlayMode } from "@/app/play/types"; export function useHotkeys() { diff --git a/apps/client/src/play/hooks/useLoadUser.ts b/apps/client/src/play/hooks/useLoadUser.ts index f034cdfaf..4484ab1bb 100644 --- a/apps/client/src/play/hooks/useLoadUser.ts +++ b/apps/client/src/play/hooks/useLoadUser.ts @@ -1,7 +1,7 @@ import { useClientStore } from "@unavi/react-client"; import { useEffect } from "react"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { useAuth } from "@/src/client/AuthProvider"; import { HOME_SERVER } from "@/src/constants"; diff --git a/apps/client/src/play/hooks/useSave.ts b/apps/client/src/play/hooks/useSave.ts index c0ccd18b7..871cf65aa 100644 --- a/apps/client/src/play/hooks/useSave.ts +++ b/apps/client/src/play/hooks/useSave.ts @@ -5,7 +5,7 @@ import { toast } from "react-hot-toast"; import { updateWorld } from "@/app/api/worlds/[id]/helper"; import { getWorldModelFileUpload } from "@/app/api/worlds/[id]/model/files/[file]/helper"; import { createWorldModel } from "@/app/api/worlds/[id]/model/helper"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; const toastId = "world-save"; diff --git a/apps/client/src/play/ui/ChatBox.tsx b/apps/client/src/play/ui/ChatBox.tsx index d283e13e4..035506a7a 100644 --- a/apps/client/src/play/ui/ChatBox.tsx +++ b/apps/client/src/play/ui/ChatBox.tsx @@ -1,7 +1,7 @@ import { useClientStore } from "@unavi/react-client"; import { useEffect, useRef } from "react"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { usePointerLocked } from "../hooks/usePointerLocked"; import ChatMessage from "./ChatMessage"; diff --git a/apps/client/src/play/ui/ChatMessage.tsx b/apps/client/src/play/ui/ChatMessage.tsx index 70b6a3914..c48fa28d1 100644 --- a/apps/client/src/play/ui/ChatMessage.tsx +++ b/apps/client/src/play/ui/ChatMessage.tsx @@ -4,7 +4,7 @@ import { } from "@unavi/react-client"; import { useEffect, useState } from "react"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import Tooltip from "@/src/ui/Tooltip"; import { usePointerLocked } from "../hooks/usePointerLocked"; diff --git a/apps/client/src/play/ui/Settings/AvatarSettings.tsx b/apps/client/src/play/ui/Settings/AvatarSettings.tsx index 2cdc12663..56b271d03 100644 --- a/apps/client/src/play/ui/Settings/AvatarSettings.tsx +++ b/apps/client/src/play/ui/Settings/AvatarSettings.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { BsFillGridFill } from "react-icons/bs"; import { MdClose } from "react-icons/md"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { env } from "../../../env.mjs"; import FileInput from "../../../ui/FileInput"; diff --git a/apps/client/src/play/ui/Settings/NameSettings.tsx b/apps/client/src/play/ui/Settings/NameSettings.tsx index 4ac21130c..57b203a96 100644 --- a/apps/client/src/play/ui/Settings/NameSettings.tsx +++ b/apps/client/src/play/ui/Settings/NameSettings.tsx @@ -1,6 +1,6 @@ import { useClientStore } from "@unavi/react-client"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { useAuth } from "@/src/client/AuthProvider"; import { toHex } from "@/src/utils/toHex"; diff --git a/apps/client/src/play/ui/Settings/SettingsDialog.tsx b/apps/client/src/play/ui/Settings/SettingsDialog.tsx index 73567ee0e..e30b86f3a 100644 --- a/apps/client/src/play/ui/Settings/SettingsDialog.tsx +++ b/apps/client/src/play/ui/Settings/SettingsDialog.tsx @@ -1,7 +1,7 @@ import { useClientStore } from "@unavi/react-client"; import { useEffect, useState } from "react"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import DialogContent, { DialogRoot } from "../../../ui/Dialog"; import { LocalStorageKey } from "../../constants"; diff --git a/apps/client/src/play/ui/editor/EditModeButton.tsx b/apps/client/src/play/ui/editor/EditModeButton.tsx index 837dec20f..add755a69 100644 --- a/apps/client/src/play/ui/editor/EditModeButton.tsx +++ b/apps/client/src/play/ui/editor/EditModeButton.tsx @@ -1,8 +1,12 @@ -import { ClientSchedules, useClientStore } from "@unavi/react-client"; +import { + ClientSchedules, + useClientStore, + useSceneStore, +} from "@unavi/react-client"; import { useEffect } from "react"; import { MdConstruction } from "react-icons/md"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { PlayMode } from "@/app/play/types"; import Tooltip from "@/src/ui/Tooltip"; @@ -26,8 +30,10 @@ export default function EditModeButton() { + + ); } diff --git a/apps/client/src/play/ui/editor/SceneTree.tsx b/apps/client/src/play/ui/editor/SceneTree.tsx new file mode 100644 index 000000000..7ab175e75 --- /dev/null +++ b/apps/client/src/play/ui/editor/SceneTree.tsx @@ -0,0 +1,11 @@ +import { useSceneStore } from "@unavi/react-client"; + +import TreeItem from "./TreeItem"; + +export default function SceneTree() { + const rootId = useSceneStore((state) => state.rootId); + + if (!rootId) return null; + + return ; +} diff --git a/apps/client/src/play/ui/editor/Tools.tsx b/apps/client/src/play/ui/editor/Tools.tsx index 7b314e04e..87e06b30d 100644 --- a/apps/client/src/play/ui/editor/Tools.tsx +++ b/apps/client/src/play/ui/editor/Tools.tsx @@ -3,7 +3,7 @@ import { BiMove } from "react-icons/bi"; import { CgArrowsExpandUpRight } from "react-icons/cg"; import { MdSync } from "react-icons/md"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { Tool } from "@/app/play/types"; import Tooltip from "@/src/ui/Tooltip"; diff --git a/apps/client/src/play/ui/editor/TreeItem.tsx b/apps/client/src/play/ui/editor/TreeItem.tsx new file mode 100644 index 000000000..bea9c66f6 --- /dev/null +++ b/apps/client/src/play/ui/editor/TreeItem.tsx @@ -0,0 +1,38 @@ +import { useSceneStore } from "@unavi/react-client"; + +interface Props { + id: bigint; +} + +export default function TreeItem({ id }: Props) { + const name = useSceneStore((state) => state.items.get(id)?.name); + const childrenIds = useSceneStore( + (state) => state.items.get(id)?.childrenIds + ); + const selectedId = useSceneStore((state) => state.selectedId); + + function select() { + useSceneStore.setState({ selectedId: id }); + } + + const isSelected = selectedId === id; + + return ( +
+ + +
+ {childrenIds?.map((childId) => ( + + ))} +
+
+ ); +} diff --git a/apps/client/src/play/ui/editor/WorldPage.tsx b/apps/client/src/play/ui/editor/WorldPage.tsx index 28c7c9a0e..2c599fd14 100644 --- a/apps/client/src/play/ui/editor/WorldPage.tsx +++ b/apps/client/src/play/ui/editor/WorldPage.tsx @@ -1,4 +1,4 @@ -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import ImageInput from "@/src/ui/ImageInput"; import TextAreaDark from "@/src/ui/TextAreaDark"; import TextFieldDark from "@/src/ui/TextFieldDark"; diff --git a/apps/client/src/play/ui/editor/hooks/useTreeItem.ts b/apps/client/src/play/ui/editor/hooks/useTreeItem.ts new file mode 100644 index 000000000..5f89cc65e --- /dev/null +++ b/apps/client/src/play/ui/editor/hooks/useTreeItem.ts @@ -0,0 +1,13 @@ +import { useSceneStore } from "@unavi/react-client"; +import { useMemo } from "react"; + +export function useTreeItem(id: bigint | undefined) { + const items = useSceneStore((state) => state.items); + + const item = useMemo(() => { + if (!id) return undefined; + return items.get(id); + }, [id, items]); + + return item; +} diff --git a/apps/client/src/play/utils/setAvatar.ts b/apps/client/src/play/utils/setAvatar.ts index 2b56b01fe..a20a18ca6 100644 --- a/apps/client/src/play/utils/setAvatar.ts +++ b/apps/client/src/play/utils/setAvatar.ts @@ -1,7 +1,7 @@ import { useClientStore } from "@unavi/react-client"; import { getTempUpload } from "@/app/api/temp/helper"; -import { usePlayStore } from "@/app/play/store"; +import { usePlayStore } from "@/app/play/playStore"; import { cdnURL, S3Path } from "@/src/utils/s3Paths"; import { LocalStorageKey } from "../constants"; diff --git a/packages/react-client/src/store.ts b/packages/react-client/src/clientStore.ts similarity index 100% rename from packages/react-client/src/store.ts rename to packages/react-client/src/clientStore.ts diff --git a/packages/react-client/src/components/Client.tsx b/packages/react-client/src/components/Client.tsx index 0d926877d..d45f6e3bc 100644 --- a/packages/react-client/src/components/Client.tsx +++ b/packages/react-client/src/components/Client.tsx @@ -2,8 +2,8 @@ import { WorldMetadata } from "@wired-protocol/types"; import { Engine } from "lattice-engine/core"; import { useEffect } from "react"; +import { useClientStore } from "../clientStore"; import { useWorld } from "../hooks/useWorld"; -import { useClientStore } from "../store"; import Canvas from "./Canvas"; interface Props { diff --git a/packages/react-client/src/editor/classes/SceneTree.ts b/packages/react-client/src/editor/classes/SceneTree.ts new file mode 100644 index 000000000..936cf279e --- /dev/null +++ b/packages/react-client/src/editor/classes/SceneTree.ts @@ -0,0 +1,25 @@ +import { TreeItem } from "./TreeItem"; + +export class SceneTree { + readonly items = new Map(); + + #rootId?: bigint; + + root?: TreeItem; + + get rootId() { + return this.#rootId; + } + + set rootId(value: bigint | undefined) { + if (this.#rootId === value) return; + + this.#rootId = value; + + if (value) { + this.root = this.items.get(value); + } else { + this.root = undefined; + } + } +} diff --git a/packages/react-client/src/editor/classes/TreeItem.ts b/packages/react-client/src/editor/classes/TreeItem.ts new file mode 100644 index 000000000..51b20be21 --- /dev/null +++ b/packages/react-client/src/editor/classes/TreeItem.ts @@ -0,0 +1,94 @@ +import { useSceneStore } from "../sceneStore"; + +export class TreeItem { + readonly id: bigint; + + name = ""; + translation: [number, number, number] = [0, 0, 0]; + rotation: [number, number, number, number] = [0, 0, 0, 1]; + scale: [number, number, number] = [1, 1, 1]; + + #parentId?: bigint; + #childrenIds: bigint[] = []; + + constructor(id: bigint) { + this.id = id; + } + + get parentId(): bigint | undefined { + return this.#parentId; + } + + set parentId(value: bigint | undefined) { + if (this.#parentId === value) return; + + if (this.#parentId) { + const parent = useSceneStore.getState().items.get(this.#parentId); + this.#parentId = undefined; + + if (parent) { + parent.removeChild(this); + } + } + + if (value) { + const parent = useSceneStore.getState().items.get(value); + if (parent) { + this.#parentId = value; + parent.addChild(this); + } + } + } + + get childrenIds(): bigint[] { + return this.#childrenIds; + } + + get children(): TreeItem[] { + return this.childrenIds.map((id) => { + const item = useSceneStore.getState().items.get(id); + if (!item) throw new Error(`Item with id ${id} not found`); + return item; + }); + } + + get parent(): TreeItem | undefined { + if (!this.#parentId) return undefined; + return useSceneStore.getState().items.get(this.#parentId); + } + + addChild(child: TreeItem) { + if (child.parentId !== this.id) { + child.parentId = this.id; + } + + this.childrenIds.push(child.id); + } + + removeChild(child: TreeItem) { + child.parentId = undefined; + + const index = this.childrenIds.indexOf(child.id); + if (index !== -1) this.childrenIds.splice(index, 1); + } + + clearChildren() { + for (const child of this.children) { + child.parentId = undefined; + } + + this.childrenIds.length = 0; + } + + destroy() { + this.parent?.removeChild(this); + this.clearChildren(); + + useSceneStore.setState((state) => { + state.items.delete(this.id); + return { + items: new Map(state.items), + }; + }); + } +} diff --git a/packages/react-client/src/editor/events.ts b/packages/react-client/src/editor/events.ts new file mode 100644 index 000000000..22881dac4 --- /dev/null +++ b/packages/react-client/src/editor/events.ts @@ -0,0 +1,26 @@ +import { Resource } from "lattice-engine/core"; +import { struct } from "thyseus"; + +@struct +export class AddNode { + @struct.string declare name: string; + @struct.string declare meshName: string; + @struct.string declare parentName: string; +} + +@struct +export class AddMesh { + @struct.string declare name: string; + @struct.string declare materialName: string; + + @struct.substruct(Resource) declare indices: Resource; + @struct.substruct(Resource) declare colors: Resource; + @struct.substruct(Resource) declare joints: Resource; + @struct.substruct(Resource) declare normals: Resource; + @struct.substruct(Resource) declare positions: Resource; + @struct.substruct(Resource) declare uv1: Resource; + @struct.substruct(Resource) declare uv2: Resource; + @struct.substruct(Resource) declare uv3: Resource; + @struct.substruct(Resource) declare uv: Resource; + @struct.substruct(Resource) declare weights: Resource; +} diff --git a/packages/react-client/src/editor/index.ts b/packages/react-client/src/editor/index.ts new file mode 100644 index 000000000..ad14ee687 --- /dev/null +++ b/packages/react-client/src/editor/index.ts @@ -0,0 +1,3 @@ +export * from "./classes/SceneTree"; +export * from "./classes/TreeItem"; +export * from "./sceneStore"; diff --git a/packages/react-client/src/editor/plugin.ts b/packages/react-client/src/editor/plugin.ts new file mode 100644 index 000000000..c7ea0bf2f --- /dev/null +++ b/packages/react-client/src/editor/plugin.ts @@ -0,0 +1,17 @@ +import { WorldBuilder } from "thyseus"; + +import { ClientSchedules } from "../constants"; +import { addMeshes } from "./systems/addMeshes"; +import { addNodes } from "./systems/addNodes"; +import { createTreeItems } from "./systems/createTreeItems"; +import { enterEditMode } from "./systems/enterEditMode"; +import { exitEditMode } from "./systems/exitEditMode"; +import { sendExportEvent } from "./systems/sendExportEvent"; + +export function editorPlugin(builder: WorldBuilder) { + builder + .addSystemsToSchedule(ClientSchedules.EnterEditMode, enterEditMode) + .addSystemsToSchedule(ClientSchedules.ExitEditMode, exitEditMode) + .addSystemsToSchedule(ClientSchedules.Export, sendExportEvent) + .addSystems(addMeshes, addNodes, createTreeItems); +} diff --git a/packages/react-client/src/editor/sceneStore.ts b/packages/react-client/src/editor/sceneStore.ts new file mode 100644 index 000000000..462daac42 --- /dev/null +++ b/packages/react-client/src/editor/sceneStore.ts @@ -0,0 +1,17 @@ +import { create } from "zustand"; + +import { TreeItem } from "./classes/TreeItem"; + +export interface SceneStore { + enabled: boolean; + items: Map; + rootId?: bigint; + selectedId?: bigint; +} + +export const useSceneStore = create(() => ({ + enabled: false, + items: new Map(), + rootId: undefined, + selectedId: undefined, +})); diff --git a/packages/react-client/src/systems/editor/addMeshes.ts b/packages/react-client/src/editor/systems/addMeshes.ts similarity index 97% rename from packages/react-client/src/systems/editor/addMeshes.ts rename to packages/react-client/src/editor/systems/addMeshes.ts index 86eaf59a9..1771b5522 100644 --- a/packages/react-client/src/systems/editor/addMeshes.ts +++ b/packages/react-client/src/editor/systems/addMeshes.ts @@ -2,7 +2,7 @@ import { Warehouse } from "lattice-engine/core"; import { Geometry, Mesh, Name } from "lattice-engine/scene"; import { Commands, dropStruct, Entity, EventReader, Query, Res } from "thyseus"; -import { AddMesh } from "../../events"; +import { AddMesh } from "../events"; export function addMeshes( commands: Commands, diff --git a/packages/react-client/src/systems/editor/addNodes.ts b/packages/react-client/src/editor/systems/addNodes.ts similarity index 96% rename from packages/react-client/src/systems/editor/addNodes.ts rename to packages/react-client/src/editor/systems/addNodes.ts index 922a67307..58ee9b0b0 100644 --- a/packages/react-client/src/systems/editor/addNodes.ts +++ b/packages/react-client/src/editor/systems/addNodes.ts @@ -7,7 +7,7 @@ import { } from "lattice-engine/scene"; import { Commands, dropStruct, Entity, EventReader, Mut, Query } from "thyseus"; -import { AddNode } from "../../events"; +import { AddNode } from "../events"; export function addNodes( commands: Commands, diff --git a/packages/react-client/src/editor/systems/createTreeItems.ts b/packages/react-client/src/editor/systems/createTreeItems.ts new file mode 100644 index 000000000..7e5cb9c3f --- /dev/null +++ b/packages/react-client/src/editor/systems/createTreeItems.ts @@ -0,0 +1,85 @@ +import { + Name, + Parent, + Scene, + SceneStruct, + Transform, +} from "lattice-engine/scene"; +import { Entity, Query, Res, With } from "thyseus"; + +import { TreeItem } from "../classes/TreeItem"; +import { useSceneStore } from "../sceneStore"; + +/** + * Updates the scene tree with the latest entities. + */ +export function createTreeItems( + nodes: Query<[Entity, Transform, Parent]>, + scenes: Query>, + names: Query<[Entity, Name]>, + sceneStruct: Res +) { + const { enabled, items } = useSceneStore.getState(); + if (!enabled) return; + + const ids: bigint[] = []; + + for (const entity of scenes) { + ids.push(entity.id); + + let item = items.get(entity.id); + + if (!item) { + item = new TreeItem(entity.id); + items.set(entity.id, item); + } + + if (sceneStruct.activeScene === entity.id) { + useSceneStore.setState({ rootId: entity.id }); + } + } + + for (const [entity, transform, parent] of nodes) { + ids.push(entity.id); + + let item = items.get(entity.id); + + if (!item) { + item = new TreeItem(entity.id); + items.set(entity.id, item); + } + + item.translation[0] = transform.translation.x; + item.translation[1] = transform.translation.y; + item.translation[2] = transform.translation.z; + + item.rotation[0] = transform.rotation.x; + item.rotation[1] = transform.rotation.y; + item.rotation[2] = transform.rotation.z; + item.rotation[3] = transform.rotation.w; + + item.scale[0] = transform.scale.x; + item.scale[1] = transform.scale.y; + item.scale[2] = transform.scale.z; + + if (parent) { + item.parentId = parent.id; + } else { + item.parentId = undefined; + } + } + + for (const [entity, name] of names) { + const item = items.get(entity.id); + if (!item) continue; + + item.name = name.value; + } + + // Destroy items that no longer exist + for (const [id, item] of items.entries()) { + if (!ids.includes(id)) { + item.destroy(); + } + } +} diff --git a/packages/react-client/src/systems/editor/enterEditMode.ts b/packages/react-client/src/editor/systems/enterEditMode.ts similarity index 100% rename from packages/react-client/src/systems/editor/enterEditMode.ts rename to packages/react-client/src/editor/systems/enterEditMode.ts diff --git a/packages/react-client/src/systems/editor/exitEditMode.ts b/packages/react-client/src/editor/systems/exitEditMode.ts similarity index 100% rename from packages/react-client/src/systems/editor/exitEditMode.ts rename to packages/react-client/src/editor/systems/exitEditMode.ts diff --git a/packages/react-client/src/systems/editor/sendExportEvent.ts b/packages/react-client/src/editor/systems/sendExportEvent.ts similarity index 100% rename from packages/react-client/src/systems/editor/sendExportEvent.ts rename to packages/react-client/src/editor/systems/sendExportEvent.ts diff --git a/packages/react-client/src/events.ts b/packages/react-client/src/events.ts index 37c6cafda..4cceed7ff 100644 --- a/packages/react-client/src/events.ts +++ b/packages/react-client/src/events.ts @@ -1,4 +1,3 @@ -import { Resource } from "lattice-engine/core"; import { struct } from "thyseus"; @struct @@ -10,27 +9,3 @@ export class PlayerJoin { export class PlayerLeave { @struct.u8 declare playerId: number; } - -@struct -export class AddNode { - @struct.string declare name: string; - @struct.string declare meshName: string; - @struct.string declare parentName: string; -} - -@struct -export class AddMesh { - @struct.string declare name: string; - @struct.string declare materialName: string; - - @struct.substruct(Resource) declare indices: Resource; - @struct.substruct(Resource) declare colors: Resource; - @struct.substruct(Resource) declare joints: Resource; - @struct.substruct(Resource) declare normals: Resource; - @struct.substruct(Resource) declare positions: Resource; - @struct.substruct(Resource) declare uv1: Resource; - @struct.substruct(Resource) declare uv2: Resource; - @struct.substruct(Resource) declare uv3: Resource; - @struct.substruct(Resource) declare uv: Resource; - @struct.substruct(Resource) declare weights: Resource; -} diff --git a/packages/react-client/src/index.ts b/packages/react-client/src/index.ts index ca0f80545..ed7ec7153 100644 --- a/packages/react-client/src/index.ts +++ b/packages/react-client/src/index.ts @@ -1,5 +1,6 @@ +export * from "./clientStore"; export * from "./components/Client"; export * from "./constants"; -export * from "./store"; +export * from "./editor"; export * from "./systems/exportLoadingInfo"; export * from "./types"; diff --git a/packages/react-client/src/plugin.ts b/packages/react-client/src/plugin.ts index 85c8880f9..0569bf5f7 100644 --- a/packages/react-client/src/plugin.ts +++ b/packages/react-client/src/plugin.ts @@ -12,13 +12,9 @@ import { vrmPlugin } from "lattice-engine/vrm"; import { run, WorldBuilder } from "thyseus"; import { ClientSchedules } from "./constants"; +import { editorPlugin } from "./editor/plugin"; import { calcPlayerVelocity } from "./systems/calcPlayerVelocity"; import { connectToHost } from "./systems/connectToHost"; -import { addMeshes } from "./systems/editor/addMeshes"; -import { addNodes } from "./systems/editor/addNodes"; -import { enterEditMode } from "./systems/editor/enterEditMode"; -import { exitEditMode } from "./systems/editor/exitEditMode"; -import { sendExportEvent } from "./systems/editor/sendExportEvent"; import { exportLoadingInfo } from "./systems/exportLoadingInfo"; import { initApp } from "./systems/initApp"; import { joinWorld } from "./systems/joinWorld"; @@ -48,16 +44,12 @@ export function clientPlugin(builder: WorldBuilder) { .addPlugin(textPlugin) .addPlugin(transformPlugin) .addPlugin(vrmPlugin) + .addPlugin(editorPlugin) .addSystemsToSchedule(LatticeSchedules.Startup, initApp) .addSystemsToSchedule(LatticeSchedules.PreUpdate, sendEvents) .addSystemsToSchedule(LatticeSchedules.PostFixedUpdate, publishLocation) .addSystemsToSchedule(ClientSchedules.ConnectToHost, connectToHost) - .addSystemsToSchedule(ClientSchedules.EnterEditMode, enterEditMode) - .addSystemsToSchedule(ClientSchedules.ExitEditMode, exitEditMode) - .addSystemsToSchedule(ClientSchedules.Export, sendExportEvent) .addSystems( - addMeshes, - addNodes, exportLoadingInfo, joinWorld, movePlayers, diff --git a/packages/react-client/src/systems/connectToHost.ts b/packages/react-client/src/systems/connectToHost.ts index 3f6cfee98..6f419c64c 100644 --- a/packages/react-client/src/systems/connectToHost.ts +++ b/packages/react-client/src/systems/connectToHost.ts @@ -10,9 +10,9 @@ import { } from "mediasoup-client/lib/types"; import { Query, SystemRes } from "thyseus"; +import { useClientStore } from "../clientStore"; import { WorldJson } from "../components"; import { LOCATION_ROUNDING } from "../constants"; -import { useClientStore } from "../store"; import { ValidSendMessage } from "../types"; import { toHex } from "../utils/toHex"; diff --git a/packages/react-client/src/systems/joinWorld.ts b/packages/react-client/src/systems/joinWorld.ts index 36fc20939..47bfa807e 100644 --- a/packages/react-client/src/systems/joinWorld.ts +++ b/packages/react-client/src/systems/joinWorld.ts @@ -1,8 +1,8 @@ import { Asset } from "lattice-engine/core"; import { Mut, Query, With } from "thyseus"; +import { useClientStore } from "../clientStore"; import { WorldJson } from "../components"; -import { useClientStore } from "../store"; export function joinWorld(worlds: Query, With>) { const uri = useClientStore.getState().worldUri; diff --git a/packages/react-client/src/systems/movePlayers.ts b/packages/react-client/src/systems/movePlayers.ts index de8f7fb4a..2847c8d5e 100644 --- a/packages/react-client/src/systems/movePlayers.ts +++ b/packages/react-client/src/systems/movePlayers.ts @@ -1,7 +1,7 @@ import { Mut, Query } from "thyseus"; +import { useClientStore } from "../clientStore"; import { NetworkTransform, OtherPlayer } from "../components"; -import { useClientStore } from "../store"; const locations = useClientStore.getState().locations; diff --git a/packages/react-client/src/systems/publishLocation.ts b/packages/react-client/src/systems/publishLocation.ts index e110dad99..b5aa1bc11 100644 --- a/packages/react-client/src/systems/publishLocation.ts +++ b/packages/react-client/src/systems/publishLocation.ts @@ -3,8 +3,8 @@ import { PlayerAvatar, PlayerBody, PlayerCamera } from "lattice-engine/player"; import { Parent, Transform } from "lattice-engine/scene"; import { Entity, Query, Res, struct, SystemRes, With } from "thyseus"; +import { useClientStore } from "../clientStore"; import { NETWORK_UPDATE_HZ } from "../constants"; -import { useClientStore } from "../store"; import { serializeLocation } from "../utils/serializeLocation"; const FALL_THRESHOLD_SECONDS = 0.35; diff --git a/packages/react-client/src/systems/saveExport.ts b/packages/react-client/src/systems/saveExport.ts index a55e3ec72..549e2746b 100644 --- a/packages/react-client/src/systems/saveExport.ts +++ b/packages/react-client/src/systems/saveExport.ts @@ -1,7 +1,7 @@ import { ExportedGltf } from "lattice-engine/gltf"; import { EventReader } from "thyseus"; -import { useClientStore } from "../store"; +import { useClientStore } from "../clientStore"; export function saveExport(reader: EventReader) { if (reader.length === 0) return; diff --git a/packages/react-client/src/systems/sendEvents.ts b/packages/react-client/src/systems/sendEvents.ts index bd9415737..00a25d4cd 100644 --- a/packages/react-client/src/systems/sendEvents.ts +++ b/packages/react-client/src/systems/sendEvents.ts @@ -1,8 +1,9 @@ import { Warehouse } from "lattice-engine/core"; import { EventWriter, Res } from "thyseus"; -import { AddMesh, AddNode, PlayerJoin, PlayerLeave } from "../events"; -import { useClientStore } from "../store"; +import { useClientStore } from "../clientStore"; +import { AddMesh, AddNode } from "../editor/events"; +import { PlayerJoin, PlayerLeave } from "../events"; export function sendEvents( warehouse: Res, diff --git a/packages/react-client/src/systems/setLocationUpdateTime.ts b/packages/react-client/src/systems/setLocationUpdateTime.ts index bf97d3d5e..8f7f1c449 100644 --- a/packages/react-client/src/systems/setLocationUpdateTime.ts +++ b/packages/react-client/src/systems/setLocationUpdateTime.ts @@ -1,7 +1,7 @@ import { Mut, Query } from "thyseus"; +import { useClientStore } from "../clientStore"; import { NetworkTransform, OtherPlayer } from "../components"; -import { useClientStore } from "../store"; const lastLocationUpdates = useClientStore.getState().lastLocationUpdates; diff --git a/packages/react-client/src/systems/setPlayersAirTime.ts b/packages/react-client/src/systems/setPlayersAirTime.ts index 8bfdbab2e..951c02191 100644 --- a/packages/react-client/src/systems/setPlayersAirTime.ts +++ b/packages/react-client/src/systems/setPlayersAirTime.ts @@ -2,8 +2,8 @@ import { Time } from "lattice-engine/core"; import { PlayerBody } from "lattice-engine/player"; import { Mut, Query, Res } from "thyseus"; +import { useClientStore } from "../clientStore"; import { OtherPlayer } from "../components"; -import { useClientStore } from "../store"; const falling = useClientStore.getState().falling; diff --git a/packages/react-client/src/systems/setPlayersAvatars.ts b/packages/react-client/src/systems/setPlayersAvatars.ts index 15c83a06b..200c6a529 100644 --- a/packages/react-client/src/systems/setPlayersAvatars.ts +++ b/packages/react-client/src/systems/setPlayersAvatars.ts @@ -3,8 +3,8 @@ import { Parent } from "lattice-engine/scene"; import { Vrm } from "lattice-engine/vrm"; import { Entity, Mut, Query, With } from "thyseus"; +import { useClientStore } from "../clientStore"; import { OtherPlayer } from "../components"; -import { useClientStore } from "../store"; export function setPlayersAvatar( players: Query<[Entity, OtherPlayer]>, diff --git a/packages/react-client/src/systems/setRootName.ts b/packages/react-client/src/systems/setRootName.ts index e15622b51..447f2104c 100644 --- a/packages/react-client/src/systems/setRootName.ts +++ b/packages/react-client/src/systems/setRootName.ts @@ -1,7 +1,7 @@ import { Name, Scene, SceneStruct } from "lattice-engine/scene"; import { Entity, Query, Res } from "thyseus"; -import { useClientStore } from "../store"; +import { useClientStore } from "../clientStore"; export function setRootName( sceneStruct: Res, diff --git a/packages/react-client/src/systems/setSkybox.ts b/packages/react-client/src/systems/setSkybox.ts index 609ea55cd..487b5b396 100644 --- a/packages/react-client/src/systems/setSkybox.ts +++ b/packages/react-client/src/systems/setSkybox.ts @@ -2,7 +2,7 @@ import { Asset } from "lattice-engine/core"; import { Image, Skybox } from "lattice-engine/scene"; import { Entity, Mut, Query } from "thyseus"; -import { useClientStore } from "../store"; +import { useClientStore } from "../clientStore"; export function setSkybox( skyboxes: Query, diff --git a/packages/react-client/src/systems/setUserAvatar.ts b/packages/react-client/src/systems/setUserAvatar.ts index f21dccd76..eb82fde30 100644 --- a/packages/react-client/src/systems/setUserAvatar.ts +++ b/packages/react-client/src/systems/setUserAvatar.ts @@ -3,7 +3,7 @@ import { Parent } from "lattice-engine/scene"; import { Vrm } from "lattice-engine/vrm"; import { Entity, Mut, Query, With } from "thyseus"; -import { useClientStore } from "../store"; +import { useClientStore } from "../clientStore"; export function setUserAvatar( bodies: Query>, diff --git a/packages/react-client/src/systems/spawnPlayers.ts b/packages/react-client/src/systems/spawnPlayers.ts index 6b75d53ea..407efb35f 100644 --- a/packages/react-client/src/systems/spawnPlayers.ts +++ b/packages/react-client/src/systems/spawnPlayers.ts @@ -10,9 +10,9 @@ import { import { Vrm } from "lattice-engine/vrm"; import { Commands, dropStruct, Entity, EventReader, Query, Res } from "thyseus"; +import { useClientStore } from "../clientStore"; import { NetworkTransform, OtherPlayer, PrevTranslation } from "../components"; import { PlayerJoin, PlayerLeave } from "../events"; -import { useClientStore } from "../store"; export function spawnPlayers( commands: Commands, From 91a114225255f3fe792993db28384b2dfb57177d Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 12:56:55 -0400 Subject: [PATCH 04/33] better tree display --- apps/client/src/play/ui/editor/PanelPage.tsx | 37 +++++++++++++------ apps/client/src/play/ui/editor/ScenePage.tsx | 24 +++++++++++- apps/client/src/play/ui/editor/SceneTree.tsx | 23 +++++++++--- apps/client/src/play/ui/editor/TreeItem.tsx | 25 ++++++++----- .../src/editor/classes/TreeItem.ts | 15 ++++++++ packages/react-client/src/editor/plugin.ts | 3 +- .../react-client/src/editor/sceneStore.ts | 2 + .../src/editor/systems/createTreeItems.ts | 23 ++++-------- .../src/editor/systems/syncTransformTarget.ts | 24 ++++++++++++ 9 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 packages/react-client/src/editor/systems/syncTransformTarget.ts diff --git a/apps/client/src/play/ui/editor/PanelPage.tsx b/apps/client/src/play/ui/editor/PanelPage.tsx index f34194488..fef186914 100644 --- a/apps/client/src/play/ui/editor/PanelPage.tsx +++ b/apps/client/src/play/ui/editor/PanelPage.tsx @@ -6,6 +6,7 @@ import { LeftPanelPage, RightPanelPage } from "@/app/play/types"; interface PropsBase { children: React.ReactNode; title: string; + onBack?: () => void; } interface PropsNone extends PropsBase { @@ -30,8 +31,13 @@ export default function PanelPage({ title, side, parentPage, + onBack, }: Props) { function handleBack() { + if (onBack) { + onBack(); + } + if (!side) return; if (side === "left") { @@ -41,21 +47,28 @@ export default function PanelPage({ } } + const showBack = side !== undefined || onBack !== undefined; + return (
-
- {!side ? ( -
- ) : ( - - )} +
+
+ {showBack ? ( + + ) : null} +
-

{title}

+

+ {title} +

{children} diff --git a/apps/client/src/play/ui/editor/ScenePage.tsx b/apps/client/src/play/ui/editor/ScenePage.tsx index 11f91b6d0..26ab27430 100644 --- a/apps/client/src/play/ui/editor/ScenePage.tsx +++ b/apps/client/src/play/ui/editor/ScenePage.tsx @@ -1,12 +1,32 @@ +import { useSceneStore } from "@unavi/react-client"; + import { usePlayStore } from "@/app/play/playStore"; import { LeftPanelPage } from "@/app/play/types"; +import { useTreeItem } from "./hooks/useTreeItem"; import PanelPage from "./PanelPage"; import SceneTree from "./SceneTree"; export default function ScenePage() { + const rootId = useSceneStore((state) => state.rootId); + const sceneTreeId = useSceneStore((state) => state.sceneTreeId); + const usedId = sceneTreeId || rootId; + + const item = useTreeItem(usedId); + + if (usedId === undefined) { + return null; + } + + const handleBack = item?.parentId + ? () => useSceneStore.setState({ sceneTreeId: item.parentId }) + : undefined; + + const name = + usedId === rootId ? "Scene" : item?.name || `(${usedId.toString()})`; + return ( - + - + ); } diff --git a/apps/client/src/play/ui/editor/SceneTree.tsx b/apps/client/src/play/ui/editor/SceneTree.tsx index 7ab175e75..87d3dc1a0 100644 --- a/apps/client/src/play/ui/editor/SceneTree.tsx +++ b/apps/client/src/play/ui/editor/SceneTree.tsx @@ -1,11 +1,22 @@ -import { useSceneStore } from "@unavi/react-client"; - +import { useTreeItem } from "./hooks/useTreeItem"; import TreeItem from "./TreeItem"; -export default function SceneTree() { - const rootId = useSceneStore((state) => state.rootId); +interface Props { + rootId: bigint; +} + +export default function SceneTree({ rootId }: Props) { + const rootItem = useTreeItem(rootId); - if (!rootId) return null; + if (!rootItem) { + return null; + } - return ; + return ( +
+ {rootItem.childrenIds.map((id) => ( + + ))} +
+ ); } diff --git a/apps/client/src/play/ui/editor/TreeItem.tsx b/apps/client/src/play/ui/editor/TreeItem.tsx index bea9c66f6..6a0cbec69 100644 --- a/apps/client/src/play/ui/editor/TreeItem.tsx +++ b/apps/client/src/play/ui/editor/TreeItem.tsx @@ -1,4 +1,5 @@ import { useSceneStore } from "@unavi/react-client"; +import { IoMdExpand } from "react-icons/io"; interface Props { id: bigint; @@ -6,33 +7,37 @@ interface Props { export default function TreeItem({ id }: Props) { const name = useSceneStore((state) => state.items.get(id)?.name); - const childrenIds = useSceneStore( - (state) => state.items.get(id)?.childrenIds - ); const selectedId = useSceneStore((state) => state.selectedId); function select() { useSceneStore.setState({ selectedId: id }); } + function expand() { + useSceneStore.setState({ sceneTreeId: id, selectedId: id }); + } + const isSelected = selectedId === id; return ( -
+
-
- {childrenIds?.map((childId) => ( - - ))} -
+
); } diff --git a/packages/react-client/src/editor/classes/TreeItem.ts b/packages/react-client/src/editor/classes/TreeItem.ts index 51b20be21..d247d7b07 100644 --- a/packages/react-client/src/editor/classes/TreeItem.ts +++ b/packages/react-client/src/editor/classes/TreeItem.ts @@ -80,6 +80,21 @@ export class TreeItem { this.childrenIds.length = 0; } + sortChildren() { + this.childrenIds.sort((a, b) => { + const aItem = useSceneStore.getState().items.get(a); + const bItem = useSceneStore.getState().items.get(b); + + if (aItem?.childrenIds?.length && !bItem?.childrenIds?.length) { + return 1; + } else if (!aItem?.childrenIds?.length && bItem?.childrenIds?.length) { + return -1; + } else { + return aItem?.name?.localeCompare(bItem?.name || "") || 0; + } + }); + } + destroy() { this.parent?.removeChild(this); this.clearChildren(); diff --git a/packages/react-client/src/editor/plugin.ts b/packages/react-client/src/editor/plugin.ts index c7ea0bf2f..d962361e1 100644 --- a/packages/react-client/src/editor/plugin.ts +++ b/packages/react-client/src/editor/plugin.ts @@ -7,11 +7,12 @@ import { createTreeItems } from "./systems/createTreeItems"; import { enterEditMode } from "./systems/enterEditMode"; import { exitEditMode } from "./systems/exitEditMode"; import { sendExportEvent } from "./systems/sendExportEvent"; +import { syncTransformTarget } from "./systems/syncTransformTarget"; export function editorPlugin(builder: WorldBuilder) { builder .addSystemsToSchedule(ClientSchedules.EnterEditMode, enterEditMode) .addSystemsToSchedule(ClientSchedules.ExitEditMode, exitEditMode) .addSystemsToSchedule(ClientSchedules.Export, sendExportEvent) - .addSystems(addMeshes, addNodes, createTreeItems); + .addSystems(addMeshes, addNodes, createTreeItems, syncTransformTarget); } diff --git a/packages/react-client/src/editor/sceneStore.ts b/packages/react-client/src/editor/sceneStore.ts index 462daac42..66a6b5ac5 100644 --- a/packages/react-client/src/editor/sceneStore.ts +++ b/packages/react-client/src/editor/sceneStore.ts @@ -6,6 +6,7 @@ export interface SceneStore { enabled: boolean; items: Map; rootId?: bigint; + sceneTreeId?: bigint; selectedId?: bigint; } @@ -13,5 +14,6 @@ export const useSceneStore = create(() => ({ enabled: false, items: new Map(), rootId: undefined, + sceneTreeId: undefined, selectedId: undefined, })); diff --git a/packages/react-client/src/editor/systems/createTreeItems.ts b/packages/react-client/src/editor/systems/createTreeItems.ts index 7e5cb9c3f..a26387967 100644 --- a/packages/react-client/src/editor/systems/createTreeItems.ts +++ b/packages/react-client/src/editor/systems/createTreeItems.ts @@ -5,7 +5,7 @@ import { SceneStruct, Transform, } from "lattice-engine/scene"; -import { Entity, Query, Res, With } from "thyseus"; +import { Entity, Query, Res } from "thyseus"; import { TreeItem } from "../classes/TreeItem"; import { useSceneStore } from "../sceneStore"; @@ -15,30 +15,23 @@ import { useSceneStore } from "../sceneStore"; */ export function createTreeItems( nodes: Query<[Entity, Transform, Parent]>, - scenes: Query>, + scenes: Query<[Entity, Scene]>, names: Query<[Entity, Name]>, sceneStruct: Res ) { const { enabled, items } = useSceneStore.getState(); if (!enabled) return; - const ids: bigint[] = []; - - for (const entity of scenes) { - ids.push(entity.id); - - let item = items.get(entity.id); - - if (!item) { - item = new TreeItem(entity.id); - items.set(entity.id, item); - } - + // Set root id + for (const [entity, scene] of scenes) { if (sceneStruct.activeScene === entity.id) { - useSceneStore.setState({ rootId: entity.id }); + useSceneStore.setState({ rootId: scene.rootId }); } } + const ids: bigint[] = []; + + // Create or update items for (const [entity, transform, parent] of nodes) { ids.push(entity.id); diff --git a/packages/react-client/src/editor/systems/syncTransformTarget.ts b/packages/react-client/src/editor/systems/syncTransformTarget.ts new file mode 100644 index 000000000..ed6a12269 --- /dev/null +++ b/packages/react-client/src/editor/systems/syncTransformTarget.ts @@ -0,0 +1,24 @@ +import { TransformControls } from "lattice-engine/transform"; +import { Mut, Query } from "thyseus"; + +import { useSceneStore } from "../sceneStore"; + +let lastTransformTarget: bigint | undefined; + +export function syncTransformTarget( + transformControls: Query> +) { + for (const transformControl of transformControls) { + const targetId = transformControl.targetId; + + if (targetId !== lastTransformTarget) { + // Set UI from transform controls + useSceneStore.setState({ selectedId: targetId }); + } else { + // Set transform controls from UI + transformControl.targetId = useSceneStore.getState().selectedId ?? 0n; + } + + lastTransformTarget = targetId; + } +} From 8d672938dbaae2e70b376b644318adc1642bd188 Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 13:24:19 -0400 Subject: [PATCH 05/33] create inspect page + organize --- apps/client/app/play/BuildOverlay.tsx | 4 ++-- apps/client/src/play/ui/editor/Right.tsx | 16 --------------- .../ui/editor/{ => pages/Add}/AddPage.tsx | 8 ++++---- .../ui/editor/pages/Inspect/InspectPage.tsx | 20 +++++++++++++++++++ .../src/play/ui/editor/{ => pages}/Left.tsx | 4 ++-- .../play/ui/editor/{ => pages}/PanelPage.tsx | 0 .../client/src/play/ui/editor/pages/Right.tsx | 16 +++++++++++++++ .../ui/editor/{ => pages/Scene}/ScenePage.tsx | 4 ++-- .../ui/editor/{ => pages/Scene}/SceneTree.tsx | 2 +- .../ui/editor/{ => pages/Scene}/TreeItem.tsx | 0 .../ui/editor/{ => pages/World}/WorldPage.tsx | 4 ++-- 11 files changed, 49 insertions(+), 29 deletions(-) delete mode 100644 apps/client/src/play/ui/editor/Right.tsx rename apps/client/src/play/ui/editor/{ => pages/Add}/AddPage.tsx (89%) create mode 100644 apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx rename apps/client/src/play/ui/editor/{ => pages}/Left.tsx (86%) rename apps/client/src/play/ui/editor/{ => pages}/PanelPage.tsx (100%) create mode 100644 apps/client/src/play/ui/editor/pages/Right.tsx rename apps/client/src/play/ui/editor/{ => pages/Scene}/ScenePage.tsx (91%) rename apps/client/src/play/ui/editor/{ => pages/Scene}/SceneTree.tsx (87%) rename apps/client/src/play/ui/editor/{ => pages/Scene}/TreeItem.tsx (100%) rename apps/client/src/play/ui/editor/{ => pages/World}/WorldPage.tsx (96%) diff --git a/apps/client/app/play/BuildOverlay.tsx b/apps/client/app/play/BuildOverlay.tsx index ec08b8b5b..baa6d8ca8 100644 --- a/apps/client/app/play/BuildOverlay.tsx +++ b/apps/client/app/play/BuildOverlay.tsx @@ -1,5 +1,5 @@ -import Left from "@/src/play/ui/editor/Left"; -import Right from "@/src/play/ui/editor/Right"; +import Left from "@/src/play/ui/editor/pages/Left"; +import Right from "@/src/play/ui/editor/pages/Right"; import Tools from "@/src/play/ui/editor/Tools"; export default function BuildOverlay() { diff --git a/apps/client/src/play/ui/editor/Right.tsx b/apps/client/src/play/ui/editor/Right.tsx deleted file mode 100644 index 111b4ff5a..000000000 --- a/apps/client/src/play/ui/editor/Right.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { usePlayStore } from "@/app/play/playStore"; -import { RightPanelPage } from "@/app/play/types"; - -import WorldPage from "./WorldPage"; - -export default function Right() { - const page = usePlayStore((state) => state.rightPage); - - return ( -
-
- {page === RightPanelPage.World ? : null} -
-
- ); -} diff --git a/apps/client/src/play/ui/editor/AddPage.tsx b/apps/client/src/play/ui/editor/pages/Add/AddPage.tsx similarity index 89% rename from apps/client/src/play/ui/editor/AddPage.tsx rename to apps/client/src/play/ui/editor/pages/Add/AddPage.tsx index 79b41003d..8bc4d0097 100644 --- a/apps/client/src/play/ui/editor/AddPage.tsx +++ b/apps/client/src/play/ui/editor/pages/Add/AddPage.tsx @@ -9,10 +9,10 @@ import { import { LeftPanelPage } from "@/app/play/types"; -import { addBox } from "../../actions/addBox"; -import { addCylinder } from "../../actions/addCylinder"; -import { addSphere } from "../../actions/addSphere"; -import PanelPage from "./PanelPage"; +import { addBox } from "../../../../actions/addBox"; +import { addCylinder } from "../../../../actions/addCylinder"; +import { addSphere } from "../../../../actions/addSphere"; +import PanelPage from "../PanelPage"; export default function AddPage() { return ( diff --git a/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx b/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx new file mode 100644 index 000000000..807562798 --- /dev/null +++ b/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx @@ -0,0 +1,20 @@ +import { useTreeItem } from "../../hooks/useTreeItem"; +import PanelPage from "../PanelPage"; + +interface Props { + id: bigint; +} + +export default function InspectPage({ id }: Props) { + const item = useTreeItem(id); + + if (!item) { + return null; + } + + return ( + +
+
+ ); +} diff --git a/apps/client/src/play/ui/editor/Left.tsx b/apps/client/src/play/ui/editor/pages/Left.tsx similarity index 86% rename from apps/client/src/play/ui/editor/Left.tsx rename to apps/client/src/play/ui/editor/pages/Left.tsx index 90cb5cce6..2b69b4e91 100644 --- a/apps/client/src/play/ui/editor/Left.tsx +++ b/apps/client/src/play/ui/editor/pages/Left.tsx @@ -1,8 +1,8 @@ import { usePlayStore } from "@/app/play/playStore"; import { LeftPanelPage } from "@/app/play/types"; -import AddPage from "./AddPage"; -import ScenePage from "./ScenePage"; +import AddPage from "./Add/AddPage"; +import ScenePage from "./Scene/ScenePage"; export default function Left() { const page = usePlayStore((state) => state.leftPage); diff --git a/apps/client/src/play/ui/editor/PanelPage.tsx b/apps/client/src/play/ui/editor/pages/PanelPage.tsx similarity index 100% rename from apps/client/src/play/ui/editor/PanelPage.tsx rename to apps/client/src/play/ui/editor/pages/PanelPage.tsx diff --git a/apps/client/src/play/ui/editor/pages/Right.tsx b/apps/client/src/play/ui/editor/pages/Right.tsx new file mode 100644 index 000000000..bbb7802ee --- /dev/null +++ b/apps/client/src/play/ui/editor/pages/Right.tsx @@ -0,0 +1,16 @@ +import { useSceneStore } from "@unavi/react-client"; + +import InspectPage from "./Inspect/InspectPage"; +import WorldPage from "./World/WorldPage"; + +export default function Right() { + const selectedId = useSceneStore((state) => state.selectedId); + + return ( +
+
+ {selectedId ? : } +
+
+ ); +} diff --git a/apps/client/src/play/ui/editor/ScenePage.tsx b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx similarity index 91% rename from apps/client/src/play/ui/editor/ScenePage.tsx rename to apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx index 26ab27430..8cea5e534 100644 --- a/apps/client/src/play/ui/editor/ScenePage.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx @@ -3,8 +3,8 @@ import { useSceneStore } from "@unavi/react-client"; import { usePlayStore } from "@/app/play/playStore"; import { LeftPanelPage } from "@/app/play/types"; -import { useTreeItem } from "./hooks/useTreeItem"; -import PanelPage from "./PanelPage"; +import { useTreeItem } from "../../hooks/useTreeItem"; +import PanelPage from "../PanelPage"; import SceneTree from "./SceneTree"; export default function ScenePage() { diff --git a/apps/client/src/play/ui/editor/SceneTree.tsx b/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx similarity index 87% rename from apps/client/src/play/ui/editor/SceneTree.tsx rename to apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx index 87d3dc1a0..33c2454f0 100644 --- a/apps/client/src/play/ui/editor/SceneTree.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx @@ -1,4 +1,4 @@ -import { useTreeItem } from "./hooks/useTreeItem"; +import { useTreeItem } from "../../hooks/useTreeItem"; import TreeItem from "./TreeItem"; interface Props { diff --git a/apps/client/src/play/ui/editor/TreeItem.tsx b/apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx similarity index 100% rename from apps/client/src/play/ui/editor/TreeItem.tsx rename to apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx diff --git a/apps/client/src/play/ui/editor/WorldPage.tsx b/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx similarity index 96% rename from apps/client/src/play/ui/editor/WorldPage.tsx rename to apps/client/src/play/ui/editor/pages/World/WorldPage.tsx index 2c599fd14..5be7951c9 100644 --- a/apps/client/src/play/ui/editor/WorldPage.tsx +++ b/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx @@ -3,8 +3,8 @@ import ImageInput from "@/src/ui/ImageInput"; import TextAreaDark from "@/src/ui/TextAreaDark"; import TextFieldDark from "@/src/ui/TextFieldDark"; -import { useSave } from "../../hooks/useSave"; -import PanelPage from "./PanelPage"; +import { useSave } from "../../../../hooks/useSave"; +import PanelPage from "../PanelPage"; export default function WorldPage() { const worldId = usePlayStore((state) => state.worldId); From 7691d32ad121731bfe38707672af9e3b47392d1b Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 13:34:24 -0400 Subject: [PATCH 06/33] styling --- apps/client/src/play/ui/editor/pages/Left.tsx | 2 +- apps/client/src/play/ui/editor/pages/PanelPage.tsx | 2 +- apps/client/src/play/ui/editor/pages/Right.tsx | 2 +- apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx | 6 +++++- apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx | 8 +++++++- apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx | 6 ++++-- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/client/src/play/ui/editor/pages/Left.tsx b/apps/client/src/play/ui/editor/pages/Left.tsx index 2b69b4e91..d72c1193f 100644 --- a/apps/client/src/play/ui/editor/pages/Left.tsx +++ b/apps/client/src/play/ui/editor/pages/Left.tsx @@ -9,7 +9,7 @@ export default function Left() { return (
-
+
{page === LeftPanelPage.Add ? ( ) : page === LeftPanelPage.Scene ? ( diff --git a/apps/client/src/play/ui/editor/pages/PanelPage.tsx b/apps/client/src/play/ui/editor/pages/PanelPage.tsx index fef186914..631641990 100644 --- a/apps/client/src/play/ui/editor/pages/PanelPage.tsx +++ b/apps/client/src/play/ui/editor/pages/PanelPage.tsx @@ -50,7 +50,7 @@ export default function PanelPage({ const showBack = side !== undefined || onBack !== undefined; return ( -
+
{showBack ? ( diff --git a/apps/client/src/play/ui/editor/pages/Right.tsx b/apps/client/src/play/ui/editor/pages/Right.tsx index bbb7802ee..df4b201c6 100644 --- a/apps/client/src/play/ui/editor/pages/Right.tsx +++ b/apps/client/src/play/ui/editor/pages/Right.tsx @@ -8,7 +8,7 @@ export default function Right() { return (
-
+
{selectedId ? : }
diff --git a/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx index 8cea5e534..e6d90a248 100644 --- a/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx @@ -19,7 +19,11 @@ export default function ScenePage() { } const handleBack = item?.parentId - ? () => useSceneStore.setState({ sceneTreeId: item.parentId }) + ? () => + useSceneStore.setState({ + sceneTreeId: item.parentId, + selectedId: usedId, + }) : undefined; const name = diff --git a/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx b/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx index 33c2454f0..c279c69bd 100644 --- a/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/SceneTree.tsx @@ -1,3 +1,5 @@ +import { useSceneStore } from "@unavi/react-client"; + import { useTreeItem } from "../../hooks/useTreeItem"; import TreeItem from "./TreeItem"; @@ -12,8 +14,12 @@ export default function SceneTree({ rootId }: Props) { return null; } + function clearSelected() { + useSceneStore.setState({ selectedId: undefined }); + } + return ( -
+
{rootItem.childrenIds.map((id) => ( ))} diff --git a/apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx b/apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx index 6a0cbec69..d5a205c27 100644 --- a/apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/TreeItem.tsx @@ -9,11 +9,13 @@ export default function TreeItem({ id }: Props) { const name = useSceneStore((state) => state.items.get(id)?.name); const selectedId = useSceneStore((state) => state.selectedId); - function select() { + function select(e: React.MouseEvent) { + e.stopPropagation(); useSceneStore.setState({ selectedId: id }); } - function expand() { + function expand(e: React.MouseEvent) { + e.stopPropagation(); useSceneStore.setState({ sceneTreeId: id, selectedId: id }); } From 588e4164b6d6deda1655afd8dba71013d8537c63 Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 14:28:51 -0400 Subject: [PATCH 07/33] fix world page styling --- .../src/play/ui/editor/pages/PanelPage.tsx | 2 +- .../play/ui/editor/pages/World/WorldPage.tsx | 132 +++++++++--------- packages/react-client/src/systems/initApp.ts | 4 +- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/apps/client/src/play/ui/editor/pages/PanelPage.tsx b/apps/client/src/play/ui/editor/pages/PanelPage.tsx index 631641990..0bfe744a6 100644 --- a/apps/client/src/play/ui/editor/pages/PanelPage.tsx +++ b/apps/client/src/play/ui/editor/pages/PanelPage.tsx @@ -50,7 +50,7 @@ export default function PanelPage({ const showBack = side !== undefined || onBack !== undefined; return ( -
+
{showBack ? ( diff --git a/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx b/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx index 5be7951c9..6343deddf 100644 --- a/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx +++ b/apps/client/src/play/ui/editor/pages/World/WorldPage.tsx @@ -18,85 +18,85 @@ export default function WorldPage() { worldId.type === "id" ? `World ${worldId.value.slice(0, 6)}` : ""; return ( -
- - { - const file = e.target.files?.[0]; - if (!file) return; + + { + const file = e.target.files?.[0]; + if (!file) return; - const reader = new FileReader(); + const reader = new FileReader(); - reader.onload = (e) => { - const image = e.target?.result as string; + reader.onload = (e) => { + const image = e.target?.result as string; - usePlayStore.setState((prev) => ({ - metadata: { - ...prev.metadata, - info: { - ...prev.metadata.info, - image, - }, - }, - })); - }; - - reader.readAsDataURL(file); - }} - dark - className="h-40 w-full rounded-lg object-cover" - /> - - { usePlayStore.setState((prev) => ({ metadata: { ...prev.metadata, info: { ...prev.metadata.info, - title: e.target.value, + image, }, }, })); - }} - /> + }; - { - usePlayStore.setState((prev) => ({ - metadata: { - ...prev.metadata, - info: { - ...prev.metadata.info, - description: e.target.value, - }, + reader.readAsDataURL(file); + }} + dark + className="h-40 w-full rounded-lg object-cover" + /> + + { + usePlayStore.setState((prev) => ({ + metadata: { + ...prev.metadata, + info: { + ...prev.metadata.info, + title: e.target.value, }, - })); - }} - className="max-h-40" - /> - + }, + })); + }} + /> + + { + usePlayStore.setState((prev) => ({ + metadata: { + ...prev.metadata, + info: { + ...prev.metadata.info, + description: e.target.value, + }, + }, + })); + }} + className="max-h-40" + /> - -
+
+ +
+ ); } diff --git a/packages/react-client/src/systems/initApp.ts b/packages/react-client/src/systems/initApp.ts index 473d3a8ab..7a384f878 100644 --- a/packages/react-client/src/systems/initApp.ts +++ b/packages/react-client/src/systems/initApp.ts @@ -21,10 +21,10 @@ export function initApp( ) { coreStore.canvas = document.querySelector("canvas"); - const { rootId } = createScene(commands, coreStore, sceneStruct); + const { rootId, sceneId } = createScene(commands, coreStore, sceneStruct); const cameraId = createPlayerControls( [0, 8, 0], - rootId, + sceneId, commands, sceneStruct, inputStruct From d99908bde523d68277d594071dceab76d7f289a8 Mon Sep 17 00:00:00 2001 From: Lean Date: Wed, 19 Jul 2023 14:41:37 -0400 Subject: [PATCH 08/33] create useTreeValue --- .../src/play/ui/editor/hooks/useTreeValue.ts | 12 ++++++++++++ .../ui/editor/pages/Inspect/InspectPage.tsx | 19 +++++++++++++++++-- .../play/ui/editor/pages/Scene/ScenePage.tsx | 16 +++++++++------- .../play/ui/editor/pages/Scene/SceneTree.tsx | 10 +++------- .../play/ui/editor/utils/getDisplayName.ts | 3 +++ 5 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 apps/client/src/play/ui/editor/hooks/useTreeValue.ts create mode 100644 apps/client/src/play/ui/editor/utils/getDisplayName.ts diff --git a/apps/client/src/play/ui/editor/hooks/useTreeValue.ts b/apps/client/src/play/ui/editor/hooks/useTreeValue.ts new file mode 100644 index 000000000..177993653 --- /dev/null +++ b/apps/client/src/play/ui/editor/hooks/useTreeValue.ts @@ -0,0 +1,12 @@ +import { TreeItem, useSceneStore } from "@unavi/react-client"; + +export function useTreeValue( + id: bigint | undefined, + key: T +): TreeItem[T] | undefined { + const value = useSceneStore((state) => + id ? state.items.get(id)?.[key] : undefined + ); + + return value; +} diff --git a/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx b/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx index 807562798..7e79ab539 100644 --- a/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx +++ b/apps/client/src/play/ui/editor/pages/Inspect/InspectPage.tsx @@ -1,4 +1,9 @@ +import { useSceneStore } from "@unavi/react-client"; + +import TextFieldDark from "@/src/ui/TextFieldDark"; + import { useTreeItem } from "../../hooks/useTreeItem"; +import { getDisplayName } from "../../utils/getDisplayName"; import PanelPage from "../PanelPage"; interface Props { @@ -6,15 +11,25 @@ interface Props { } export default function InspectPage({ id }: Props) { + const name = useSceneStore((state) => state.items.get(id)?.name); const item = useTreeItem(id); if (!item) { return null; } + const displayName = getDisplayName(name, id); + return ( - -
+ + { + item.name = e.target.value; + }} + /> ); } diff --git a/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx index e6d90a248..0ea27db77 100644 --- a/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx +++ b/apps/client/src/play/ui/editor/pages/Scene/ScenePage.tsx @@ -3,7 +3,8 @@ import { useSceneStore } from "@unavi/react-client"; import { usePlayStore } from "@/app/play/playStore"; import { LeftPanelPage } from "@/app/play/types"; -import { useTreeItem } from "../../hooks/useTreeItem"; +import { useTreeValue } from "../../hooks/useTreeValue"; +import { getDisplayName } from "../../utils/getDisplayName"; import PanelPage from "../PanelPage"; import SceneTree from "./SceneTree"; @@ -12,25 +13,26 @@ export default function ScenePage() { const sceneTreeId = useSceneStore((state) => state.sceneTreeId); const usedId = sceneTreeId || rootId; - const item = useTreeItem(usedId); + const parentId = useTreeValue(usedId, "parentId"); + const name = useTreeValue(usedId, "name"); if (usedId === undefined) { return null; } - const handleBack = item?.parentId + const handleBack = parentId ? () => useSceneStore.setState({ - sceneTreeId: item.parentId, + sceneTreeId: parentId, selectedId: usedId, }) : undefined; - const name = - usedId === rootId ? "Scene" : item?.name || `(${usedId.toString()})`; + const displayName = + usedId === rootId ? "Scene" : getDisplayName(name, usedId); return ( - + - +
+ + + + + + + +
); } diff --git a/apps/client/src/ui/TextFieldDark.tsx b/apps/client/src/ui/TextFieldDark.tsx index a24236cfb..26e6f0b05 100644 --- a/apps/client/src/ui/TextFieldDark.tsx +++ b/apps/client/src/ui/TextFieldDark.tsx @@ -5,7 +5,7 @@ interface Props extends InputHTMLAttributes { } const TextFieldDark = forwardRef( - ({ label, className, children, ...rest }, ref) => { + ({ label, className, disabled, children, ...rest }, ref) => { return (