diff --git a/packages/client-core/src/admin/adminRoutes.tsx b/packages/client-core/src/admin/adminRoutes.tsx index 496aef8edc..3656723f79 100644 --- a/packages/client-core/src/admin/adminRoutes.tsx +++ b/packages/client-core/src/admin/adminRoutes.tsx @@ -40,7 +40,7 @@ import '@etherealengine/engine/src/EngineModule' const $allowed = lazy(() => import('@etherealengine/client-core/src/admin/allowedRoutes')) const AdminRoutes = () => { - const location = useLocation() + const _location = useLocation() const admin = useHookstate(getMutableState(AuthState)).user const allowedRoutes = useHookstate(getMutableState(AllowedAdminRoutesState)) @@ -70,7 +70,7 @@ const AdminRoutes = () => { return ( - }> + }> } /> {} />} diff --git a/packages/client-core/src/admin/components/Analytics/index.tsx b/packages/client-core/src/admin/components/Analytics/index.tsx index eb29cb8584..155cfa3f1d 100755 --- a/packages/client-core/src/admin/components/Analytics/index.tsx +++ b/packages/client-core/src/admin/components/Analytics/index.tsx @@ -95,8 +95,13 @@ const Analytics = () => { return ( <>
- {analyticsServiceQueries.map((query) => ( - + {analyticsServiceQueries.map((query, index) => ( + ))}
diff --git a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx index 039abbd733..d1818f0550 100755 --- a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx +++ b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx @@ -144,8 +144,7 @@ const CreateInviteModal = ({ open, onClose }: Props) => { locationId.set(e.target.value) const location = adminLocations.find((location) => location.id === e.target.value) if (location && location.sceneId) { - const sceneName = location.sceneId.split('/') - AdminSceneService.fetchAdminScene(sceneName[0], sceneName[1]) + AdminSceneService.fetchAdminScene(location.sceneId) } } @@ -155,8 +154,7 @@ const CreateInviteModal = ({ open, onClose }: Props) => { if (instance) { const location = adminLocations.find((location) => location.id === instance.locationId) if (location) { - const sceneName = location.sceneId.split('/') - AdminSceneService.fetchAdminScene(sceneName[0], sceneName[1]) + AdminSceneService.fetchAdminScene(location.sceneId) } } } diff --git a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx index 1049cb6136..8f091a5624 100755 --- a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx +++ b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx @@ -202,8 +202,7 @@ const UpdateInviteModal = ({ open, onClose, invite }: Props) => { locationId.set(e.target.value) const location = await Engine.instance.api.service(locationPath).get(e.target.value) if (location && location.sceneId) { - const sceneName = location.sceneId.split('/') - AdminSceneService.fetchAdminScene(sceneName[0], sceneName[1]) + AdminSceneService.fetchAdminScene(location.sceneId) } } @@ -216,7 +215,7 @@ const UpdateInviteModal = ({ open, onClose, invite }: Props) => { if (!location) return const sceneName = location.sceneId.split('/') - AdminSceneService.fetchAdminScene(sceneName[0], sceneName[1]) + AdminSceneService.fetchAdminScene(location.sceneId) } const handleUserChange = (e) => { diff --git a/packages/client-core/src/admin/components/Location/LocationTable.tsx b/packages/client-core/src/admin/components/Location/LocationTable.tsx index e770c703dd..73dec4b9c5 100644 --- a/packages/client-core/src/admin/components/Location/LocationTable.tsx +++ b/packages/client-core/src/admin/components/Location/LocationTable.tsx @@ -112,7 +112,7 @@ const LocationTable = ({ className, search }: Props) => { el, id, name: {name}, - sceneId: {sceneId}, + sceneId: {sceneId}, maxUsersPerInstance, scene, locationType, diff --git a/packages/client-core/src/admin/services/SceneService.ts b/packages/client-core/src/admin/services/SceneService.ts index 771d74bcfb..1288f095f4 100644 --- a/packages/client-core/src/admin/services/SceneService.ts +++ b/packages/client-core/src/admin/services/SceneService.ts @@ -23,9 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { SceneData, SceneMetadata } from '@etherealengine/common/src/interfaces/SceneInterface' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' -import { SceneDataType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { SceneDataType, SceneMetadataType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { defineState, getMutableState } from '@etherealengine/hyperflux' export const SCENE_PAGE_LIMIT = 100 @@ -33,7 +32,7 @@ export const SCENE_PAGE_LIMIT = 100 export const AdminSceneState = defineState({ name: 'AdminSceneState', initial: () => ({ - scenes: [] as Array, + scenes: [] as Array, skip: 0, limit: SCENE_PAGE_LIMIT, total: 0, @@ -41,7 +40,7 @@ export const AdminSceneState = defineState({ fetched: false, updateNeeded: true, lastFetched: Date.now(), - singleScene: { scene: {} } as SceneData + singleScene: { scene: {} } as SceneDataType }) }) @@ -58,10 +57,10 @@ export const AdminSceneService = { lastFetched: Date.now() }) }, - fetchAdminScene: async (projectName: string, sceneName: string) => { + fetchAdminScene: async (sceneKey: string) => { const scene = await Engine.instance.api .service(scenePath) - .get(null, { query: { project: projectName, name: sceneName, metadataOnly: false } }) + .get(null, { query: { sceneKey: sceneKey, metadataOnly: false } }) getMutableState(AdminSceneState).merge({ singleScene: scene, retrieving: false, diff --git a/packages/client-core/src/components/World/LoadLocationScene.tsx b/packages/client-core/src/components/World/LoadLocationScene.tsx index a73824e69c..f9316be5e1 100755 --- a/packages/client-core/src/components/World/LoadLocationScene.tsx +++ b/packages/client-core/src/components/World/LoadLocationScene.tsx @@ -76,7 +76,9 @@ export const useLoadLocation = (props: { locationName: string }) => { */ useEffect(() => { if (!locationState.currentLocation.location.sceneId.value) return - const [project, scene] = locationState.currentLocation.location.sceneId.value.split('/') + const scenePath = locationState.currentLocation.location.sceneId.value.split('/') + const project = scenePath[scenePath.length - 2] + const scene = scenePath[scenePath.length - 1].replace('.scene.json', '') return SceneServices.setCurrentScene(project, scene) }, [locationState.currentLocation.location.sceneId]) } diff --git a/packages/client-core/src/world/utils.ts b/packages/client-core/src/world/utils.ts index beb8150f44..9c066326d2 100644 --- a/packages/client-core/src/world/utils.ts +++ b/packages/client-core/src/world/utils.ts @@ -24,16 +24,15 @@ Ethereal Engine. All Rights Reserved. */ import config from '@etherealengine/common/src/config' -import { SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { parseStorageProviderURLs } from '@etherealengine/engine/src/common/functions/parseSceneJSON' import { SceneState } from '@etherealengine/engine/src/ecs/classes/Scene' -import { SceneID } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { SceneID, SceneJsonType } from '@etherealengine/engine/src/schemas/projects/scene.schema' const fileServer = config.client.fileServer export const loadSceneJsonOffline = async (projectName, sceneName) => { const sceneID = `${projectName}/${sceneName}` as SceneID - const sceneData = (await (await fetch(`${fileServer}/projects/${sceneID}.scene.json`)).json()) as SceneJson + const sceneData = (await (await fetch(`${fileServer}/projects/${sceneID}.scene.json`)).json()) as SceneJsonType const hasKTX2 = await fetch(`${fileServer}/projects/${sceneID}.thumbnail.ktx2`).then((res) => res.ok) SceneState.loadScene(sceneID, { scene: parseStorageProviderURLs(sceneData), diff --git a/packages/client/src/engine.tsx b/packages/client/src/engine.tsx index 914ce4ac25..aa6ac3f3dd 100755 --- a/packages/client/src/engine.tsx +++ b/packages/client/src/engine.tsx @@ -48,12 +48,14 @@ initializeBrowser() API.createAPI() pipeLogs(Engine.instance.api) -export default function ({ children }) { +export default function ({ children, tailwind = false }) { const ref = createRef() const { t } = useTranslation() - return ( + return !tailwind ? ( }>{children} + ) : ( + children ) } diff --git a/packages/client/src/engine_tw.tsx b/packages/client/src/engine_tw.tsx deleted file mode 100644 index 999ea7e7b4..0000000000 --- a/packages/client/src/engine_tw.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { API } from '@etherealengine/client-core/src/API' -import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' -import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState' -import { initializeBrowser } from '@etherealengine/engine/src/initializeBrowser' -import { createEngine } from '@etherealengine/engine/src/initializeEngine' -import { getMutableState } from '@etherealengine/hyperflux' - -import { pipeLogs } from '@etherealengine/engine/src/common/functions/logger' -import { initializei18n } from './util' - -createEngine() -getMutableState(EngineState).publicPath.set( - // @ts-ignore - import.meta.env.BASE_URL === '/client/' ? location.origin : import.meta.env.BASE_URL!.slice(0, -1) // remove trailing '/' -) -initializei18n() -initializeBrowser() -API.createAPI() -pipeLogs(Engine.instance.api) - -export default function ({ children }) { - return children -} diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx index 72b895b518..5dc91b17a3 100755 --- a/packages/client/src/main.tsx +++ b/packages/client/src/main.tsx @@ -49,8 +49,8 @@ const App = () => { {/* @todo - these are for backwards compatibility with non tailwind pages - they will be removed eventually */} }> @@ -60,8 +60,8 @@ const App = () => { } /> }> @@ -71,8 +71,8 @@ const App = () => { } /> }> @@ -82,8 +82,8 @@ const App = () => { } /> }> @@ -94,11 +94,13 @@ const App = () => { /> {/* This will become redundant and we can embed the TailwindPage directly */} - + + + } /> diff --git a/packages/client/src/pages/_app_tw.tsx b/packages/client/src/pages/_app_tw.tsx index ff7c026904..27268f940f 100755 --- a/packages/client/src/pages/_app_tw.tsx +++ b/packages/client/src/pages/_app_tw.tsx @@ -38,13 +38,10 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { getMutableState, useHookstate } from '@etherealengine/hyperflux' import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjection' -import EngineTW from '../engine_tw' import PublicRouter, { CenteredLoadingCircle } from '../route/public_tw' import { ThemeContextProvider } from '../themes/themeContext' -import 'daisyui/dist/full.css' import { useTranslation } from 'react-i18next' -import 'tailwindcss/tailwind.css' import '../themes/base.css' import '../themes/components.css' import '../themes/utilities.css' @@ -101,19 +98,17 @@ const TailwindPage = () => { }, [notistackRef.current]) return ( - - - - - - - - + + + + + + ) } diff --git a/packages/client/src/pages/editor/editor.tsx b/packages/client/src/pages/editor/editor.tsx index e7a7603078..d04191049b 100644 --- a/packages/client/src/pages/editor/editor.tsx +++ b/packages/client/src/pages/editor/editor.tsx @@ -46,7 +46,6 @@ const EditorRouter = () => { }> - } /> } /> } /> diff --git a/packages/client/tailwind.config.js b/packages/client/tailwind.config.js index e18e89d1e2..1ee0ef19c4 100644 --- a/packages/client/tailwind.config.js +++ b/packages/client/tailwind.config.js @@ -35,7 +35,7 @@ module.exports = { themes: ['default', 'dark', 'vaporwave'], // daisyUI config (optional) styled: true, - base: true, + base: false, utils: true, logs: false, rtl: false, diff --git a/packages/common/src/interfaces/SceneInterface.ts b/packages/common/src/interfaces/SceneInterface.ts deleted file mode 100644 index b78d0cc3b2..0000000000 --- a/packages/common/src/interfaces/SceneInterface.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { EntityUUID } from './EntityUUID' - -export interface ComponentJson { - name: string - props?: Partial | T -} - -export interface EntityJson { - name: EntityUUID | string - components: Array - parent?: EntityUUID - index?: number -} - -export interface SceneJson { - entities: { [uuid: EntityUUID]: EntityJson } - root: EntityUUID - version: number -} - -export interface SceneMetadata { - name: string - thumbnailUrl: string - project: string -} - -export interface SceneData extends SceneMetadata { - scene: SceneJson -} diff --git a/packages/editor/src/components/EditorContainer.tsx b/packages/editor/src/components/EditorContainer.tsx index e797d56247..6091aeba95 100755 --- a/packages/editor/src/components/EditorContainer.tsx +++ b/packages/editor/src/components/EditorContainer.tsx @@ -198,7 +198,7 @@ const onSaveAs = async () => { if (result?.name && projectName) { await saveScene(projectName, result.name, file, abortController.signal) editorState.sceneModified.set(false) - RouterState.navigate(`/studio/${projectName}/${result.name}`) + editorState.sceneName.set(result.name) } } DialogState.setDialog(null) @@ -409,6 +409,13 @@ const EditorContainer = () => { } }) + useEffect(() => { + const sceneInParams = new URL(window.location.href).searchParams.get('scene') + if (sceneInParams) { + editorState.sceneName.set(sceneInParams) + } + }, []) + useHotkeys(`${cmdOrCtrlString}+s`, () => onSaveScene() as any) useEffect(() => { @@ -428,8 +435,18 @@ const EditorContainer = () => { useEffect(() => { if (sceneName.value) { - logger.info(`Loading scene ${sceneName.value} via given url`) + logger.info(`Loading scene ${sceneName.value}`) loadScene(sceneName.value) + + const parsed = new URL(window.location.href) + const query = parsed.searchParams + + query.set('scene', sceneName.value) + + parsed.search = query.toString() + if (typeof history.pushState !== 'undefined') { + window.history.replaceState({}, '', parsed.toString()) + } } }, [sceneName]) diff --git a/packages/editor/src/components/assets/ScenesPanel.tsx b/packages/editor/src/components/assets/ScenesPanel.tsx index 2b891c55ca..23596cd3a2 100644 --- a/packages/editor/src/components/assets/ScenesPanel.tsx +++ b/packages/editor/src/components/assets/ScenesPanel.tsx @@ -23,8 +23,6 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { RouterState } from '@etherealengine/client-core/src/common/services/RouterService' -import { SceneData } from '@etherealengine/common/src/interfaces/SceneInterface' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' import createReadableTexture from '@etherealengine/engine/src/assets/functions/createReadableTexture' import multiLogger from '@etherealengine/engine/src/common/functions/logger' @@ -39,9 +37,10 @@ import { MoreVert } from '@mui/icons-material' import { ClickAwayListener, IconButton, InputBase, Menu, MenuItem, Paper } from '@mui/material' import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' +import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' import Typography from '@etherealengine/ui/src/primitives/mui/Typography' import { TabData } from 'rc-dock' -import { deleteScene, getScenes, onNewScene, reRouteToLoadScene, renameScene } from '../../functions/sceneFunctions' +import { deleteScene, getScenes, onNewScene, renameScene, setSceneInState } from '../../functions/sceneFunctions' import { EditorState } from '../../services/EditorServices' import { DialogState } from '../dialogs/DialogState' import ErrorDialog from '../dialogs/ErrorDialog' @@ -60,13 +59,13 @@ const editorState = getMutableState(EditorState) */ export default function ScenesPanel({ loadScene, newScene }) { const { t } = useTranslation() - const [scenes, setScenes] = useState([]) + const [scenes, setScenes] = useState([]) const [isContextMenuOpen, setContextMenuOpen] = useState(false) const [isDeleteOpen, setDeleteOpen] = useState(false) const [anchorEl, setAnchorEl] = useState(null) const [newName, setNewName] = useState('') const [isRenaming, setRenaming] = useState(false) - const [activeScene, setActiveScene] = useState(null) + const [activeScene, setActiveScene] = useState(null) const editorState = useHookstate(getMutableState(EditorState)) const [scenesLoading, setScenesLoading] = useState(true) @@ -116,7 +115,7 @@ export default function ScenesPanel({ loadScene, newScene }) { await deleteScene(editorState.projectName.value, activeScene.name) if (editorState.sceneName.value === activeScene.name) { getMutableState(EngineState).sceneLoaded.set(false) - RouterState.navigate(`/studio/${editorState.projectName.value}`) + editorState.sceneName.set(null) } fetchItems() @@ -154,7 +153,6 @@ export default function ScenesPanel({ loadScene, newScene }) { const finishRenaming = async () => { setRenaming(false) await renameScene(editorState.projectName.value as string, newName, activeScene!.name) - RouterState.navigate(`/studio/${editorState.projectName.value}/${newName}`) setNewName('') fetchItems() } @@ -265,5 +263,5 @@ export const ScenePanelTab: TabData = { Scenes ), - content: + content: } diff --git a/packages/editor/src/components/assets/styles.module.scss b/packages/editor/src/components/assets/styles.module.scss index b8ab305eb7..c6f7413e6d 100755 --- a/packages/editor/src/components/assets/styles.module.scss +++ b/packages/editor/src/components/assets/styles.module.scss @@ -230,7 +230,7 @@ .breadcrumb { color: var(--textColor); - [class*=MuiLink-root] { + [class*="MuiLink-root"] { cursor: pointer; } } diff --git a/packages/editor/src/functions/EditorControlFunctions.ts b/packages/editor/src/functions/EditorControlFunctions.ts index 35d8320669..db572c531c 100644 --- a/packages/editor/src/functions/EditorControlFunctions.ts +++ b/packages/editor/src/functions/EditorControlFunctions.ts @@ -60,10 +60,10 @@ import { } from '@etherealengine/engine/src/transform/components/TransformComponent' import { dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' -import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { getNestedObject } from '@etherealengine/common/src/utils/getNestedProperty' import { SceneObjectComponent } from '@etherealengine/engine/src/scene/components/SceneObjectComponent' import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent' +import { ComponentJsonType } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { computeLocalTransformMatrix, computeTransformMatrix @@ -206,7 +206,7 @@ const modifyMaterial = (nodes: string[], materialId: string, properties: { [_: s } const createObjectFromSceneElement = ( - componentJson: ComponentJson[] = [], + componentJson: ComponentJsonType[] = [], parentEntity?: Entity, beforeEntity?: Entity, updateSelection = true diff --git a/packages/editor/src/functions/addMediaNode.ts b/packages/editor/src/functions/addMediaNode.ts index 87fa7e5a55..c156404187 100644 --- a/packages/editor/src/functions/addMediaNode.ts +++ b/packages/editor/src/functions/addMediaNode.ts @@ -32,7 +32,7 @@ import { ModelComponent } from '@etherealengine/engine/src/scene/components/Mode import { VideoComponent } from '@etherealengine/engine/src/scene/components/VideoComponent' import { VolumetricComponent } from '@etherealengine/engine/src/scene/components/VolumetricComponent' -import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface' +import { ComponentJsonType } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { EditorControlFunctions } from './EditorControlFunctions' /** @@ -46,7 +46,7 @@ export async function addMediaNode( url: string, parent?: Entity, before?: Entity, - extraComponentJson: ComponentJson[] = [] + extraComponentJson: ComponentJsonType[] = [] ) { const contentType = (await getContentType(url)) || '' const { hostname } = new URL(url) diff --git a/packages/editor/src/functions/sceneFunctions.tsx b/packages/editor/src/functions/sceneFunctions.tsx index 79efb1aefc..9a963bfff9 100644 --- a/packages/editor/src/functions/sceneFunctions.tsx +++ b/packages/editor/src/functions/sceneFunctions.tsx @@ -25,21 +25,18 @@ Ethereal Engine. All Rights Reserved. import i18n from 'i18next' -import { API } from '@etherealengine/client-core/src/API' -import { RouterState } from '@etherealengine/client-core/src/common/services/RouterService' import { uploadToFeathersService } from '@etherealengine/client-core/src/util/upload' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { SceneData } from '@etherealengine/common/src/interfaces/SceneInterface' import multiLogger from '@etherealengine/engine/src/common/functions/logger' +import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { SceneState } from '@etherealengine/engine/src/ecs/classes/Scene' import { getComponent, hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { iterateEntityNode } from '@etherealengine/engine/src/ecs/functions/EntityTree' import { GLTFLoadedComponent } from '@etherealengine/engine/src/scene/components/GLTFLoadedComponent' import { UUIDComponent } from '@etherealengine/engine/src/scene/components/UUIDComponent' -import { sceneDataPath } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' import { sceneUploadPath } from '@etherealengine/engine/src/schemas/projects/scene-upload.schema' -import { SceneID, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' -import { getState } from '@etherealengine/hyperflux' +import { SceneDataType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { getMutableState, getState } from '@etherealengine/hyperflux' import { EditorState } from '../services/EditorServices' const logger = multiLogger.child({ component: 'editor:sceneFunctions' }) @@ -49,12 +46,12 @@ const logger = multiLogger.child({ component: 'editor:sceneFunctions' }) * * @return {Promise} */ -export const getScenes = async (projectName: string): Promise => { +export const getScenes = async (projectName: string): Promise => { try { - const result = await API.instance.client - .service(sceneDataPath) - .get(null, { query: { projectName, metadataOnly: true } }) - return result?.data + const result = (await Engine.instance.api + .service(scenePath) + .find({ query: { project: projectName, metadataOnly: true, paginate: false } })) as SceneDataType[] + return result } catch (error) { logger.error(error, 'Error in getting project getScenes()') throw error @@ -67,9 +64,9 @@ export const getScenes = async (projectName: string): Promise => { * @param projectId * @returns */ -export const getScene = async (projectName: string, sceneName: string, metadataOnly = true): Promise => { +export const getScene = async (projectName: string, sceneName: string, metadataOnly = true): Promise => { try { - return await API.instance.client + return await Engine.instance.api .service(scenePath) .get(null, { query: { project: projectName, name: sceneName, metadataOnly: metadataOnly } }) } catch (error) { @@ -86,7 +83,7 @@ export const getScene = async (projectName: string, sceneName: string, metadataO */ export const deleteScene = async (projectName, sceneName): Promise => { try { - await API.instance.client.service(scenePath).remove(null, { query: { project: projectName, name: sceneName } }) + await Engine.instance.api.service(scenePath).remove(null, { query: { project: projectName, name: sceneName } }) } catch (error) { logger.error(error, 'Error in deleting project') throw error @@ -96,7 +93,7 @@ export const deleteScene = async (projectName, sceneName): Promise => { export const renameScene = async (projectName: string, newSceneName: string, oldSceneName: string): Promise => { try { - await API.instance.client.service(scenePath).patch(null, { newSceneName, oldSceneName, project: projectName }) + await Engine.instance.api.service(scenePath).patch(null, { newSceneName, oldSceneName, project: projectName }) } catch (error) { logger.error(error, 'Error in renaming project') throw error @@ -148,11 +145,11 @@ export const saveScene = async ( } } -export const reRouteToLoadScene = async (newSceneName: string) => { +export const setSceneInState = async (newSceneName: string) => { const { projectName, sceneName } = getState(EditorState) if (sceneName === newSceneName) return if (!projectName || !newSceneName) return - RouterState.navigate(`/studio/${projectName}/${newSceneName}`) + getMutableState(EditorState).sceneName.set(newSceneName) } export const onNewScene = async () => { @@ -163,7 +160,7 @@ export const onNewScene = async () => { const sceneData = await createNewScene(projectName) if (!sceneData) return - reRouteToLoadScene(sceneData.name) + setSceneInState(sceneData.name) } catch (error) { logger.error(error) } @@ -171,7 +168,7 @@ export const onNewScene = async () => { export const createNewScene = async (projectName: string) => { try { - return API.instance.client.service(scenePath).create({ project: projectName }) + return Engine.instance.api.service(scenePath).create({ project: projectName }) } catch (error) { logger.error(error, 'Error in creating project') throw error diff --git a/packages/editor/src/pages/EditorPage.tsx b/packages/editor/src/pages/EditorPage.tsx index 993492cca5..d15112e5a6 100644 --- a/packages/editor/src/pages/EditorPage.tsx +++ b/packages/editor/src/pages/EditorPage.tsx @@ -65,8 +65,8 @@ export const EditorPage = () => { const editorState = useHookstate(getMutableState(EditorState)) useEffect(() => { - const { projectName, sceneName } = params - getMutableState(EditorState).merge({ projectName: projectName ?? null, sceneName: sceneName ?? null }) + const { projectName } = params + getMutableState(EditorState).merge({ projectName: projectName ?? null }) }, [params]) return <>{projectState.projects.value.length && editorState.projectName.value && } diff --git a/packages/engine/src/assets/loaders/gltf/extensions/EEECSImporterExtension.ts b/packages/engine/src/assets/loaders/gltf/extensions/EEECSImporterExtension.ts index 71505f1118..d38b85c91a 100644 --- a/packages/engine/src/assets/loaders/gltf/extensions/EEECSImporterExtension.ts +++ b/packages/engine/src/assets/loaders/gltf/extensions/EEECSImporterExtension.ts @@ -23,8 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { ComponentJSONIDMap, componentJsonDefaults } from '../../../../ecs/functions/ComponentFunctions' +import { ComponentJsonType } from '../../../../schemas/projects/scene.schema' import { GLTFJson } from '../../../constants/GLTF' import { GLTFLoaderPlugin } from '../GLTFLoader' import { ImporterExtension } from './ImporterExtension' @@ -51,14 +51,14 @@ export default class EEECSImporterExtension extends ImporterExtension implements // CURRENT ECS EXTENSION FORMAT // const ecsExtensions: Record = nodeDef.extensions ?? {} - const componentJson: ComponentJson[] = [] + const componentJson: ComponentJsonType[] = [] for (const extensionName of Object.keys(ecsExtensions)) { const jsonID = /^EE_(.*)$/.exec(extensionName)?.[1] if (!jsonID) continue const component = ComponentJSONIDMap.get(jsonID) if (!component) continue const compData = ecsExtensions[extensionName] - const parsedComponent: ComponentJson = { + const parsedComponent: ComponentJsonType = { name: jsonID, props: { ...componentJsonDefaults(component), diff --git a/packages/engine/src/behave-graph/nodes/Profiles/Engine/helper/entityHelper.ts b/packages/engine/src/behave-graph/nodes/Profiles/Engine/helper/entityHelper.ts index 70ec76afcf..92c0ea55d7 100644 --- a/packages/engine/src/behave-graph/nodes/Profiles/Engine/helper/entityHelper.ts +++ b/packages/engine/src/behave-graph/nodes/Profiles/Engine/helper/entityHelper.ts @@ -24,7 +24,6 @@ Ethereal Engine. All Rights Reserved. */ import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { MathUtils } from 'three' import { Entity } from '../../../../../ecs/classes/Entity' import { SceneState } from '../../../../../ecs/classes/Scene' @@ -38,10 +37,11 @@ import { createEntity } from '../../../../../ecs/functions/EntityFunctions' import { EntityTreeComponent } from '../../../../../ecs/functions/EntityTree' import { UUIDComponent } from '../../../../../scene/components/UUIDComponent' import { VisibleComponent } from '../../../../../scene/components/VisibleComponent' +import { ComponentJsonType } from '../../../../../schemas/projects/scene.schema' import { LocalTransformComponent } from '../../../../../transform/components/TransformComponent' export const addEntityToScene = ( - componentJson: Array, + componentJson: Array, parentEntity = SceneState.getRootEntity(), beforeEntity = null as Entity | null ) => { diff --git a/packages/engine/src/ecs/classes/Scene.ts b/packages/engine/src/ecs/classes/Scene.ts index 965af661ae..ba7b3dd58d 100644 --- a/packages/engine/src/ecs/classes/Scene.ts +++ b/packages/engine/src/ecs/classes/Scene.ts @@ -38,11 +38,10 @@ import { } from '@etherealengine/hyperflux' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { SceneData, SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { useEffect } from 'react' import { Validator, matches } from '../../common/functions/MatchesUtils' import { UUIDComponent } from '../../scene/components/UUIDComponent' -import { SceneDataType, SceneID, scenePath } from '../../schemas/projects/scene.schema' +import { SceneDataType, SceneID, SceneJsonType, scenePath } from '../../schemas/projects/scene.schema' import { PresentationSystemGroup } from '../functions/EngineFunctions' import { defineSystem } from '../functions/SystemFunctions' import { Engine } from './Engine' @@ -158,7 +157,6 @@ export const SceneServices = { .service(scenePath) .get(null, { query: { project: projectName, name: sceneName } }) .then((sceneData) => { - /**@todo replace projectName/sceneName with sceneID once #9119 */ SceneState.loadScene(`${projectName}/${sceneName}` as SceneID, sceneData) }) @@ -193,7 +191,7 @@ export class SceneSnapshotAction { static appendSnapshot = defineAction({ type: 'ee.scene.snapshot.APPEND_SNAPSHOT' as const, sceneID: matches.string as Validator, - json: matches.object as Validator + json: matches.object as Validator // $topic: EditorTopic, // $cache: true }) @@ -202,7 +200,7 @@ export class SceneSnapshotAction { type: 'ee.scene.snapshot.CREATE_SNAPSHOT' as const, sceneID: matches.string as Validator, selectedEntities: matches.array as Validator>, - data: matches.object as Validator + data: matches.object as Validator }) } diff --git a/packages/engine/src/scene/functions/GLTFConversion.ts b/packages/engine/src/scene/functions/GLTFConversion.ts index 293efd0695..54a752c9cd 100644 --- a/packages/engine/src/scene/functions/GLTFConversion.ts +++ b/packages/engine/src/scene/functions/GLTFConversion.ts @@ -27,11 +27,11 @@ import { MathUtils, Object3D } from 'three' import config from '@etherealengine/common/src/config' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { EntityJson, SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { sceneRelativePathIdentifier } from '../../common/functions/parseSceneJSON' +import { EntityJsonType, SceneJsonType } from '../../schemas/projects/scene.schema' -export const nodeToEntityJson = (node: any): EntityJson => { +export const nodeToEntityJson = (node: any): EntityJsonType => { const parentId = node.extras?.parent ? { parent: node.extras.parent } : {} const uuid = node.extras?.uuid ? node.extras.uuid : MathUtils.generateUUID() return { @@ -45,11 +45,11 @@ export const nodeToEntityJson = (node: any): EntityJson => { } } -export const gltfToSceneJson = (gltf: any): SceneJson => { +export const gltfToSceneJson = (gltf: any): SceneJsonType => { handleScenePaths(gltf, 'decode') const rootGL = gltf.scenes[gltf.scene] const rootUuid = MathUtils.generateUUID() as EntityUUID - const result: SceneJson = { + const result: SceneJsonType = { entities: {}, root: rootUuid, version: 2.0 diff --git a/packages/engine/src/scene/functions/loadGLTFModel.ts b/packages/engine/src/scene/functions/loadGLTFModel.ts index 72d6e18d87..29d1191048 100644 --- a/packages/engine/src/scene/functions/loadGLTFModel.ts +++ b/packages/engine/src/scene/functions/loadGLTFModel.ts @@ -26,8 +26,6 @@ Ethereal Engine. All Rights Reserved. import { AnimationMixer, InstancedMesh, Mesh, Object3D } from 'three' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' - -import { ComponentJson, EntityJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { AnimationComponent } from '../../avatar/components/AnimationComponent' import { Entity } from '../../ecs/classes/Entity' import { @@ -41,6 +39,7 @@ import { } from '../../ecs/functions/ComponentFunctions' import { createEntity } from '../../ecs/functions/EntityFunctions' import { EntityTreeComponent } from '../../ecs/functions/EntityTree' +import { ComponentJsonType, EntityJsonType } from '../../schemas/projects/scene.schema' import { LocalTransformComponent, TransformComponent } from '../../transform/components/TransformComponent' import { GLTFLoadedComponent } from '../components/GLTFLoadedComponent' import { GroupComponent, Object3DWithEntity, addObjectToGroup } from '../components/GroupComponent' @@ -62,7 +61,7 @@ declare module 'three/src/core/Object3D' { } } -export const parseECSData = (entity: Entity, data: [string, any][]): ComponentJson[] => { +export const parseECSData = (entity: Entity, data: [string, any][]): ComponentJsonType[] => { const components: { [key: string]: any } = {} const prefabs: { [key: string]: any } = {} @@ -85,7 +84,7 @@ export const parseECSData = (entity: Entity, data: [string, any][]): ComponentJs } } - const result: ComponentJson[] = [] + const result: ComponentJsonType[] = [] for (const [key, value] of Object.entries(components)) { const component = ComponentMap.get(key) if (typeof component === 'undefined') { @@ -109,14 +108,14 @@ export const parseECSData = (entity: Entity, data: [string, any][]): ComponentJs return result } -export const createObjectEntityFromGLTF = (entity: Entity, obj3d: Object3D): ComponentJson[] => { +export const createObjectEntityFromGLTF = (entity: Entity, obj3d: Object3D): ComponentJsonType[] => { return parseECSData(entity, Object.entries(obj3d.userData)) } export const parseObjectComponentsFromGLTF = ( entity: Entity, object3d?: Object3D -): { entities: Entity[]; entityJson: Record } => { +): { entities: Entity[]; entityJson: Record } => { const scene = object3d ?? getComponent(entity, ModelComponent).scene const meshesToProcess: Mesh[] = [] @@ -129,7 +128,7 @@ export const parseObjectComponentsFromGLTF = ( }) const entities: Entity[] = [] - const entityJson: Record = {} + const entityJson: Record = {} for (const mesh of meshesToProcess) { const e = createEntity() @@ -139,7 +138,7 @@ export const parseObjectComponentsFromGLTF = ( const uuid = mesh.uuid as EntityUUID setComponent(e, UUIDComponent, uuid) const parentUuid = getComponent(entity, UUIDComponent) - const eJson: EntityJson = { + const eJson: EntityJsonType = { name, components: [], parent: parentUuid @@ -209,7 +208,7 @@ export const parseGLTFModel = (entity: Entity) => { const uuid = obj.uuid as EntityUUID const name = obj.userData['xrengine.entity'] ?? obj.name - const eJson: EntityJson = { + const eJson: EntityJsonType = { name, components: [], parent: getComponent(parentEntity, UUIDComponent) diff --git a/packages/engine/src/scene/functions/serializeWorld.ts b/packages/engine/src/scene/functions/serializeWorld.ts index e3eae382af..1c03813263 100644 --- a/packages/engine/src/scene/functions/serializeWorld.ts +++ b/packages/engine/src/scene/functions/serializeWorld.ts @@ -23,16 +23,15 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { ComponentJson } from '@etherealengine/common/src/interfaces/SceneInterface' - import { Entity } from '../../ecs/classes/Entity' import { getAllComponents, getOptionalComponent, serializeComponent } from '../../ecs/functions/ComponentFunctions' +import { ComponentJsonType } from '../../schemas/projects/scene.schema' import { GLTFLoadedComponent } from '../components/GLTFLoadedComponent' export const serializeEntity = (entity: Entity) => { const ignoreComponents = getOptionalComponent(entity, GLTFLoadedComponent) - const jsonComponents = [] as ComponentJson[] + const jsonComponents = [] as ComponentJsonType[] const components = getAllComponents(entity) for (const component of components) { diff --git a/packages/engine/src/scene/systems/SceneLoadingSystem.test.ts b/packages/engine/src/scene/systems/SceneLoadingSystem.test.ts index ac20557729..e2dbe7f0fb 100644 --- a/packages/engine/src/scene/systems/SceneLoadingSystem.test.ts +++ b/packages/engine/src/scene/systems/SceneLoadingSystem.test.ts @@ -24,10 +24,10 @@ Ethereal Engine. All Rights Reserved. */ import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { SceneData, SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { destroyEngine } from '../../ecs/classes/Engine' import { createEngine } from '../../initializeEngine' +import { SceneDataType, SceneJsonType } from '../../schemas/projects/scene.schema' const sceneJSON_1 = { scene: { @@ -39,8 +39,8 @@ const sceneJSON_1 = { }, root: 'root' as EntityUUID, version: 0 - } as SceneJson -} as SceneData + } as SceneJsonType +} as SceneDataType const sceneJSON_2 = { scene: { @@ -57,8 +57,8 @@ const sceneJSON_2 = { }, root: 'root' as EntityUUID, version: 0 - } as SceneJson -} as SceneData + } as SceneJsonType +} as SceneDataType describe('SceneLoadingSystem', () => { beforeEach(() => { diff --git a/packages/engine/src/schemas/projects/scene.schema.ts b/packages/engine/src/schemas/projects/scene.schema.ts index f3a32b05b9..e2c1c3e4cb 100644 --- a/packages/engine/src/schemas/projects/scene.schema.ts +++ b/packages/engine/src/schemas/projects/scene.schema.ts @@ -101,7 +101,9 @@ export const sceneCreateDataSchema = Type.Object( sceneData: Type.Optional(Type.Ref(sceneJsonSchema)), thumbnailBuffer: Type.Optional(Type.Any()), storageProviderName: Type.Optional(Type.String()), - project: Type.Optional(Type.String()) + project: Type.Optional(Type.String()), + directory: Type.Optional(Type.String()), + localDirectory: Type.Optional(Type.String()) }, { $id: 'SceneCreateData', additionalProperties: false } ) @@ -134,7 +136,9 @@ export const scenePatchSchema = Type.Object( newSceneName: Type.Optional(Type.String()), oldSceneName: Type.Optional(Type.String()), storageProviderName: Type.Optional(Type.String()), - project: Type.Optional(Type.String()) + project: Type.Optional(Type.String()), + directory: Type.Optional(Type.String()), + localDirectory: Type.Optional(Type.String()) }, { $id: 'ScenePatch' @@ -153,7 +157,11 @@ export const sceneQuerySchema = Type.Intersect( { storageProviderName: Type.Optional(Type.String()), metadataOnly: Type.Optional(Type.Boolean()), - paginate: Type.Optional(Type.Boolean()) + internal: Type.Optional(Type.Boolean()), + paginate: Type.Optional(Type.Boolean()), + sceneKey: Type.Optional(Type.String()), + directory: Type.Optional(Type.String()), + localDirectory: Type.Optional(Type.String()) }, { additionalProperties: false } ) diff --git a/packages/engine/src/xrui/functions/createXRUI.tsx b/packages/engine/src/xrui/functions/createXRUI.tsx index 47ec1a522c..5df930ca67 100644 --- a/packages/engine/src/xrui/functions/createXRUI.tsx +++ b/packages/engine/src/xrui/functions/createXRUI.tsx @@ -34,7 +34,7 @@ import { WebLayerManager } from '@etherealengine/xrui/core/three/WebLayerManager import { isClient } from '../../common/functions/getEnvironment' import { Entity } from '../../ecs/classes/Entity' import { setComponent } from '../../ecs/functions/ComponentFunctions' -import { createEntity } from '../../ecs/functions/EntityFunctions' +import { createEntity, EntityContext } from '../../ecs/functions/EntityFunctions' import { InputComponent } from '../../input/components/InputComponent' import { addObjectToGroup } from '../../scene/components/GroupComponent' import { VisibleComponent } from '../../scene/components/VisibleComponent' @@ -60,9 +60,13 @@ export function createXRUI | null>( const rootElement = createRoot(containerElement!) rootElement.render( //@ts-ignore - - - + + {/* + // @ts-ignore */} + + + + ) const container = new WebContainer3D(containerElement, { manager: WebLayerManager.instance }) diff --git a/packages/projects/createLocations.ts b/packages/projects/createLocations.ts index 20339f9559..1c1144eb25 100644 --- a/packages/projects/createLocations.ts +++ b/packages/projects/createLocations.ts @@ -70,7 +70,7 @@ export const createLocations = async (app: Application, projectName: string) => name: locationName, slugifiedName: sceneName, maxUsersPerInstance: 30, - sceneId: `${projectName}/${sceneName}` as SceneID, + sceneId: `projects/${projectName}/${sceneName}.scene.json` as SceneID, locationSetting, isLobby: false, isFeatured: false diff --git a/packages/projects/loadSystemInjection.ts b/packages/projects/loadSystemInjection.ts index 607e953753..6d7d55689f 100644 --- a/packages/projects/loadSystemInjection.ts +++ b/packages/projects/loadSystemInjection.ts @@ -25,17 +25,17 @@ Ethereal Engine. All Rights Reserved. import config from '@etherealengine/common/src/config' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import type { SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { ComponentType } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { SystemDefinitions, SystemUUID } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' import { SystemComponent } from '@etherealengine/engine/src/scene/components/SystemComponent' +import { SceneJsonType } from '@etherealengine/engine/src/schemas/projects/scene.schema' export type SystemImportType = { systemUUID: SystemUUID entityUUID: EntityUUID } -export const getSystemsFromSceneData = (project: string, sceneData: SceneJson): Promise => { +export const getSystemsFromSceneData = (project: string, sceneData: SceneJsonType): Promise => { const systems = [] as ReturnType[] for (const [uuid, entity] of Object.entries(sceneData.entities)) { for (const component of entity.components) { diff --git a/packages/projects/makeSceneUUIDsUnique.ts b/packages/projects/makeSceneUUIDsUnique.ts index 70e5a3cff9..937a7317f1 100644 --- a/packages/projects/makeSceneUUIDsUnique.ts +++ b/packages/projects/makeSceneUUIDsUnique.ts @@ -29,7 +29,7 @@ import path from 'path' import { v4 } from 'uuid' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' -import { SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' +import { SceneJsonType } from '@etherealengine/engine/src/schemas/projects/scene.schema' for (const project of fs.readdirSync(path.resolve(appRootPath.path, 'packages/projects/projects/'))) { const files = fs.readdirSync(path.resolve(appRootPath.path, 'packages/projects/projects/', project)) @@ -38,7 +38,7 @@ for (const project of fs.readdirSync(path.resolve(appRootPath.path, 'packages/pr const uuidMapping = {} as { [uuid: string]: string } const sceneJson = JSON.parse( fs.readFileSync(path.resolve(appRootPath.path, 'packages/projects/projects/', project, scene)).toString() - ) as SceneJson + ) as SceneJsonType for (const uuid of Object.keys(sceneJson.entities)) { uuidMapping[uuid] = v4() } diff --git a/packages/server-core/src/projects/portal/portal.class.ts b/packages/server-core/src/projects/portal/portal.class.ts index 4954775226..91fcdf671a 100644 --- a/packages/server-core/src/projects/portal/portal.class.ts +++ b/packages/server-core/src/projects/portal/portal.class.ts @@ -21,8 +21,7 @@ Ethereal Engine. All Rights Reserved. import { Application } from '../../../declarations' import { PortalQuery, PortalType } from '@etherealengine/engine/src/schemas/projects/portal.schema' -import { sceneDataPath } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' -import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { SceneDataType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import { Paginated, Params, ServiceInterface } from '@feathersjs/feathers' import { getSceneData } from '../scene/scene-helper' @@ -51,9 +50,7 @@ export class PortalService implements ServiceInterface portal.portalEntityId === id) || ({} as PortalType) @@ -65,9 +62,7 @@ export class PortalService implements ServiceInterface parseScenePortals(scene)).flat() as PortalType[] return paginate === false ? sceneResult : { data: sceneResult, total: sceneResult.length, limit: 0, skip: 0 } } diff --git a/packages/server-core/src/projects/scene-data/scene-data-helper.ts b/packages/server-core/src/projects/scene-data/scene-data-helper.ts deleted file mode 100644 index ce6ac6880f..0000000000 --- a/packages/server-core/src/projects/scene-data/scene-data-helper.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema' -import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' -import { Application, Paginated } from '@feathersjs/feathers' -import logger from '../../ServerLogger' -import { getStorageProvider } from '../../media/storageprovider/storageprovider' -import { getSceneData } from '../scene/scene-helper' -import { SceneDataParams } from './scene-data.class' - -export const getScenesForProject = async (app: Application, params?: SceneDataParams) => { - const storageProvider = getStorageProvider(params?.query?.storageProviderName) - const projectName = params?.query?.projectName - const metadataOnly = params?.query?.metadataOnly - const internal = params?.query?.internal - try { - const project = (await app - .service(projectPath) - .find({ ...params, query: { name: projectName, $limit: 1 } })) as Paginated - if (project.data.length === 0) throw new Error(`No project named ${projectName} exists`) - - const newSceneJsonPath = `projects/${projectName}/` - - const fileResults = await storageProvider.listObjects(newSceneJsonPath, false) - const files = fileResults.Contents.map((dirent) => dirent.Key) - .filter((name) => name.endsWith('.scene.json')) - .map((name) => name.slice(0, -'.scene.json'.length)) - - const sceneData: SceneDataType[] = await Promise.all( - files.map(async (sceneName) => - getSceneData(projectName!, sceneName.replace(newSceneJsonPath, ''), metadataOnly, internal) - ) - ) - - return { data: sceneData } - } catch (e) { - logger.error(e) - return { data: [] as SceneDataType[] } - } -} diff --git a/packages/server-core/src/projects/scene-data/scene-data.class.ts b/packages/server-core/src/projects/scene-data/scene-data.class.ts deleted file mode 100644 index bae957af25..0000000000 --- a/packages/server-core/src/projects/scene-data/scene-data.class.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* -CPAL-1.0 License -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. -The Original Code is Ethereal Engine. -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { Application } from '../../../declarations' - -import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema' -import { SceneDataQuery, SceneDataServiceType } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' -import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' -import { NullableId, Paginated, Params, ServiceInterface } from '@feathersjs/feathers' -import { getScenesForProject } from './scene-data-helper' - -export interface SceneDataParams extends Params, SceneDataQuery { - paginate?: false -} - -export class SceneDataService - implements ServiceInterface, SceneDataParams> -{ - app: Application - - constructor(app: Application) { - this.app = app - } - - async get(id: NullableId, params?: SceneDataParams) { - return getScenesForProject(this.app, params) - } - - async find(params?: SceneDataParams) { - const paginate = params?.paginate === false || params?.query?.paginate === false ? false : undefined - delete params?.paginate - delete params?.query?.paginate - - const projects = (await this.app - .service(projectPath) - .find({ ...params, query: { name: params?.query?.projectName }, paginate: false })) as any as ProjectType[] - const scenes = await Promise.all( - projects.map( - (project) => - new Promise(async (resolve) => { - const projectScenes = ( - await getScenesForProject(this.app, { - ...params, - query: { - ...params?.query, - projectName: project.name, - metadataOnly: params?.query?.metadataOnly, - internal: params?.provider == null - } - }) - ).data - projectScenes.forEach((scene) => (scene.project = project.name)) - resolve(projectScenes) - }) - ) - ) - - return paginate === false ? scenes.flat() : { total: scenes.flat().length, limit: 0, skip: 0, data: scenes.flat() } - } -} diff --git a/packages/server-core/src/projects/scene-data/scene-data.docs.ts b/packages/server-core/src/projects/scene-data/scene-data.docs.ts deleted file mode 100644 index d9226bd348..0000000000 --- a/packages/server-core/src/projects/scene-data/scene-data.docs.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { sceneDataServiceSchema } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' -import { createSwaggerServiceOptions } from 'feathers-swagger' - -export default createSwaggerServiceOptions({ - schemas: { sceneDataServiceSchema }, - docs: { - description: 'Scene data service description', - securities: ['all'] - } -}) diff --git a/packages/server-core/src/projects/scene-data/scene-data.hooks.ts b/packages/server-core/src/projects/scene-data/scene-data.hooks.ts deleted file mode 100644 index 47f9919950..0000000000 --- a/packages/server-core/src/projects/scene-data/scene-data.hooks.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* -CPAL-1.0 License -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. -The Original Code is Ethereal Engine. -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -export default { - around: { - all: [] - }, - - before: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - after: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -} as any diff --git a/packages/server-core/src/projects/scene-data/scene-data.ts b/packages/server-core/src/projects/scene-data/scene-data.ts deleted file mode 100644 index 303f1c2cda..0000000000 --- a/packages/server-core/src/projects/scene-data/scene-data.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { sceneDataMethods, sceneDataPath } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' -import { Application } from '../../../declarations' -import { SceneDataService } from './scene-data.class' -import sceneDataDocs from './scene-data.docs' -import hooks from './scene-data.hooks' - -declare module '@etherealengine/common/declarations' { - interface ServiceTypes { - [sceneDataPath]: SceneDataService - } -} - -export default (app: Application): void => { - app.use(sceneDataPath, new SceneDataService(app), { - // A list of all methods this service exposes externally - methods: sceneDataMethods, - // You can add additional custom events to be sent to clients here - events: [], - docs: sceneDataDocs - }) - - const service = app.service(sceneDataPath) - service.hooks(hooks) -} diff --git a/packages/server-core/src/projects/scene/scene-helper.ts b/packages/server-core/src/projects/scene/scene-helper.ts index 998ebb513b..2e1648274a 100644 --- a/packages/server-core/src/projects/scene/scene-helper.ts +++ b/packages/server-core/src/projects/scene/scene-helper.ts @@ -25,11 +25,10 @@ Ethereal Engine. All Rights Reserved. import koa from '@feathersjs/koa' -import { SceneData } from '@etherealengine/common/src/interfaces/SceneInterface' - import { Application } from '../../../declarations' // import { addVolumetricAssetFromProject } from '../../media/volumetric/volumetric-upload.helper' import { parseStorageProviderURLs } from '@etherealengine/engine/src/common/functions/parseSceneJSON' +import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { getCacheDomain } from '../../media/storageprovider/getCacheDomain' import { getCachedURL } from '../../media/storageprovider/getCachedURL' import { getStorageProvider } from '../../media/storageprovider/storageprovider' @@ -45,32 +44,34 @@ export const getEnvMapBake = (app: Application) => { } export const getSceneData = async ( - projectName: string, - sceneName: string, + sceneKey: string, metadataOnly?: boolean, internal = false, storageProviderName?: string ) => { const storageProvider = getStorageProvider(storageProviderName) - const sceneExists = await storageProvider.doesExist(`${sceneName}.scene.json`, `projects/${projectName}/`) - if (!sceneExists) throw new Error(`No scene named ${sceneName} exists in project ${projectName}`) + const sceneName = sceneKey.split('/').pop()!.replace('.scene.json', '') + const directory = sceneKey.replace(`${sceneName}.scene.json`, '') + + const sceneExists = await storageProvider.doesExist(`${sceneName}.scene.json`, directory) + if (!sceneExists) throw new Error(`No scene named ${sceneName} exists in project ${directory}`) - let thumbnailPath = `projects/${projectName}/${sceneName}.thumbnail.ktx2` + let thumbnailPath = `${directory}${sceneName}.thumbnail.ktx2` //if no ktx2 is found, fallback on legacy jpg thumbnail format, if still not found, fallback on ethereal logo - if (!(await storageProvider.doesExist(`${sceneName}.thumbnail.ktx2`, `projects/${projectName}`))) { - thumbnailPath = `projects/${projectName}/${sceneName}.thumbnail.jpeg` - if (!(await storageProvider.doesExist(`${sceneName}.thumbnail.jpeg`, `projects/${projectName}`))) thumbnailPath = `` + if (!(await storageProvider.doesExist(`${sceneName}.thumbnail.ktx2`, directory))) { + thumbnailPath = `${directory}${sceneName}.thumbnail.jpeg` + if (!(await storageProvider.doesExist(`${sceneName}.thumbnail.jpeg`, directory))) thumbnailPath = `` } + const projectName = directory.split('/')[1] + const cacheDomain = getCacheDomain(storageProvider, internal) const thumbnailUrl = thumbnailPath !== `` ? getCachedURL(thumbnailPath, cacheDomain) : `/static/etherealengine_thumbnail.jpg` - const scenePath = `projects/${projectName}/${sceneName}.scene.json` - - const sceneResult = await storageProvider.getObject(scenePath) - const sceneData: SceneData = { + const sceneResult = await storageProvider.getCachedObject(sceneKey) + const sceneData: SceneDataType = { name: sceneName, project: projectName, thumbnailUrl: thumbnailUrl, diff --git a/packages/server-core/src/projects/scene/scene-parser.ts b/packages/server-core/src/projects/scene/scene-parser.ts index 7963f2850e..fc43aa4643 100644 --- a/packages/server-core/src/projects/scene/scene-parser.ts +++ b/packages/server-core/src/projects/scene/scene-parser.ts @@ -23,10 +23,10 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { SceneData } from '@etherealengine/common/src/interfaces/SceneInterface' import { PortalType } from '@etherealengine/engine/src/schemas/projects/portal.schema' +import { SceneDataType } from '@etherealengine/engine/src/schemas/projects/scene.schema' -export const parseScenePortals = (scene: SceneData) => { +export const parseScenePortals = (scene: SceneDataType) => { const portals: PortalType[] = [] for (const [entityId, entity] of Object.entries(scene.scene?.entities!)) { for (const component of entity.components) diff --git a/packages/server-core/src/projects/scene/scene.class.ts b/packages/server-core/src/projects/scene/scene.class.ts index edf9a35d9c..467f21d478 100644 --- a/packages/server-core/src/projects/scene/scene.class.ts +++ b/packages/server-core/src/projects/scene/scene.class.ts @@ -29,24 +29,25 @@ import fs from 'fs' import path from 'path' import { isDev } from '@etherealengine/common/src/config' -import { SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import defaultSceneSeed from '@etherealengine/projects/default-project/default.scene.json' import { cleanStorageProviderURLs } from '@etherealengine/engine/src/common/functions/parseSceneJSON' import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema' -import { sceneDataPath } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' import { SceneCreateData, SceneDataType, + SceneJsonType, SceneMetadataCreate, ScenePatch, SceneQuery, - SceneUpdate + SceneUpdate, + scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' import { Application } from '../../../declarations' import logger from '../../ServerLogger' import { getStorageProvider } from '../../media/storageprovider/storageprovider' import { cleanString } from '../../util/cleanString' +import { ProjectParams } from '../project/project.class' import { getSceneData } from './scene-helper' const NEW_SCENE_NAME = 'New-Scene' @@ -71,44 +72,87 @@ export class SceneService this.app = app } + async getSceneFiles(directory: string, storageProviderName?: string) { + const storageProvider = getStorageProvider(storageProviderName) + const fileResults = await storageProvider.listObjects(directory, false) + return fileResults.Contents.map((dirent) => dirent.Key) + .filter((name) => name.endsWith('.scene.json')) + .map((name) => name.split('/').pop()!.replace('.scene.json', '')) + } + async find(params?: SceneParams) { const paginate = params?.paginate === false || params?.query?.paginate === false ? false : undefined delete params?.paginate delete params?.query?.paginate - const projects = (await this.app.service(projectPath).find({ paginate: false })) as ProjectType[] + let projectParams: ProjectParams = { paginate: false } + + const projectName = params?.query?.project?.toString() + + if (projectName) { + projectParams = { ...projectParams, query: { name: projectName } } + } + + const storageProviderName = params?.query?.storageProviderName?.toString() const scenes: SceneDataType[] = [] + + const projects = (await this.app.service(projectPath).find(projectParams)) as ProjectType[] + for (const project of projects) { - const { data } = await this.app.service(sceneDataPath).get(null, { - ...params, - query: { ...params?.query, projectName: project.name, metadataOnly: true, internal: true } - }) - scenes.push( - ...data.map((d) => { - d.project = project.name - return d - }) + const sceneJsonPath = `projects/${project.name}/` + + const files = await this.getSceneFiles(sceneJsonPath, storageProviderName) + const sceneData = await Promise.all( + files.map(async (sceneName) => + this.app.service(scenePath).get('', { + ...params, + query: { + ...params?.query, + name: sceneName, + project: project.name, + metadataOnly: params?.query?.metadataOnly, + internal: params?.query?.internal + } + }) + ) ) + scenes.push(...sceneData) } - for (const [index, _] of scenes.entries()) { - scenes[index].thumbnailUrl += `?${Date.now()}` + if (projects.length === 0 && params?.query?.directory) { + const sceneJsonPath = params?.query?.directory?.toString() + const files = await this.getSceneFiles(sceneJsonPath, storageProviderName) + const sceneData = await Promise.all( + files.map(async (sceneName) => + this.app.service(scenePath).get('', { + ...params, + query: { + ...params?.query, + name: sceneName, + metadataOnly: true, + internal: true + } + }) + ) + ) + scenes.push(...sceneData) } + // for (const [index, _] of scenes.entries()) { + // scenes[index].thumbnailUrl += `?${Date.now()}` + // } + return paginate === false ? scenes : { total: scenes.length, limit: 0, skip: 0, data: scenes } } async get(id: NullableId, params?: SceneParams) { - const projectName = params?.query?.project?.toString() const metadataOnly = params?.query?.metadataOnly - const sceneName = params?.query?.name?.toString() - const project = (await this.app - .service(projectPath) - .find({ ...params, query: { name: projectName!, $limit: 1 } })) as Paginated - if (project.data.length === 0) throw new Error(`No project named ${projectName!} exists`) + const internal = params?.query?.internal + const storageProviderName = params?.query?.storageProviderName + const sceneKey = params?.query?.sceneKey?.toString() - const sceneData = await getSceneData(projectName!, sceneName!, metadataOnly, params!.provider == null) + const sceneData = await getSceneData(sceneKey!, metadataOnly, internal, storageProviderName) return sceneData as SceneDataType } @@ -120,12 +164,8 @@ export class SceneService const storageProvider = getStorageProvider(storageProviderName) - const projectResult = (await this.app - .service(projectPath) - .find({ ...params, query: { name: project, $limit: 1 } })) as Paginated - if (projectResult.data.length === 0) throw new Error(`No project named ${project} exists`) - - const projectRoutePath = `projects/${project}/` + const directory = data.directory! + const localDirectory = data.localDirectory! let newSceneName = NEW_SCENE_NAME let counter = 1 @@ -133,7 +173,7 @@ export class SceneService // eslint-disable-next-line no-constant-condition while (true) { if (counter > 1) newSceneName = NEW_SCENE_NAME + '-' + counter - if (!(await storageProvider.doesExist(`${newSceneName}.scene.json`, projectRoutePath))) break + if (!(await storageProvider.doesExist(`${newSceneName}.scene.json`, directory))) break counter++ } @@ -144,25 +184,23 @@ export class SceneService `default${ext}`, `${newSceneName}${ext}`, `projects/default-project`, - projectRoutePath, + directory, true ) ) ) try { - await storageProvider.createInvalidation( - sceneAssetFiles.map((asset) => `projects/${project}/${newSceneName}${asset}`) - ) + await storageProvider.createInvalidation(sceneAssetFiles.map((asset) => `${directory}${newSceneName}${asset}`)) } catch (e) { logger.error(e) logger.info(sceneAssetFiles) } if (isDev) { - const projectPathLocal = path.resolve(appRootPath.path, 'packages/projects/projects/' + project) + '/' + const projectPathLocal = path.resolve(appRootPath.path, localDirectory) for (const ext of sceneAssetFiles) { fs.copyFileSync( - path.resolve(appRootPath.path, `packages/projects/default-project/default${ext}`), + path.resolve(appRootPath.path, `${localDirectory}default${ext}`), path.resolve(projectPathLocal + newSceneName + ext) ) } @@ -172,46 +210,33 @@ export class SceneService } async patch(id: NullableId, data: ScenePatch, params?: Params) { - const { newSceneName, oldSceneName, project, storageProviderName } = data + const { newSceneName, oldSceneName, storageProviderName } = data const storageProvider = getStorageProvider(storageProviderName) - const projectResult = (await this.app - .service(projectPath) - .find({ ...params, query: { name: project, $limit: 1 } })) as Paginated - if (projectResult.data.length === 0) throw new Error(`No project named ${project} exists`) - - const projectRoutePath = `projects/${project}/` + const directory = data.directory! + const localDirectory = data.localDirectory! for (const ext of sceneAssetFiles) { const oldSceneJsonName = `${oldSceneName}${ext}` const newSceneJsonName = `${newSceneName}${ext}` - if (await storageProvider.doesExist(oldSceneJsonName, projectRoutePath)) { - await storageProvider.moveObject(oldSceneJsonName, newSceneJsonName, projectRoutePath, projectRoutePath) + if (await storageProvider.doesExist(oldSceneJsonName, directory)) { + await storageProvider.moveObject(oldSceneJsonName, newSceneJsonName, directory, directory) try { - await storageProvider.createInvalidation([ - projectRoutePath + oldSceneJsonName, - projectRoutePath + newSceneJsonName - ]) + await storageProvider.createInvalidation([directory + oldSceneJsonName, directory + newSceneJsonName]) } catch (e) { logger.error(e) - logger.info(projectRoutePath + oldSceneJsonName, projectRoutePath + newSceneJsonName) + logger.info(directory + oldSceneJsonName, directory + newSceneJsonName) } } } if (isDev) { for (const ext of sceneAssetFiles) { - const oldSceneJsonPath = path.resolve( - appRootPath.path, - `packages/projects/projects/${project}/${oldSceneName}${ext}` - ) + const oldSceneJsonPath = path.resolve(appRootPath.path, `${localDirectory}${oldSceneName}${ext}`) if (fs.existsSync(oldSceneJsonPath)) { - const newSceneJsonPath = path.resolve( - appRootPath.path, - `packages/projects/projects/${project}/${newSceneName}${ext}` - ) + const newSceneJsonPath = path.resolve(appRootPath.path, `${localDirectory}${newSceneName}${ext}`) fs.renameSync(oldSceneJsonPath, newSceneJsonPath) } } @@ -222,30 +247,28 @@ export class SceneService async update(id: NullableId, data: SceneCreateData, params?: Params) { try { - const { name, sceneData, thumbnailBuffer, storageProviderName, project } = data + const { name, sceneData, thumbnailBuffer, storageProviderName } = data let parsedSceneData = sceneData if (sceneData && typeof sceneData === 'string') parsedSceneData = JSON.parse(sceneData) - logger.info('[scene.update]: ', project, data) + logger.info('[scene.update]: ', data) const storageProvider = getStorageProvider(storageProviderName) - const projectResult = (await this.app - .service(projectPath) - .find({ ...params, query: { name: project }, paginate: false })) as ProjectType[] - if (projectResult.length === 0) throw new Error(`No project named ${project} exists`) + const directory = data.directory! + const localDirectory = data.localDirectory! - const newSceneJsonPath = `projects/${project}/${name}.scene.json` + const newSceneJsonPath = `${directory}${name}.scene.json` await storageProvider.putObject({ Key: newSceneJsonPath, Body: Buffer.from( - JSON.stringify(cleanStorageProviderURLs(parsedSceneData ?? (defaultSceneSeed as unknown as SceneJson))) + JSON.stringify(cleanStorageProviderURLs(parsedSceneData ?? (defaultSceneSeed as unknown as SceneJsonType))) ), ContentType: 'application/json' }) if (thumbnailBuffer && Buffer.isBuffer(thumbnailBuffer)) { - const sceneThumbnailPath = `projects/${project}/${name}.thumbnail.ktx2` + const sceneThumbnailPath = `${directory}${name}.thumbnail.ktx2` await storageProvider.putObject({ Key: sceneThumbnailPath, Body: thumbnailBuffer as Buffer, @@ -254,38 +277,34 @@ export class SceneService } try { - await storageProvider.createInvalidation(sceneAssetFiles.map((asset) => `projects/${project}/${name}${asset}`)) + await storageProvider.createInvalidation(sceneAssetFiles.map((asset) => `${directory}${name}${asset}`)) } catch (e) { logger.error(e) logger.info(sceneAssetFiles) } if (isDev) { - const newSceneJsonPathLocal = path.resolve( - appRootPath.path, - `packages/projects/projects/${project}/${name}.scene.json` - ) + const newSceneJsonPathLocal = path.resolve(appRootPath.path, `${localDirectory}${name}.scene.json`) fs.writeFileSync( path.resolve(newSceneJsonPathLocal), JSON.stringify( - cleanStorageProviderURLs(parsedSceneData ?? (defaultSceneSeed as unknown as SceneJson)), + cleanStorageProviderURLs(parsedSceneData ?? (defaultSceneSeed as unknown as SceneJsonType)), null, 2 ) ) if (thumbnailBuffer && Buffer.isBuffer(thumbnailBuffer)) { - const sceneThumbnailPath = path.resolve( - appRootPath.path, - `packages/projects/projects/${project}/${name}.thumbnail.ktx2` - ) + const sceneThumbnailPath = path.resolve(appRootPath.path, `${localDirectory}${name}.thumbnail.ktx2`) fs.writeFileSync(path.resolve(sceneThumbnailPath), thumbnailBuffer as Buffer) } } + const a = data.directory!.split('/')[1] + // return scene id for update hooks - return { id: `${project}/${name}` } + return { id: `${data.directory!.split('/')[1]}/${name}` } } catch (err) { logger.error(err) throw err @@ -300,24 +319,20 @@ export class SceneService const name = cleanString(sceneName!.toString()) - const project = (await this.app - .service(projectPath) - .find({ ...params, query: { name: projectName }, paginate: false })) as any as ProjectType[] - if (project.length === 0) throw new Error(`No project named ${projectName} exists`) + const directory = params!.query!.directory!.toString()! + const localDirectory = params!.query!.localDirectory!.toString()! for (const ext of sceneAssetFiles) { - const assetFilePath = path.resolve(appRootPath.path, `packages/projects/projects/${projectName}/${name}${ext}`) + const assetFilePath = path.resolve(appRootPath.path, `${localDirectory}/${name}${ext}`) if (fs.existsSync(assetFilePath)) { fs.rmSync(path.resolve(assetFilePath)) } } - await storageProvider.deleteResources(sceneAssetFiles.map((ext) => `projects/${projectName}/${name}${ext}`)) + await storageProvider.deleteResources(sceneAssetFiles.map((ext) => `${directory}${name}${ext}`)) try { - await storageProvider.createInvalidation( - sceneAssetFiles.map((asset) => `projects/${projectName}/${sceneName}${asset}`) - ) + await storageProvider.createInvalidation(sceneAssetFiles.map((asset) => `${directory}${sceneName}${asset}`)) } catch (e) { logger.error(e) logger.info(sceneAssetFiles) diff --git a/packages/server-core/src/projects/scene/scene.hooks.ts b/packages/server-core/src/projects/scene/scene.hooks.ts index 7d00e67a60..32b1e5da58 100755 --- a/packages/server-core/src/projects/scene/scene.hooks.ts +++ b/packages/server-core/src/projects/scene/scene.hooks.ts @@ -23,43 +23,113 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema' +import { SceneCreateData } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { BadRequest } from '@feathersjs/errors' +import { HookContext, Paginated } from '@feathersjs/feathers' import { iff, isProvider } from 'feathers-hooks-common' - +import { createSkippableHooks } from '../../hooks/createSkippableHooks' import projectPermissionAuthenticate from '../../hooks/project-permission-authenticate' import setResponseStatusCode from '../../hooks/set-response-status-code' import verifyScope from '../../hooks/verify-scope' -export default { - before: { - all: [], - find: [], - get: [], - create: [iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false))], - update: [iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false))], - patch: [iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false))], - remove: [iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false))] - }, +const checkIfProjectExists = async (context: HookContext, project: string) => { + const projectResult = (await context.app + .service(projectPath) + .find({ query: { name: project, $limit: 1 } })) as Paginated + if (projectResult.data.length === 0) throw new Error(`No project named ${project} exists`) +} - after: { - all: [], - find: [], - get: [], - create: [ - // Editor is expecting 200, while feather is sending 201 for creation - setResponseStatusCode(200) - ], - update: [], - patch: [], - remove: [] - }, +const getSceneKey = async (context: HookContext) => { + const { project, name, sceneKey } = context.params.query + if (!sceneKey) { + if (project) { + checkIfProjectExists(context, project) + } + + context.params.query = { ...context.params.query, sceneKey: `projects/${project}/${name}.scene.json` } + } +} + +const getDirectoryFromData = async (context: HookContext) => { + if (!context.data) { + throw new BadRequest(`${context.path} service data is empty`) + } - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] + if (context.method === 'create') { + const data: SceneCreateData[] = Array.isArray(context.data) ? context.data : [context.data] + + if (!data[0].directory) { + for (const item of data) { + checkIfProjectExists(context, item.project!) + item.directory = `projects/${item.project}/` + item.localDirectory = `packages/projects/projects/${item.project}/` + } + context.data = data.length === 1 ? data[0] : data + } + } else { + if (!context.data.directory) { + checkIfProjectExists(context, context.data.project) + context.data.directory = `projects/${context.data.project}/` + context.data.localDirectory = `packages/projects/projects/${context.data.project}/` + } + } +} + +const getDirectoryFromQuery = async (context: HookContext) => { + if (!context.params.query.directory) { + checkIfProjectExists(context, context.params.query.project) + context.params.query.directory = `projects/${context.params.query.project}/` + context.params.query.localDirectory = `packages/projects/projects/${context.params.query.project}/` } -} as any +} + +export default createSkippableHooks( + { + before: { + all: [], + find: [], + get: [getSceneKey], + create: [ + iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false)), + getDirectoryFromData + ], + update: [ + iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false)), + getDirectoryFromData + ], + patch: [ + iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false)), + getDirectoryFromData + ], + remove: [ + iff(isProvider('external'), verifyScope('editor', 'write'), projectPermissionAuthenticate(false)), + getDirectoryFromQuery + ] + }, + + after: { + all: [], + find: [], + get: [], + create: [ + // Editor is expecting 200, while feather is sending 201 for creation + setResponseStatusCode(200) + ], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } + }, + [] +) diff --git a/packages/server-core/src/projects/scene/scene.test.ts b/packages/server-core/src/projects/scene/scene.test.ts index 1f795a1901..933fbdeffc 100644 --- a/packages/server-core/src/projects/scene/scene.test.ts +++ b/packages/server-core/src/projects/scene/scene.test.ts @@ -23,15 +23,12 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { SceneJson } from '@etherealengine/common/src/interfaces/SceneInterface' import { parseStorageProviderURLs } from '@etherealengine/engine/src/common/functions/parseSceneJSON' import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine' import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema' -import { sceneDataPath } from '@etherealengine/engine/src/schemas/projects/scene-data.schema' import { sceneUploadPath } from '@etherealengine/engine/src/schemas/projects/scene-upload.schema' -import { SceneDataType, SceneJsonType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' +import { SceneJsonType, scenePath } from '@etherealengine/engine/src/schemas/projects/scene.schema' import defaultSceneSeed from '@etherealengine/projects/default-project/default.scene.json' -import { Paginated } from '@feathersjs/feathers' import assert from 'assert' import { v1 } from 'uuid' import { Application } from '../../../declarations' @@ -69,43 +66,6 @@ describe('scene.test', () => { await destroyEngine() }) - describe('"scene-data" service', () => { - it('should get the scene data', async () => { - const { data } = await app.service(sceneDataPath).get(null, { query: { projectName, metadataOnly: false } }) - assert.deepStrictEqual(parsedSceneData, data.find((scene) => scene.name === sceneName)!.scene) - }) - - it('should find the scene data', async () => { - const { data } = (await app - .service(sceneDataPath) - .find({ query: { projectName, metadataOnly: false } })) as Paginated - assert.deepStrictEqual(parsedSceneData, data.find((entry) => entry.name === sceneName)!.scene) - assert(data.length > 0) - data.forEach((scene) => { - assert(typeof scene.name === 'string') - assert(typeof scene.project === 'string') - assert(typeof scene.thumbnailUrl === 'string') - assert(typeof scene.scene === 'object') - }) - }) - - it('should get all scenes for a project scenes with metadata only', async function () { - const { data } = (await app.service(sceneDataPath).find({ - query: { - projectName, - metadataOnly: true - } - })) as Paginated - assert(data.length > 0) - data.forEach((scene) => { - assert(typeof scene.name === 'string') - assert(typeof scene.project === 'string') - assert(typeof scene.thumbnailUrl === 'string') - assert(typeof scene.scene === 'undefined') - }) - }) - }) - describe('"scene" service', () => { it('should get scene data', async () => { const data = await app @@ -129,7 +89,7 @@ describe('scene.test', () => { }) it('should save or update an existing scene', async () => { - const newSceneData = structuredClone(defaultSceneSeed) as unknown as SceneJson + const newSceneData = structuredClone(defaultSceneSeed) as unknown as SceneJsonType const updatedVersion = Math.floor(Math.random() * 100) newSceneData.version = updatedVersion const newParsedSceneData = parseStorageProviderURLs(structuredClone(newSceneData)) diff --git a/packages/server-core/src/projects/services.ts b/packages/server-core/src/projects/services.ts index b14c9944ac..4665c43327 100755 --- a/packages/server-core/src/projects/services.ts +++ b/packages/server-core/src/projects/services.ts @@ -37,7 +37,6 @@ import ProjectInvalidate from './project-invalidate/project-invalidate' import ProjectPermission from './project-permission/project-permission' import Project from './project/project' import Projects from './projects/projects' -import SceneData from './scene-data/scene-data' import SceneUpload from './scene-upload/scene-upload' import Scene from './scene/scene' @@ -57,6 +56,5 @@ export default [ ProjectCheckUnfetchedCommit, ProjectCheckSourceDestinationMatch, Scene, - SceneData, SceneUpload ] diff --git a/packages/server-core/src/social/location/location.seed.ts b/packages/server-core/src/social/location/location.seed.ts index cbab167856..b7bc572757 100755 --- a/packages/server-core/src/social/location/location.seed.ts +++ b/packages/server-core/src/social/location/location.seed.ts @@ -41,7 +41,7 @@ export const locationSeedData = [ name: 'Default', slugifiedName: 'default', maxUsersPerInstance: 30, - sceneId: 'default-project/default' as SceneID, + sceneId: 'projects/default-project/default.scene.json' as SceneID, isFeatured: false, isLobby: false }, @@ -50,7 +50,7 @@ export const locationSeedData = [ name: 'Sky Station', slugifiedName: 'sky-station', maxUsersPerInstance: 30, - sceneId: 'default-project/sky-station' as SceneID, + sceneId: 'projects/default-project/sky-station.scene.json' as SceneID, isFeatured: false, isLobby: false }, @@ -59,7 +59,7 @@ export const locationSeedData = [ name: 'Apartment', slugifiedName: 'apartment', maxUsersPerInstance: 30, - sceneId: 'default-project/apartment' as SceneID, + sceneId: 'projects/default-project/apartment.scene.json' as SceneID, isFeatured: false, isLobby: false } diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx index e35a7bfe9f..5cb0bca624 100644 --- a/packages/ui/.storybook/preview.tsx +++ b/packages/ui/.storybook/preview.tsx @@ -28,18 +28,15 @@ import { Preview } from '@storybook/react' import React from 'react' import { withRouter } from 'storybook-addon-react-router-v6' -import Engine_tw from '@etherealengine/client/src/engine_tw' import { ThemeContextProvider } from '@etherealengine/client/src/themes/themeContext' export const decorators = [ withRouter, (Story) => { return ( - - - - - + + + ) } ] diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js index c63aef1435..db646fb5a6 100644 --- a/packages/ui/tailwind.config.js +++ b/packages/ui/tailwind.config.js @@ -39,7 +39,7 @@ module.exports = { themes: ['default', 'dark', 'vaporwave'], // daisyUI config (optional) styled: true, - base: true, + base: false, utils: true, logs: false, rtl: false,