diff --git a/package.json b/package.json
index 9c2738dab4..a7c95c0d0c 100755
--- a/package.json
+++ b/package.json
@@ -68,6 +68,7 @@
"make-user-admin": "cross-env ts-node --swc scripts/make-user-admin.ts",
"migrate": "cd packages/server-core && npm run migrate",
"migrate:rollback": "cd packages/server-core && npm run migrate:rollback",
+ "migrate:unlock": "cd packages/server-core && npm run migrate:unlock",
"postinstall": "patch-package --exclude ./node_modules/@mui/",
"precommit": "no-master-commits -b master",
"prepare-database": "cross-env APP_ENV=production PREPARE_DATABASE=true EXIT_ON_DB_INIT=true ts-node --swc packages/server/src/index.ts",
diff --git a/packages/client-core/i18n/en/admin.json b/packages/client-core/i18n/en/admin.json
index 6584f152b0..928cb451c4 100755
--- a/packages/client-core/i18n/en/admin.json
+++ b/packages/client-core/i18n/en/admin.json
@@ -355,6 +355,7 @@
"homepageLinkButtonEnabled": "Show link button on homepage",
"homepageLinkButtonRedirect": "URL of homepage link button",
"homepageLinkButtonText": "Text of homepage link button",
+ "privacyPolicy": "Privacy Policy",
"taskServer": {
"taskServer": "Task Server",
"port": "Port",
diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json
index 7ed08ab631..f797e332c8 100755
--- a/packages/client-core/i18n/en/editor.json
+++ b/packages/client-core/i18n/en/editor.json
@@ -99,6 +99,36 @@
"toolbar": {
"lbl-publish": "Publish",
"lbl-published": "Published",
+ "gizmo": {
+ "description": "Transform Gizmo",
+ "translate": "[T] Translate",
+ "rotate": "[R] Rotate",
+ "scale": "[S] Scale"
+ },
+ "transformSpace": {
+ "description": "Sets your transform control to be oriented to the object(selection) or world",
+ "lbl-world": "World",
+ "info-world": "World space relates to the entire scene’s orientation",
+ "lbl-selection": "Object",
+ "info-selection": "Object Space (your selection) relates to transforms made to a specific object in relationship to the world space",
+ "lbl-toggleTransformSpace": "[Z] Toggle Transform Space"
+ },
+ "transformPivot": {
+ "toggleTransformPivot" : "[X] Toggle Transform Pivot",
+ "lbl-selection": "Selection",
+ "info-selection": "The center pivot of the final asset you selected in the sequence.",
+ "lbl-center": "Center",
+ "info-center": "A pivot that sits at an equal distance between all selections.",
+ "lbl-bottom": "Bottom",
+ "info-bottom": "A pivot that sits equally between all selections and at the bottom of your final selection in the sequence.",
+ "lbl-origin": "Origin",
+ "info-origin": "Sets pivot mode to the world origin (0,0,0)"
+ },
+ "transformSnapTool": {
+ "toggleSnapMode": "[C] Toggle Snap Mode",
+ "info-translate": "Translate objects by a unit of measurement.",
+ "info-rotate": "Rotate objects by a specific degrees."
+ },
"command": {
"translate": "Translate",
"rotate": "Rotate",
@@ -109,8 +139,10 @@
"toggleGrid": "Toggle Grid Visibility",
"increaseGridSize": "Increase Grid Size",
"descreaseGridSize": "Decrease Grid Size",
- "stopPreview": "Stop Previewing Scene",
- "playPreview": "Preview Scene"
+ "lbl-stopPreview": "Stop Previewing Scene",
+ "info-stopPreview": "Remove your avatar from scene",
+ "lbl-playPreview": "Preview Scene",
+ "info-playPreview": "Spawns you into the scene"
},
"instance": {
"none": "None",
@@ -118,6 +150,8 @@
"user": "User"
},
"render-settings":{
+ "lbl": "Render Settings",
+ "info": "How you view materials in the Engine",
"lbl-usePostProcessing":"Use Post Processing",
"info-usePostProcessing":"Enables post processing",
"lbl-shadowMapResolution":"Shadow Map Resolution",
@@ -126,12 +160,29 @@
"info-renderMode":"Choose render mode"
},
"grid": {
+ "info-toggleGridVisibility": "Toggle Grid Visibility",
+ "info-gridSpacing": "Set grid spacing meters",
"info-incrementHeight": "Increment Grid Height",
"info-decrementHeight": "Decrement Grid Height"
+ },
+ "stats": {
+ "lbl": "Toggle Stats",
+ "info": "Show stats about the scene and gives you a clue as to how optimized your scene is."
+ },
+ "helpersToggle": {
+ "lbl-helpers": "Toggle Helpers",
+ "info-helpers": "View hidden information about your scene, ie: colliders",
+ "lbl-nodeHelpers": "Toggle Node Helpers",
+ "info-nodeHelpers": "Helper geometry that helps components have visibility in the scene when inactive."
+ },
+ "sceneScreenshot": {
+ "lbl": "Screenshot",
+ "info": "Takes a screenshot of your scene at the current view."
}
},
"properties": {
"title": "Properties",
+ "info": "Propeties let you access and edit detailed information about objects in your scene.",
"lbl-visible": "Visible",
"lbl-preventBake": "Prevent Bake",
"lbl-dynamicLoad": "Load Children Dynamically",
@@ -746,7 +797,33 @@
"placeObjectAtOrigin": "Place Object at Origin",
"copyURL": "Copy URL",
"openInNewTab": "Open URL in New Tab",
- "deleteAsset": "Delete Asset"
+ "deleteAsset": "Delete Asset",
+ "tooltip": {
+ "EE_model": "Creates objects in the hierarchy. Drag a model from the assets folder into the URL box or drag assets directly from project files into the hierarchy.",
+ "EE_volumetric": "Import volumetric files. Accepts DRCS, UVOL, or Manifest Files. Links to cloud hosting.",
+ "EE_video": "Imports a 2D plane that accepts .mp4, .mkv, .avi",
+ "EE_positionalAudio": "Import audio clips, .mp3, .flac, .ogg, .wav, .m4a",
+ "EE_image": "Imports an image into the scene",
+ "GroundPlaneComponent": "Create collision ground plane.",
+ "GroupComponent": "Collection of models or assets.",
+ "PrefabComponent": "Create prefabs from groups or objects that are saved to the assets folder. Saving requires specific naming: 'assetName'.xre.gltf",
+ "ColliderComponent": "Creates a collision ball, cuboid, capsule, or cylinder to be manually placement.",
+ "SpawnPointComponent": "A point where people will appear when they enter your scene.",
+ "PortalComponent": "A portal to teleport a player to a port in a different location.",
+ "AmbientLightComponent": "A combination of direct and indirect light, provides general lighting to all assets.",
+ "PointLightComponent": "A light which emits in all directions from a single point.",
+ "SpotLightComponent": "Creates a light that shines in a specific direction.",
+ "DirectionalLightComponent": "Creates a light that emits evenly in a single direction.",
+ "HemisphereLightComponent": "A light which illuminates the scene from directly overhead.",
+ "EE_ParticleSystem": "Creates a particle emitter.",
+ "SystemComponent": "Inserts code into the scene by creating a new Entity Component System based on the provided .ts file",
+ "EE_behaveGraph": "Customizes state and behavior of entities through a node graph connection.",
+ "EnvMapBakeComponent": "Add Env Mapbake to your scene.",
+ "EE_scenePreviewCamera": "A preview camera which generates a scene thumbnail and the starting position.",
+ "SkyboxComponent": "Sets the area outside your map (skybox) with a specific sky type.",
+ "SplineTrackComponent": "Creates a spline track.",
+ "SplineComponent": "Create and customize curves."
+ }
},
"filebrowser": {
"addNewFolder": "Add New Folder",
@@ -770,6 +847,8 @@
}
},
"hierarchy": {
+ "lbl": "Hierarchy",
+ "info": "The scene Hierarchy contains all element currently in your scene (assets, lighting, items from the tool menu, etc).",
"lbl-rename": "Rename",
"lbl-duplicate": "Duplicate",
"lbl-group": "Group",
@@ -777,8 +856,13 @@
"lbl-expandAll": "Expand All",
"lbl-collapseAll": "Collapse All",
"lbl-explode": "Explode Objects",
+ "lbl-addEntity": "Add Entity",
"isseus": "Issues:"
},
+ "materialLibrary": {
+ "lbl": "Material Library",
+ "info": "Location of an assets materials and where you can select and edit them."
+ },
"dnd": {
"nodes": "{{count}} Nodes Selected",
"models": "{{count}} Models Selected",
diff --git a/packages/client-core/i18n/en/user.json b/packages/client-core/i18n/en/user.json
index 5b7890d76f..2da196b7e3 100755
--- a/packages/client-core/i18n/en/user.json
+++ b/packages/client-core/i18n/en/user.json
@@ -257,7 +257,8 @@
},
"userIdCopied": "User ID copied",
"apiKeyCopied": "API Key copied",
- "refreshApiKey": "Refresh API Key"
+ "refreshApiKey": "Refresh API Key",
+ "privacyPolicy": "Privacy Policy"
},
"oauth": {
"authenticating": "Authenticating...",
diff --git a/packages/client-core/src/admin/common/variables/location.ts b/packages/client-core/src/admin/common/variables/location.ts
index d28353c175..1f262740d8 100644
--- a/packages/client-core/src/admin/common/variables/location.ts
+++ b/packages/client-core/src/admin/common/variables/location.ts
@@ -23,6 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
+
export interface LocationColumn {
id: 'sceneId' | 'maxUsersPerInstance' | 'scene' | 'name' | 'locationType' | 'tags' | 'videoEnabled' | 'action'
label: string
@@ -77,6 +79,6 @@ export interface LocationData {
status: string
location: string
inviteCode: string
- instanceId: string
+ instanceId: InstanceID
action: any
}
diff --git a/packages/client-core/src/admin/components/Bots/CreateBot.tsx b/packages/client-core/src/admin/components/Bots/CreateBot.tsx
index 9a7c9dcee7..fd3b934723 100644
--- a/packages/client-core/src/admin/components/Bots/CreateBot.tsx
+++ b/packages/client-core/src/admin/components/Bots/CreateBot.tsx
@@ -43,6 +43,7 @@ import Typography from '@etherealengine/ui/src/primitives/mui/Typography'
import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks'
import { BotData, botPath } from '@etherealengine/engine/src/schemas/bot/bot.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema'
import { NotificationService } from '../../../common/services/NotificationService'
import { AuthState } from '../../../user/services/AuthService'
@@ -68,7 +69,7 @@ const CreateBot = () => {
const state = useHookstate({
name: '',
description: '',
- instance: '',
+ instance: '' as InstanceID,
location: ''
})
const user = useHookstate(getMutableState(AuthState).user)
@@ -107,7 +108,7 @@ const CreateBot = () => {
useEffect(() => {
const instanceFilter = data.filter((el) => el.locationId === state.location.value)
if (instanceFilter.length > 0) {
- state.merge({ instance: '' })
+ state.merge({ instance: '' as InstanceID })
currentInstance.set(instanceFilter)
} else {
currentInstance.set([])
@@ -117,7 +118,7 @@ const CreateBot = () => {
const handleSubmit = () => {
const data: BotData = {
name: state.name.value,
- instanceId: state.instance.value || '',
+ instanceId: state.instance.value || ('' as InstanceID),
userId: user.id.value,
botCommands: commandData.get({ noproxy: true }),
description: state.description.value,
@@ -132,7 +133,7 @@ const CreateBot = () => {
if (validateForm(state.value, formErrors.value)) {
createBotData(data)
- state.set({ name: '', description: '', instance: '', location: '' })
+ state.set({ name: '', description: '', instance: '' as InstanceID, location: '' })
commandData.set([])
currentInstance.set([])
} else {
diff --git a/packages/client-core/src/admin/components/Bots/UpdateBot.tsx b/packages/client-core/src/admin/components/Bots/UpdateBot.tsx
index 6529a2c09f..189a73bb17 100644
--- a/packages/client-core/src/admin/components/Bots/UpdateBot.tsx
+++ b/packages/client-core/src/admin/components/Bots/UpdateBot.tsx
@@ -40,6 +40,7 @@ import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton'
import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks'
import { BotPatch, BotType, botPath } from '@etherealengine/engine/src/schemas/bot/bot.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema'
import { NotificationService } from '../../../common/services/NotificationService'
import { AuthState } from '../../../user/services/AuthService'
@@ -57,7 +58,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => {
const state = useHookstate({
name: '',
description: '',
- instance: '',
+ instance: '' as InstanceID,
location: ''
})
const formErrors = useHookstate({
@@ -81,7 +82,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => {
state.set({
name: bot?.name,
description: bot?.description,
- instance: bot?.instance?.id || '',
+ instance: bot?.instance?.id || ('' as InstanceID),
location: bot?.location?.id || ''
})
}
@@ -127,18 +128,18 @@ const UpdateBot = ({ open, bot, onClose }: Props) => {
useEffect(() => {
const instanceFilter = data.filter((el) => el.locationId === state.location.value)
if (instanceFilter.length > 0) {
- state.merge({ instance: state.instance.value || '' })
+ state.merge({ instance: state.instance.value || ('' as InstanceID) })
currentInstance.set(instanceFilter)
} else {
currentInstance.set([])
- state.merge({ instance: '' })
+ state.merge({ instance: '' as InstanceID })
}
}, [state.location.value, instancesData])
const handleUpdate = () => {
const data: BotPatch = {
name: state.name.value,
- instanceId: state.instance.value || '',
+ instanceId: state.instance.value || ('' as InstanceID),
userId: user.id.value,
description: state.description.value,
locationId: state.location.value
@@ -152,7 +153,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => {
if (validateForm(state.value, formErrors.value) && bot) {
updateBot(bot.id, data)
- state.set({ name: '', description: '', instance: '', location: '' })
+ state.set({ name: '', description: '', instance: '' as InstanceID, location: '' })
currentInstance.set([])
onClose()
} else {
@@ -221,7 +222,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => {
disableElevation
type="submit"
onClick={() => {
- state.set({ name: '', description: '', instance: '', location: '' })
+ state.set({ name: '', description: '', instance: '' as InstanceID, location: '' })
formErrors.set({ name: '', description: '', location: '' })
onClose()
}}
diff --git a/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx b/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx
index 12c9a23299..2d992cd0e0 100644
--- a/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx
+++ b/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx
@@ -37,9 +37,10 @@ import Grid from '@etherealengine/ui/src/primitives/mui/Grid'
import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks'
import { instanceAttendancePath } from '@etherealengine/engine/src/schemas/networking/instance-attendance.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { userKickPath } from '@etherealengine/engine/src/schemas/user/user-kick.schema'
import { UserID, userPath } from '@etherealengine/engine/src/schemas/user/user.schema'
-import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql'
+import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql'
import ConfirmDialog from '../../../common/components/ConfirmDialog'
import { NotificationService } from '../../../common/services/NotificationService'
import DrawerView from '../../common/DrawerView'
@@ -57,7 +58,7 @@ const INFINITY = 'INFINITY'
const INSTANCE_USERS_PAGE_LIMIT = 10
-const useUsersInInstance = (instanceId: string) => {
+const useUsersInInstance = (instanceId: InstanceID) => {
const instanceAttendances = useFind(instanceAttendancePath, {
query: {
instanceId
@@ -99,11 +100,11 @@ const InstanceDrawer = ({ open, selectedInstance, onClose }: Props) => {
const openKickDialog = useHookstate(false)
const kickData = useHookstate({
userId: '' as UserID,
- instanceId: '',
+ instanceId: '' as InstanceID,
duration: '8'
})
- const instanceUsersQuery = useUsersInInstance(selectedInstance?.id ?? '')
+ const instanceUsersQuery = useUsersInInstance(selectedInstance?.id ?? ('' as InstanceID))
const kickUser = useKickUser()
const createData = (id: UserID, name: string) => ({
diff --git a/packages/client-core/src/admin/components/Invite/AdminInvites.tsx b/packages/client-core/src/admin/components/Invite/AdminInvites.tsx
index d1b963513f..395026c5e9 100755
--- a/packages/client-core/src/admin/components/Invite/AdminInvites.tsx
+++ b/packages/client-core/src/admin/components/Invite/AdminInvites.tsx
@@ -34,7 +34,7 @@ import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks'
import { InviteType, invitePath } from '@etherealengine/engine/src/schemas/social/invite.schema'
-import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql'
+import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql'
import { INVITE_PAGE_LIMIT } from '../../../social/services/InviteService'
import TableComponent from '../../common/Table'
import { InviteColumn, inviteColumns } from '../../common/variables/invite'
diff --git a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx
index 4b99172ce5..a988de0c98 100755
--- a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx
+++ b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx
@@ -50,7 +50,7 @@ import { useFind } from '@etherealengine/engine/src/common/functions/FeathersHoo
import { InviteData } from '@etherealengine/engine/src/schemas/social/invite.schema'
import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema'
import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema'
-import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql'
+import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql'
import { NotificationService } from '../../../common/services/NotificationService'
import { InviteService } from '../../../social/services/InviteService'
import DrawerView from '../../common/DrawerView'
diff --git a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx
index a03999ba3b..9c735e45a9 100755
--- a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx
+++ b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx
@@ -51,7 +51,7 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { InvitePatch, InviteType, invitePath } from '@etherealengine/engine/src/schemas/social/invite.schema'
import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema'
import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema'
-import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql'
+import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql'
import { Id } from '@feathersjs/feathers'
import { NotificationService } from '../../../common/services/NotificationService'
import DrawerView from '../../common/DrawerView'
diff --git a/packages/client-core/src/admin/components/Invite/index.tsx b/packages/client-core/src/admin/components/Invite/index.tsx
index 793bddeaa4..6c8fe345c7 100755
--- a/packages/client-core/src/admin/components/Invite/index.tsx
+++ b/packages/client-core/src/admin/components/Invite/index.tsx
@@ -82,8 +82,7 @@ const InvitesConsole = () => {
>
{t('admin:components.invite.create')}
-
- {selectedInviteIds.value.size && (
+ {selectedInviteIds.size.value > 0 && (
{
const homepageLinkButtonEnabled = useHookstate(clientSetting?.homepageLinkButtonEnabled)
const homepageLinkButtonRedirect = useHookstate(clientSetting?.homepageLinkButtonRedirect)
const homepageLinkButtonText = useHookstate(clientSetting?.homepageLinkButtonText)
+ const privacyPolicyLink = useHookstate(clientSetting?.privacyPolicy)
useEffect(() => {
if (user?.id?.value != null && clientSettingState?.updateNeeded?.value === true) {
@@ -97,6 +98,7 @@ const Client = () => {
favicon32px.set(clientSetting?.favicon32px)
siteDescription.set(clientSetting?.siteDescription)
key8thWall.set(clientSetting?.key8thWall)
+ privacyPolicyLink.set(clientSetting?.privacyPolicy)
homepageLinkButtonEnabled.set(clientSetting?.homepageLinkButtonEnabled)
homepageLinkButtonRedirect.set(clientSetting?.homepageLinkButtonRedirect)
homepageLinkButtonText.set(clientSetting?.homepageLinkButtonText)
@@ -152,6 +154,7 @@ const Client = () => {
themeSettings: clientSetting?.themeSettings,
themeModes: clientSetting?.themeModes,
key8thWall: key8thWall.value,
+ privacyPolicy: privacyPolicyLink.value,
homepageLinkButtonEnabled: homepageLinkButtonEnabled.value,
homepageLinkButtonRedirect: homepageLinkButtonRedirect.value,
homepageLinkButtonText: homepageLinkButtonText.value
@@ -379,6 +382,13 @@ const Client = () => {
disabled
/>
+ privacyPolicyLink.set(e.target.value)}
+ />
+
({
- instances: {} as { [id: string]: InstanceState }
+ instances: {} as { [id: InstanceID]: InstanceState }
})
})
@@ -115,7 +115,7 @@ export const LocationInstanceConnectionService = {
}, 1000)
}
},
- provisionExistingServer: async (locationId: string, instanceId: string, sceneId: string) => {
+ provisionExistingServer: async (locationId: string, instanceId: InstanceID, sceneId: string) => {
logger.info({ locationId, instanceId, sceneId }, 'Provision Existing World Server')
const token = getState(AuthState).authUser.accessToken
const instance = (await API.instance.client.service('instance').find({
@@ -198,13 +198,13 @@ export const LocationInstanceConnectionService = {
console.warn('Failed to connect to expected existing instance')
}
},
- changeActiveConnectionHostId: (currentInstanceId: UserID, newInstanceId: UserID) => {
+ changeActiveConnectionID: (currentInstanceId: InstanceID, newInstanceId: InstanceID) => {
const state = getMutableState(LocationInstanceState)
const currentNetwork = state.instances[currentInstanceId].get({ noproxy: true })
const networkState = getMutableState(NetworkState)
const currentNework = getState(NetworkState).networks[currentInstanceId]
updateNetworkID(currentNework as SocketWebRTCClientNetwork, newInstanceId)
- networkState.hostIds.media.set(newInstanceId as UserID)
+ networkState.hostIds.media.set(newInstanceId)
state.instances.merge({ [newInstanceId]: currentNetwork })
state.instances[currentInstanceId].set(none)
},
diff --git a/packages/client-core/src/common/services/MediaInstanceConnectionService.ts b/packages/client-core/src/common/services/MediaInstanceConnectionService.ts
index 3f6533ff22..57a660278d 100755
--- a/packages/client-core/src/common/services/MediaInstanceConnectionService.ts
+++ b/packages/client-core/src/common/services/MediaInstanceConnectionService.ts
@@ -31,6 +31,7 @@ import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState
import { defineState, getMutableState, getState, State, useState } from '@etherealengine/hyperflux'
import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientFunctions'
import { AuthState } from '../../user/services/AuthService'
@@ -47,7 +48,7 @@ type InstanceState = {
export const MediaInstanceState = defineState({
name: 'MediaInstanceState',
initial: () => ({
- instances: {} as { [id: string]: InstanceState }
+ instances: {} as { [id: InstanceID]: InstanceState }
})
})
diff --git a/packages/client-core/src/components/ConferenceMode/index.tsx b/packages/client-core/src/components/ConferenceMode/index.tsx
index e26218cf05..117eaa107a 100644
--- a/packages/client-core/src/components/ConferenceMode/index.tsx
+++ b/packages/client-core/src/components/ConferenceMode/index.tsx
@@ -41,10 +41,10 @@ const ConferenceMode = (): JSX.Element => {
const authState = useHookstate(getMutableState(AuthState))
const channelConnectionState = useHookstate(getMutableState(MediaInstanceState))
const network = Engine.instance.mediaNetwork
- const currentChannelInstanceConnection = network && channelConnectionState.instances[network.hostId].ornull
+ const currentChannelInstanceConnection = network && channelConnectionState.instances[network.id].ornull
const displayedUsers =
- network?.hostId && currentChannelInstanceConnection
- ? Array.from(network.peers.values()).filter(
+ network?.id && currentChannelInstanceConnection
+ ? Object.values(network.peers).filter(
(peer) => peer.peerID !== 'server' && peer.userId !== authState.user.id.value
) || []
: []
@@ -67,7 +67,7 @@ const ConferenceMode = (): JSX.Element => {
for (let user of displayedUsers) {
totalScreens += 1
- const peerID = Array.from(network.peers.values()).find((peer) => peer.userId === user.userId)?.peerID
+ const peerID = Object.values(network.peers).find((peer) => peer.userId === user.userId)?.peerID
if (screenShareConsumers.find((consumer) => consumer.peerID.value === peerID)) {
totalScreens += 1
}
diff --git a/packages/client-core/src/components/InstanceChat/index.tsx b/packages/client-core/src/components/InstanceChat/index.tsx
index 7b77585fb0..53f33cb011 100755
--- a/packages/client-core/src/components/InstanceChat/index.tsx
+++ b/packages/client-core/src/components/InstanceChat/index.tsx
@@ -145,7 +145,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu
}
const packageMessage = (): void => {
- const instanceId = Engine.instance.worldNetwork.hostId
+ const instanceId = Engine.instance.worldNetwork.id
if (composingMessage?.value?.length && instanceId) {
if (usersTyping) {
dispatchAction(
@@ -460,7 +460,7 @@ export const InstanceChatWrapper = () => {
if (worldNetwork?.connected?.value) {
ChannelService.getInstanceChannel()
}
- }, [worldNetwork?.connected])
+ }, [worldNetwork?.connected?.value])
return (
<>
diff --git a/packages/client-core/src/components/LocationIcons/index.tsx b/packages/client-core/src/components/LocationIcons/index.tsx
index 3e6fe3f588..f2d069fa51 100755
--- a/packages/client-core/src/components/LocationIcons/index.tsx
+++ b/packages/client-core/src/components/LocationIcons/index.tsx
@@ -28,7 +28,6 @@ import React from 'react'
import { TouchGamepad } from '@etherealengine/client-core/src/common/components/TouchGamepad'
import { UserMenu } from '@etherealengine/client-core/src/user/components/UserMenu'
import { iOS } from '@etherealengine/engine/src/common/functions/isMobile'
-import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState'
import { getCameraMode, XRState } from '@etherealengine/engine/src/xr/XRState'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
@@ -44,11 +43,9 @@ import styles from './index.module.scss'
export const LocationIcons = () => {
const loadingScreenOpacity = useHookstate(getMutableState(LoadingSystemState).loadingScreenOpacity)
- const isEngineInitialized = useHookstate(getMutableState(EngineState).isEngineInitialized)
useHookstate(getMutableState(XRState))
const cameraMode = getCameraMode()
- if (!isEngineInitialized.value) return <>>
return (
<>
diff --git a/packages/client-core/src/components/MediaIconsBox/index.tsx b/packages/client-core/src/components/MediaIconsBox/index.tsx
index 583c6cffea..ca40e5fdf2 100755
--- a/packages/client-core/src/components/MediaIconsBox/index.tsx
+++ b/packages/client-core/src/components/MediaIconsBox/index.tsx
@@ -73,9 +73,9 @@ export const MediaIconsBox = () => {
const channelConnectionState = useHookstate(getMutableState(MediaInstanceState))
const networkState = useHookstate(getMutableState(NetworkState))
const mediaNetworkState = useMediaNetwork()
- const mediaHostId = Engine.instance.mediaNetwork?.hostId
+ const mediaNetworkID = Engine.instance.mediaNetwork?.id
const mediaNetworkReady = mediaNetworkState?.ready?.value
- const currentChannelInstanceConnection = mediaHostId && channelConnectionState.instances[mediaHostId].ornull
+ const currentChannelInstanceConnection = mediaNetworkID && channelConnectionState.instances[mediaNetworkID].ornull
const videoEnabled = currentLocation?.locationSetting?.value
? currentLocation?.locationSetting?.videoEnabled?.value
: false
diff --git a/packages/client-core/src/components/UserMediaWindow/index.tsx b/packages/client-core/src/components/UserMediaWindow/index.tsx
index a58d2d3b75..87bff8544f 100755
--- a/packages/client-core/src/components/UserMediaWindow/index.tsx
+++ b/packages/client-core/src/components/UserMediaWindow/index.tsx
@@ -111,11 +111,11 @@ export const useUserMediaWindowHook = ({ peerID, type }: Props) => {
!mediaNetwork ||
peerID === Engine.instance.peerID ||
(mediaNetwork?.peers &&
- Array.from(mediaNetwork.peers.values()).find((peer) => peer.userId === selfUser.id)?.peerID === peerID) ||
+ Object.values(mediaNetwork.peers).find((peer) => peer.userId === selfUser.id)?.peerID === peerID) ||
peerID === 'self'
const volume = isSelf ? audioState.microphoneGain.value : _volume.value
const isScreen = type === 'screen'
- const userId = isSelf ? selfUser?.id : mediaNetwork?.peers?.get(peerID!)?.userId
+ const userId = isSelf ? selfUser?.id : mediaNetwork?.peers?.[peerID]?.userId
const mediaStreamState = useHookstate(getMutableState(MediaStreamState))
const mediaSettingState = useHookstate(getMutableState(MediaSettingsState))
diff --git a/packages/client-core/src/components/UserMediaWindows/index.tsx b/packages/client-core/src/components/UserMediaWindows/index.tsx
index 15b6b94925..a9a663c2a4 100755
--- a/packages/client-core/src/components/UserMediaWindows/index.tsx
+++ b/packages/client-core/src/components/UserMediaWindows/index.tsx
@@ -66,7 +66,7 @@ export const useMediaWindows = () => {
(cam.audioStream && !cam.audioProducerPaused && !cam.audioStreamPaused)
const userPeers: Array<[UserID, PeerID[]]> = mediaNetworkConnected
- ? Array.from(mediaNetwork.users.entries())
+ ? (Object.entries(mediaNetwork.users) as Array<[UserID, PeerID[]]>)
: [[selfUserID, [selfPeerID]]]
// reduce all userPeers to an array 'windows' of { peerID, type } objects, displaying screens first, then cams. if a user has no cameras, only include one peerID for that user
@@ -98,20 +98,28 @@ export const useMediaWindows = () => {
.sort(sortScreensBeforeCameras)
.filter(({ peerID }) => peerMediaChannelState[peerID].value)
+ // if window doesnt exist for self, add it
+ if (!windows.find(({ peerID }) => peerID === selfPeerID)) {
+ windows.unshift({ peerID: selfPeerID, type: 'cam' })
+ }
+
return windows
}
export const UserMediaWindows = () => {
const { topShelfStyle } = useShelfStyles()
+ const peerMediaChannelState = useHookstate(getMutableState(PeerMediaChannelState))
const windows = useMediaWindows()
return (
- {windows.map(({ peerID, type }) => (
-
- ))}
+ {windows
+ .filter(({ peerID }) => peerMediaChannelState[peerID].value)
+ .map(({ peerID, type }) => (
+
+ ))}
)
@@ -151,7 +159,7 @@ export const UserMediaWindowsWidget = () => {
const mediaNetwork = Engine.instance.mediaNetwork
// if window doesnt exist for self, add it
- if (!mediaNetwork || !windows.find(({ peerID }) => mediaNetwork.peers.get(peerID)?.userId === selfUserID)) {
+ if (!mediaNetwork || !windows.find(({ peerID }) => mediaNetwork.peers[peerID]?.userId === selfUserID)) {
windows.unshift({ peerID: selfPeerID, type: 'cam' })
}
diff --git a/packages/client-core/src/components/World/EngineHooks.tsx b/packages/client-core/src/components/World/EngineHooks.tsx
index cf5f8f3582..50aca9b509 100755
--- a/packages/client-core/src/components/World/EngineHooks.tsx
+++ b/packages/client-core/src/components/World/EngineHooks.tsx
@@ -54,6 +54,7 @@ import { addOutgoingTopicIfNecessary, dispatchAction, getMutableState } from '@e
import { loadEngineInjection } from '@etherealengine/projects/loadEngineInjection'
import { UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { NotificationService } from '../../common/services/NotificationService'
import { RouterService } from '../../common/services/RouterService'
import { LocationState } from '../../social/services/LocationService'
@@ -230,8 +231,8 @@ export const useOfflineNetwork = (props?: { spectate?: boolean }) => {
const peerIndex = 1
const networkState = getMutableState(NetworkState)
- networkState.hostIds.world.set(userId)
- addNetwork(createNetwork(userId, userId, NetworkTopics.world))
+ networkState.hostIds.world.set(userId as any as InstanceID)
+ addNetwork(createNetwork(userId as any as InstanceID, userId, NetworkTopics.world))
addOutgoingTopicIfNecessary(NetworkTopics.world)
NetworkPeerFunctions.createPeer(
diff --git a/packages/client-core/src/media/PeerMedia.tsx b/packages/client-core/src/media/PeerMedia.tsx
index c08e67b688..3681de8350 100644
--- a/packages/client-core/src/media/PeerMedia.tsx
+++ b/packages/client-core/src/media/PeerMedia.tsx
@@ -40,7 +40,8 @@ import {
MediasoupMediaProducerConsumerState,
MediasoupMediaProducersConsumersObjectsState
} from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState'
-import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
+import { useMediaNetwork } from '../common/services/MediaInstanceConnectionService'
import { MediaStreamState } from '../transports/MediaStreams'
import {
PeerMediaChannelState,
@@ -52,7 +53,7 @@ import { SocketWebRTCClientNetwork } from '../transports/SocketWebRTCClientFunct
/**
* Sets media stream state for a peer
*/
-const PeerMedia = (props: { consumerID: string; networkID: UserID }) => {
+const PeerMedia = (props: { consumerID: string; networkID: InstanceID }) => {
const consumerState = useHookstate(
getMutableState(MediasoupMediaProducerConsumerState)[props.networkID].consumers[props.consumerID]
)
@@ -133,47 +134,15 @@ const SelfMedia = () => {
return null
}
-export const PeerMediaChannels = (props: { networkID: UserID }) => {
- const mediaStreamState = useHookstate(getMutableState(MediaStreamState))
- const mediaNetworkState = useHookstate(getMutableState(NetworkState).networks[props.networkID])
- const consumerState = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[props.networkID].consumers)
-
- // create a peer media stream for each peer with a consumer
- useEffect(() => {
- const mediaNetwork = Engine.instance.mediaNetwork as SocketWebRTCClientNetwork
- if (!mediaNetwork) return
- const peerMediaChannels = getState(PeerMediaChannelState)
- const mediaChannelPeers = Array.from(mediaNetwork.peers.keys()).filter((peerID) => peerID !== 'server')
- for (const peerID of mediaChannelPeers) {
- if (!peerMediaChannels[peerID]) {
- createPeerMediaChannels(peerID)
- }
- }
- for (const peerID of Object.keys(peerMediaChannels)) {
- const peerConsumers = mediaChannelPeers.filter((peer) => peer === peerID)
- if (peerConsumers.length === 0) {
- removePeerMediaChannels(peerID as PeerID)
- }
- }
- }, [
- mediaNetworkState?.peers?.size,
- consumerState.keys.length,
- mediaStreamState.videoStream,
- mediaStreamState.audioStream,
- mediaStreamState.screenAudioProducer,
- mediaStreamState.screenVideoProducer
- ])
-
- return null
-}
-
-export const NetworkProducer = (props: { networkID: UserID; producerID: string }) => {
+export const NetworkProducer = (props: { networkID: InstanceID; producerID: string }) => {
const { networkID, producerID } = props
const producerState = useHookstate(
getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers[producerID]
)
+ const networkState = useHookstate(getMutableState(NetworkState).networks[networkID])
useEffect(() => {
+ if (!networkState.ready.value) return
const peerID = producerState.peerID.value
const mediaTag = producerState.mediaTag.value
const channelID = producerState.channelID.value
@@ -189,18 +158,17 @@ export const NetworkProducer = (props: { networkID: UserID; producerID: string }
$to: network.hostPeerID
})
)
- }, [])
+ }, [networkState.ready])
return null
}
-const NetworkConsumers = (props: { networkID: UserID }) => {
+const NetworkConsumers = (props: { networkID: InstanceID }) => {
const { networkID } = props
const consumers = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[networkID].consumers)
const producers = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers)
return (
<>
-
{producers.keys.map((producerID: string) => (
))}
@@ -211,14 +179,46 @@ const NetworkConsumers = (props: { networkID: UserID }) => {
)
}
+export const PeerMediaChannel = (props: { peerID: PeerID }) => {
+ useEffect(() => {
+ createPeerMediaChannels(props.peerID)
+ return () => {
+ removePeerMediaChannels(props.peerID)
+ }
+ }, [])
+ return null
+}
+
+export const PeerMediaChannels = () => {
+ const mediaNetwork = useMediaNetwork()
+
+ const mediaPeers = useHookstate([] as PeerID[])
+
+ useEffect(() => {
+ const mediaChannelPeers = mediaNetwork?.peers?.keys?.length
+ ? Array.from(mediaNetwork.peers.keys as PeerID[]).filter((peerID) => peerID !== 'server')
+ : [Engine.instance.peerID]
+ mediaPeers.set(mediaChannelPeers)
+ }, [mediaNetwork?.peers?.keys?.length])
+
+ return (
+ <>
+ {mediaPeers.value.map((peerID) => (
+
+ ))}
+ >
+ )
+}
+
export const PeerMediaConsumers = () => {
const networkIDs = useHookstate(getMutableState(MediasoupMediaProducerConsumerState))
const selfPeerMediaChannelState = useHookstate(getMutableState(PeerMediaChannelState)[Engine.instance.peerID])
return (
<>
+
{selfPeerMediaChannelState.value && }
- {networkIDs.keys.map((hostId: UserID) => (
-
+ {networkIDs.keys.map((id: InstanceID) => (
+
))}
>
)
diff --git a/packages/client-core/src/networking/ClientNetworkingSystem.tsx b/packages/client-core/src/networking/ClientNetworkingSystem.tsx
index 203a05462b..baf593e04f 100644
--- a/packages/client-core/src/networking/ClientNetworkingSystem.tsx
+++ b/packages/client-core/src/networking/ClientNetworkingSystem.tsx
@@ -36,6 +36,7 @@ import {
useHookstate
} from '@etherealengine/hyperflux'
+import { PeerID } from '@etherealengine/common/src/interfaces/PeerID'
import { NetworkActions, NetworkState } from '@etherealengine/engine/src/networking/NetworkState'
import { NetworkPeerFunctions } from '@etherealengine/engine/src/networking/functions/NetworkPeerFunctions'
import { MediasoupMediaConsumerActions } from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState'
@@ -44,7 +45,7 @@ import {
MediasoupTransportObjectsState,
MediasoupTransportState
} from '@etherealengine/engine/src/networking/systems/MediasoupTransportState'
-import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { PeerMediaConsumers } from '../media/PeerMedia'
import { FriendServiceReceptor } from '../social/services/FriendService'
import {
@@ -70,14 +71,14 @@ const execute = () => {
for (const peer of action.peers) {
NetworkPeerFunctions.createPeer(network, peer.peerID, peer.peerIndex, peer.userID, peer.userIndex, peer.name)
}
- for (const [peerID, peer] of network.peers)
+ for (const [peerID, peer] of Object.entries(network.peers))
if (!action.peers.find((p) => p.peerID === peerID)) {
- NetworkPeerFunctions.destroyPeer(network, peerID)
+ NetworkPeerFunctions.destroyPeer(network, peerID as PeerID)
}
}
}
-const NetworkConnectionReactor = (props: { networkID: UserID }) => {
+const NetworkConnectionReactor = (props: { networkID: InstanceID }) => {
const networkState = getMutableState(NetworkState).networks[props.networkID] as State
const transportState = useHookstate(getMutableState(MediasoupTransportObjectsState))
@@ -105,8 +106,8 @@ const reactor = () => {
return (
<>
- {networkIDs.map((hostId: UserID) => (
-
+ {networkIDs.map((id: InstanceID) => (
+
))}
diff --git a/packages/client-core/src/networking/DataChannelSystem.tsx b/packages/client-core/src/networking/DataChannelSystem.tsx
index f6f97bc64f..6bd6c1f771 100644
--- a/packages/client-core/src/networking/DataChannelSystem.tsx
+++ b/packages/client-core/src/networking/DataChannelSystem.tsx
@@ -37,7 +37,7 @@ import {
MediasoupTransportObjectsState,
MediasoupTransportState
} from '@etherealengine/engine/src/networking/systems/MediasoupTransportState'
-import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { defineActionQueue, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux'
import { none, useHookstate } from '@hookstate/core'
import { DataProducer, DataProducerOptions } from 'mediasoup-client/lib/DataProducer'
@@ -143,7 +143,7 @@ const execute = () => {
}
}
-export const DataChannel = (props: { networkID: UserID; dataChannelType: DataChannelType }) => {
+export const DataChannel = (props: { networkID: InstanceID; dataChannelType: DataChannelType }) => {
const { networkID, dataChannelType } = props
const transportState = useHookstate(getMutableState(MediasoupTransportObjectsState))
@@ -164,7 +164,7 @@ export const DataChannel = (props: { networkID: UserID; dataChannelType: DataCha
return null
}
-const NetworkReactor = (props: { networkID: UserID }) => {
+const NetworkReactor = (props: { networkID: InstanceID }) => {
const { networkID } = props
const dataChannelRegistry = useHookstate(getMutableState(DataChannelRegistryState))
return (
@@ -182,8 +182,8 @@ export const DataChannels = () => {
.map(([networkID, network]) => networkID)
return (
<>
- {networkIDs.map((hostId: UserID) => (
-
+ {networkIDs.map((id: InstanceID) => (
+
))}
>
)
diff --git a/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx b/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx
index 56c84c5573..f72117f419 100644
--- a/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx
+++ b/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx
@@ -44,7 +44,7 @@ import { getMutableState, none, useHookstate } from '@etherealengine/hyperflux'
import { Groups } from '@mui/icons-material'
-import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { useTranslation } from 'react-i18next'
import { FriendService } from '../social/services/FriendService'
import { connectToNetwork } from '../transports/SocketWebRTCClientFunctions'
@@ -129,14 +129,14 @@ export const WorldInstanceProvisioning = () => {
return (
<>
- {locationInstance.instances.keys.map((instanceId: UserID) => (
+ {locationInstance.instances.keys.map((instanceId: InstanceID) => (
))}
>
)
}
-export const WorldInstance = ({ id }: { id: UserID }) => {
+export const WorldInstance = ({ id }: { id: InstanceID }) => {
const worldInstance = useHookstate(getMutableState(LocationInstanceState).instances[id])
useEffect(() => {
@@ -156,7 +156,7 @@ export const WorldInstance = ({ id }: { id: UserID }) => {
export const MediaInstanceProvisioning = () => {
const channelState = useHookstate(getMutableState(ChannelState))
- const worldNetworkHostId = Engine.instance.worldNetwork?.hostId
+ const worldNetworkId = Engine.instance.worldNetwork?.id
const worldNetwork = useWorldNetwork()
MediaInstanceConnectionService.useAPIListeners()
@@ -168,7 +168,7 @@ export const MediaInstanceProvisioning = () => {
if (channelState.channels.channels?.value.length) {
const currentChannel =
channelState.targetChannelId.value === ''
- ? channelState.channels.channels.value.find((channel) => channel.instanceId === worldNetworkHostId)?.id
+ ? channelState.channels.channels.value.find((channel) => channel.instanceId === worldNetworkId)?.id
: channelState.targetChannelId.value
if (currentChannel) MediaInstanceConnectionService.provisionServer(currentChannel, true)
}
@@ -181,14 +181,14 @@ export const MediaInstanceProvisioning = () => {
return (
<>
- {mediaInstance.instances.keys.map((instanceId: UserID) => (
+ {mediaInstance.instances.keys.map((instanceId: InstanceID) => (
))}
>
)
}
-export const MediaInstance = ({ id }: { id: UserID }) => {
+export const MediaInstance = ({ id }: { id: InstanceID }) => {
const worldInstance = useHookstate(getMutableState(MediaInstanceState).instances[id])
useEffect(() => {
diff --git a/packages/client-core/src/social/services/ChannelService.ts b/packages/client-core/src/social/services/ChannelService.ts
index 5aad6dd8f2..b4af7a74d4 100755
--- a/packages/client-core/src/social/services/ChannelService.ts
+++ b/packages/client-core/src/social/services/ChannelService.ts
@@ -72,7 +72,7 @@ export const ChannelService = {
try {
const channelResult = (await Engine.instance.api.service('channel').find({
query: {
- instanceId: Engine.instance.worldNetwork.hostId
+ instanceId: Engine.instance.worldNetwork.id
}
})) as Channel[]
if (!channelResult.length) return setTimeout(() => ChannelService.getInstanceChannel(), 2000)
diff --git a/packages/client-core/src/systems/AvatarUISystem.tsx b/packages/client-core/src/systems/AvatarUISystem.tsx
index 4db19e3b66..d68c77252b 100644
--- a/packages/client-core/src/systems/AvatarUISystem.tsx
+++ b/packages/client-core/src/systems/AvatarUISystem.tsx
@@ -152,7 +152,6 @@ const onSecondaryClick = () => {
const execute = () => {
const engineState = getState(EngineState)
- if (!engineState.isEngineInitialized) return
const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0]
if (!nonCapturedInputSource) return
@@ -220,7 +219,7 @@ const execute = () => {
if (mediaNetwork)
if (immersiveMedia && videoPreviewTimer === 0) {
const { ownerId } = getComponent(userEntity, NetworkObjectComponent)
- const peers = mediaNetwork.peers ? Array.from(mediaNetwork.peers.values()) : []
+ const peers = mediaNetwork.peers ? Object.values(mediaNetwork.peers) : []
const peer = peers.find((peer) => {
return peer.userId === ownerId
})
diff --git a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx
index 79b922cc05..8a11fb1db4 100644
--- a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx
+++ b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx
@@ -59,7 +59,7 @@ const AvatarDetailView = () => {
const { t } = useTranslation()
const detailState = useXRUIState()
const user = Engine.instance.worldNetworkState?.peers
- ? Array.from(Engine.instance.worldNetwork.peers.values()).find((peer) => peer.userId === detailState.id.value)
+ ? Object.values(Engine.instance.worldNetwork.peers).find((peer) => peer.userId === detailState.id.value)
: undefined
const worldState = useHookstate(getMutableState(WorldState)).get({ noproxy: true })
const usersTypingState = useHookstate(getMutableState(AvatarUIState).usersTyping)
diff --git a/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx b/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx
index 5ed02edd7f..f7874921d2 100755
--- a/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx
+++ b/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx
@@ -209,7 +209,7 @@ export const RecordingPeerList = () => {
useEffect(() => {
if (!mediaNetworkState?.users) return
- peerIDs.set(mediaNetworkState.users.value.get(Engine.instance.userID) ?? [])
+ peerIDs.set(mediaNetworkState.users.get({ noproxy: true })[Engine.instance.userID] ?? [])
}, [mediaNetworkState?.peers, mediaNetworkState?.users])
return (
diff --git a/packages/client-core/src/systems/ui/UserMenuView/index.tsx b/packages/client-core/src/systems/ui/UserMenuView/index.tsx
index 12ca8119be..fd87126c58 100644
--- a/packages/client-core/src/systems/ui/UserMenuView/index.tsx
+++ b/packages/client-core/src/systems/ui/UserMenuView/index.tsx
@@ -66,8 +66,10 @@ const AvatarContextMenu = () => {
const authState = useHookstate(getMutableState(AuthState))
const selfId = authState.user.id?.value ?? ''
- const peers = (Engine.instance.worldNetworkState.peers?.get({ noproxy: true }) || []).values()
- const user = peers ? Array.from(peers).find((peer) => peer.userId === detailState.id.value) || undefined : undefined
+ const peers = Engine.instance.worldNetwork.peers
+ const user = peers
+ ? Object.values(peers).find((peer) => peer.userId === detailState.id.value) || undefined
+ : undefined
const { t } = useTranslation()
const isFriend = friendState.relationships.value.find(
@@ -92,9 +94,9 @@ const AvatarContextMenu = () => {
useEffect(() => {
if (detailState.id.value !== '') {
- const tappedUser = Array.from(
- (Engine.instance.worldNetworkState.peers?.get({ noproxy: true }) || []).values()
- ).find((peer) => peer.userId === detailState.id.value)
+ const tappedUser = Object.values(Engine.instance.worldNetwork.peers).find(
+ (peer) => peer.userId === detailState.id.value
+ )
dispatchAction(PopupMenuActions.showPopupMenu({ id: AvatarMenus.AvatarContext, params: { user: tappedUser } }))
}
}, [detailState.id])
diff --git a/packages/client-core/src/transports/FilteredUsersSystem.ts b/packages/client-core/src/transports/FilteredUsersSystem.ts
index 7bb4bc90ae..69d873c9f8 100755
--- a/packages/client-core/src/transports/FilteredUsersSystem.ts
+++ b/packages/client-core/src/transports/FilteredUsersSystem.ts
@@ -45,10 +45,10 @@ export const FilteredUsersService = {
if (!Engine.instance.worldNetwork) return
const mediaState = getMutableState(FilteredUsersState)
const selfUserId = getMutableState(AuthState).user.id.value
- const peers = Engine.instance.worldNetwork.peers ? Array.from(Engine.instance.worldNetwork.peers.values()) : []
+ const peers = Object.values(Engine.instance.worldNetwork.peers)
const worldUserIds = peers
- ? peers.filter((peer) => peer.peerID !== 'server' && peer.userId !== selfUserId).map((peer) => peer.userId)
- : []
+ .filter((peer) => peer.peerID !== 'server' && peer.userId !== selfUserId)
+ .map((peer) => peer.userId)
const nearbyUsers = getNearbyUsers(Engine.instance.userID, worldUserIds)
mediaState.nearbyLayerUsers.set(nearbyUsers)
}
@@ -61,7 +61,7 @@ export const updateNearbyAvatars = () => {
FilteredUsersService.updateNearbyLayerUsers()
const channelConnectionState = getState(MediaInstanceState)
- const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId]
+ const currentChannelInstanceConnection = channelConnectionState.instances[network.id]
if (!currentChannelInstanceConnection) return
const filteredUsersState = getState(FilteredUsersState)
diff --git a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
index 744b158c0c..f21f870540 100755
--- a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
+++ b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
@@ -90,6 +90,7 @@ import {
MediasoupTransportObjectsState,
MediasoupTransportState
} from '@etherealengine/engine/src/networking/systems/MediasoupTransportState'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { MathUtils } from 'three'
import { LocationInstanceState } from '../common/services/LocationInstanceConnectionService'
import { MediaInstanceState } from '../common/services/MediaInstanceConnectionService'
@@ -138,7 +139,7 @@ export const promisedRequest = (network: SocketWebRTCClientNetwork, type: any, d
})
}
-const handleFailedConnection = (topic: Topic, instanceID: UserID) => {
+const handleFailedConnection = (topic: Topic, instanceID: InstanceID) => {
console.log('handleFailedConnection', topic, instanceID)
if (topic === NetworkTopics.world) {
const locationInstanceConnectionState = getMutableState(LocationInstanceState)
@@ -167,7 +168,7 @@ export const closeNetwork = (network: SocketWebRTCClientNetwork) => {
networkState.transport.primus.set(null!)
}
-export const initializeNetwork = (id: string, hostId: UserID, topic: Topic) => {
+export const initializeNetwork = (id: InstanceID, hostId: UserID, topic: Topic) => {
const mediasoupDevice = new mediasoupClient.Device(
getMutableState(EngineState).isBot.value ? { handlerName: 'Chrome74' } : undefined
)
@@ -207,7 +208,7 @@ export const initializeNetwork = (id: string, hostId: UserID, topic: Topic) => {
export type SocketWebRTCClientNetwork = ReturnType
export const connectToNetwork = async (
- instanceID: UserID,
+ instanceID: InstanceID,
ipAddress: string,
port: string,
locationId?: string | null,
@@ -224,7 +225,7 @@ export const connectToNetwork = async (
roomCode,
token
} as {
- instanceID: string
+ instanceID: InstanceID
locationId?: string
channelId?: ChannelID
roomCode?: string
@@ -267,7 +268,7 @@ export const connectToNetwork = async (
const onConnect = () => {
const topic = locationId ? NetworkTopics.world : NetworkTopics.media
getMutableState(NetworkState).hostIds[topic].set(instanceID)
- const network = initializeNetwork(instanceID, instanceID, topic)
+ const network = initializeNetwork(instanceID, instanceID as any, topic)
addNetwork(network)
const networkState = getMutableState(NetworkState).networks[network.id] as State
@@ -296,7 +297,7 @@ export const connectToNetwork = async (
export const getChannelIdFromTransport = (network: SocketWebRTCClientNetwork) => {
const channelConnectionState = getState(MediaInstanceState)
const mediaNetwork = Engine.instance.mediaNetwork
- const currentChannelInstanceConnection = mediaNetwork && channelConnectionState.instances[mediaNetwork.hostId]
+ const currentChannelInstanceConnection = mediaNetwork && channelConnectionState.instances[mediaNetwork.id]
const isWorldConnection = network.topic === NetworkTopics.world
return isWorldConnection ? null : currentChannelInstanceConnection?.channelId
}
@@ -452,8 +453,6 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction
transport = await network.transport.mediasoupDevice.createSendTransport(transportOptions)
} else throw new Error(`bad transport 'direction': ${direction}`)
- getMutableState(MediasoupTransportObjectsState)[transportID].set(transport)
-
// mediasoup-client will emit a connect event when media needs to
// start flowing for the first time. send dtlsParameters to the
// server, then call callback() on success or errback() on failure.
@@ -596,6 +595,7 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction
errback: (error: Error) => void
) => {
const { sctpStreamParameters, label, protocol, appData } = parameters
+ if (label === '__CONNECT__') return callback({ id: '__CONNECT__' })
const requestID = MathUtils.generateUUID()
dispatchAction(
@@ -676,6 +676,51 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction
}, 5000)
}
})
+
+ /**
+ * Since mediasoup only connects the transport upon a consumer or producer being created,
+ * we need to create a dummy consumer/producer to trigger the transport to connect.
+ */
+ try {
+ if (direction === 'recv') {
+ const consumer = await transport.consumeData({
+ id: '',
+ dataProducerId: '',
+ sctpStreamParameters: {
+ streamId: 0,
+ ordered: true,
+ maxPacketLifeTime: 0
+ },
+ label: '__CONNECT__'
+ })
+ consumer.close()
+ } else {
+ const producer = await transport.produceData({
+ label: '__CONNECT__'
+ })
+ producer.close()
+ }
+ } catch (e) {
+ // no-op
+ }
+
+ // /**
+ // * Since mediasoup only connects the transport upon a consumer or producer being created,
+ // * we need to manually dive in and call it's internal implementation.
+ // * - NOTE this does not work for Edge11
+ // */
+ // const handler = (transport as any)._handler
+ // const offer = await handler._pc.createOffer()
+ // const localSdpObject = sdpTransform.parse(offer.sdp)
+ // const _dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject })
+ // _dtlsParameters.role = handler._forcedLocalDtlsRole
+ // handler._remoteSdp!.updateDtlsRole(handler._forcedLocalDtlsRole === 'client' ? 'server' : 'client')
+ // await new Promise((resolve, reject) => {
+ // transport.safeEmit('connect', { dtlsParameters: _dtlsParameters }, resolve, reject)
+ // })
+ // handler._transportReady = true
+
+ getMutableState(MediasoupTransportObjectsState)[transportID].set(transport)
}
export async function configureMediaTransports(mediaTypes: string[]): Promise {
@@ -708,7 +753,7 @@ export async function configureMediaTransports(mediaTypes: string[]): Promise {
const channelConnectionState = getState(MediaInstanceState)
- const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId]
+ const currentChannelInstanceConnection = channelConnectionState.instances[network.id]
const channelId = currentChannelInstanceConnection.channelId
const mediaStreamState = getMutableState(MediaStreamState)
if (mediaStreamState.videoStream.value !== null) {
@@ -749,7 +794,7 @@ export async function createCamVideoProducer(network: SocketWebRTCClientNetwork)
export async function createCamAudioProducer(network: SocketWebRTCClientNetwork): Promise {
const channelConnectionState = getState(MediaInstanceState)
- const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId]
+ const currentChannelInstanceConnection = channelConnectionState.instances[network.id]
const channelId = currentChannelInstanceConnection.channelId
const mediaStreamState = getMutableState(MediaStreamState)
if (mediaStreamState.audioStream.value !== null) {
@@ -802,6 +847,7 @@ export async function createCamAudioProducer(network: SocketWebRTCClientNetwork)
}
}
+/** @todo this is unused, see if it's ever needed to add these checks */
export async function subscribeToTrack(
network: SocketWebRTCClientNetwork,
peerID: PeerID,
@@ -816,7 +862,7 @@ export async function subscribeToTrack(
const selfProducerIds = [mediaStreamState.camVideoProducer?.id, mediaStreamState.camAudioProducer?.id]
const channelConnectionState = getState(MediaInstanceState)
- const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId]
+ const currentChannelInstanceConnection = channelConnectionState.instances[network.id]
const existingConsumer = MediasoupMediaProducerConsumerState.getConsumerByPeerIdAndMediaTag(
network.id,
@@ -1113,7 +1159,7 @@ export const startScreenshare = async (network: SocketWebRTCClientNetwork) => {
)
const channelConnectionState = getState(MediaInstanceState)
- const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId]
+ const currentChannelInstanceConnection = channelConnectionState.instances[network.id]
const channelId = currentChannelInstanceConnection.channelId
await waitForTransports(network)
diff --git a/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx b/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx
index 185bc36162..8dfa36d917 100755
--- a/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx
+++ b/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx
@@ -33,6 +33,7 @@ import Box from '@etherealengine/ui/src/primitives/mui/Box'
import Container from '@etherealengine/ui/src/primitives/mui/Container'
import Typography from '@etherealengine/ui/src/primitives/mui/Typography'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { AuthService, AuthState } from '../../services/AuthService'
import { VerifyEmail } from '../Auth/VerifyEmail'
@@ -40,7 +41,7 @@ interface Props {
//auth: any
type: string
token: string
- instanceId: string
+ instanceId: InstanceID
path: string
}
diff --git a/packages/client-core/src/user/components/UserMenu/index.module.scss b/packages/client-core/src/user/components/UserMenu/index.module.scss
index 4188f5b4c2..aff9957cd8 100755
--- a/packages/client-core/src/user/components/UserMenu/index.module.scss
+++ b/packages/client-core/src/user/components/UserMenu/index.module.scss
@@ -117,3 +117,9 @@
}
}
}
+
+.center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx
index 960dee16a2..030731e174 100755
--- a/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx
+++ b/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx
@@ -164,8 +164,8 @@ const FriendsMenu = ({ defaultSelectedTab }: Props): JSX.Element => {
.map((item) => ({ id: item.relatedUserId, name: item.relatedUser.name, relationType: 'blocking' as const }))
displayList.push(...blockingList)
} else if (selectedTab.value === 'find') {
- const layerPeers = Engine.instance.worldNetworkState?.peers
- ? Array.from(Engine.instance.worldNetworkState.peers.get({ noproxy: true }).values()).filter(
+ const layerPeers = Engine.instance.worldNetwork
+ ? Object.values(Engine.instance.worldNetwork.peers).filter(
(peer) =>
peer.peerID !== 'server' &&
peer.userId !== userId &&
diff --git a/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx
index 84d4122ed3..1af8083ca3 100755
--- a/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx
+++ b/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx
@@ -42,7 +42,6 @@ import Menu from '@etherealengine/client-core/src/common/components/Menu'
import Text from '@etherealengine/client-core/src/common/components/Text'
import { validateEmail, validatePhoneNumber } from '@etherealengine/common/src/config'
import { useFind } from '@etherealengine/engine/src/common/functions/FeathersHooks'
-import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import Box from '@etherealengine/ui/src/primitives/mui/Box'
import CircularProgress from '@etherealengine/ui/src/primitives/mui/CircularProgress'
@@ -50,6 +49,7 @@ import Icon from '@etherealengine/ui/src/primitives/mui/Icon'
import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton'
import { authenticationSettingPath } from '@etherealengine/engine/src/schemas/setting/authentication-setting.schema'
+import { clientSettingPath } from '@etherealengine/engine/src/schemas/setting/client-setting.schema'
import { initialAuthState, initialOAuthConnectedState } from '../../../../common/initialAuthState'
import { NotificationService } from '../../../../common/services/NotificationService'
import { useUserAvatarThumbnail } from '../../../functions/useUserAvatarThumbnail'
@@ -81,8 +81,8 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => {
const oauthConnectedState = useHookstate(Object.assign({}, initialOAuthConnectedState))
const authState = useHookstate(initialAuthState)
- const engineInitialized = useHookstate(getMutableState(EngineState).isEngineInitialized)
const authSetting = useFind(authenticationSettingPath).data.at(0)
+ const clientSetting = useFind(clientSettingPath).data.at(0)
const loading = useHookstate(getMutableState(AuthState).isProcessing)
const userId = selfUser.id.value
const apiKey = selfUser.apiKey?.token?.value
@@ -355,7 +355,7 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => {
PopupMenuServices.showPopupMenu(UserMenus.AvatarSelect)}
/>
@@ -638,6 +638,9 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => {
)}
>
)}
+
)
diff --git a/packages/client-core/src/user/functions/userPatched.ts b/packages/client-core/src/user/functions/userPatched.ts
index d8f1f26098..a330919bc8 100644
--- a/packages/client-core/src/user/functions/userPatched.ts
+++ b/packages/client-core/src/user/functions/userPatched.ts
@@ -28,7 +28,8 @@ import { WorldState } from '@etherealengine/engine/src/networking/interfaces/Wor
import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState'
import { getMutableState, getState } from '@etherealengine/hyperflux'
-import { UserID, UserType } from '@etherealengine/engine/src/schemas/user/user.schema'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
+import { UserType } from '@etherealengine/engine/src/schemas/user/user.schema'
import { LocationInstanceConnectionService } from '../../common/services/LocationInstanceConnectionService'
import { AuthState } from '../services/AuthService'
@@ -49,9 +50,9 @@ export const userPatched = (user: UserType) => {
if (selfUser.id.value === patchedUser.id) {
getMutableState(AuthState).merge({ user: patchedUser })
const currentInstanceId = patchedUser.instanceAttendance?.find((attendance) => !attendance.isChannel)
- ?.instanceId as UserID
+ ?.instanceId as InstanceID
if (worldHostID && currentInstanceId && worldHostID !== currentInstanceId) {
- LocationInstanceConnectionService.changeActiveConnectionHostId(worldHostID, currentInstanceId)
+ LocationInstanceConnectionService.changeActiveConnectionID(worldHostID, currentInstanceId)
}
}
}
diff --git a/packages/client/src/pages/capture/capture.tsx b/packages/client/src/pages/capture/capture.tsx
index f659544908..2beba1d1eb 100755
--- a/packages/client/src/pages/capture/capture.tsx
+++ b/packages/client/src/pages/capture/capture.tsx
@@ -42,7 +42,7 @@ import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState
import { MediasoupDataProducerConsumerStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupDataProducerConsumerState'
import { MediasoupMediaProducerConsumerStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState'
import { MediasoupTransportStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState'
-import { dispatchAction, getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { dispatchAction, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
import { loadEngineInjection } from '@etherealengine/projects/loadEngineInjection'
import CaptureUI from '@etherealengine/ui/src/pages/Capture'
@@ -63,7 +63,7 @@ const startCaptureSystems = () => {
}
export const initializeEngineForRecorder = async () => {
- if (getMutableState(EngineState).isEngineInitialized.value) return
+ if (getState(EngineState).isEngineInitialized) return
const projects = API.instance.client.service('projects').find()
@@ -98,7 +98,7 @@ export const CaptureLocation = () => {
const engineState = useHookstate(getMutableState(EngineState))
- if (!engineState.isEngineInitialized.value && !engineState.connectedWorld.value) return <>>
+ if (!engineState.connectedWorld.value) return <>>
return
}
diff --git a/packages/common/src/dbmodels/Channel.ts b/packages/common/src/dbmodels/Channel.ts
index bcde924721..2e4d9cfe6e 100644
--- a/packages/common/src/dbmodels/Channel.ts
+++ b/packages/common/src/dbmodels/Channel.ts
@@ -25,6 +25,7 @@ Ethereal Engine. All Rights Reserved.
// TODO: Move it to channel schema once moved to feathers 5 and all dependencies on common packages are removed.
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { OpaqueType } from '../interfaces/OpaqueType'
export type ChannelID = OpaqueType<'ChannelID'> & string
@@ -32,5 +33,5 @@ export type ChannelID = OpaqueType<'ChannelID'> & string
export interface ChannelInterface {
id: string
name: string
- instanceId: string
+ instanceId: InstanceID
}
diff --git a/packages/common/src/interfaces/Instance.ts b/packages/common/src/interfaces/Instance.ts
index 1aa4c5dd87..b8645bf88e 100755
--- a/packages/common/src/interfaces/Instance.ts
+++ b/packages/common/src/interfaces/Instance.ts
@@ -23,12 +23,13 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { LocationData, LocationType } from '@etherealengine/engine/src/schemas/social/location.schema'
import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema'
import { ChannelID } from '../dbmodels/Channel'
export interface Instance {
- id: string
+ id: InstanceID
roomCode: string
currentUsers: number
ipAddress: string
@@ -44,7 +45,7 @@ export interface Instance {
}
export const InstanceSeed: Instance = {
- id: '',
+ id: '' as InstanceID,
roomCode: '',
ipAddress: '',
currentUsers: 0,
diff --git a/packages/editor/src/components/element/ElementList.tsx b/packages/editor/src/components/element/ElementList.tsx
index 5ea45a0924..019c5c9399 100644
--- a/packages/editor/src/components/element/ElementList.tsx
+++ b/packages/editor/src/components/element/ElementList.tsx
@@ -59,13 +59,13 @@ import { TransformComponent } from '@etherealengine/engine/src/transform/compone
import { NO_PROXY, getState, useState } from '@etherealengine/hyperflux'
import MenuItem from '@etherealengine/ui/src/primitives/mui/MenuItem'
-import Tooltip from '@etherealengine/ui/src/primitives/mui/Tooltip'
import Typography from '@etherealengine/ui/src/primitives/mui/Typography'
import { GroupAddOutlined as PlaceHolderIcon } from '@mui/icons-material'
import { IconButton, PopoverPosition } from '@mui/material'
import { BehaveGraphComponent } from '@etherealengine/engine/src/behave-graph/components/BehaveGraphComponent'
+import { EnvmapComponent } from '@etherealengine/engine/src/scene/components/EnvmapComponent'
import { ItemTypes } from '../../constants/AssetTypes'
import { EntityNodeEditor } from '../../functions/ComponentEditors'
import { EditorControlFunctions } from '../../functions/EditorControlFunctions'
@@ -73,6 +73,7 @@ import { getSpawnPositionAtCenter } from '../../functions/screenSpaceFunctions'
import { Button } from '../inputs/Button'
import StringInput from '../inputs/StringInput'
import { ContextMenu } from '../layout/ContextMenu'
+import { InfoTooltip } from '../layout/Tooltip'
import styles from './styles.module.scss'
export type SceneElementType = {
@@ -99,7 +100,7 @@ export const ComponentShelfCategories: Record = {
DirectionalLightComponent,
HemisphereLightComponent
],
- FX: [ParticleSystemComponent],
+ FX: [ParticleSystemComponent, EnvmapComponent],
Scripting: [SystemComponent, BehaveGraphComponent],
Misc: [EnvMapBakeComponent, ScenePreviewCameraComponent, SkyboxComponent, SplineTrackComponent, SplineComponent]
}
@@ -114,6 +115,8 @@ export const addSceneComponentElement = (
}
const SceneElementListItem = ({ item, onClick, onContextMenu }: SceneElementListItemType) => {
+ const { t } = useTranslation()
+
const onClickItem = useCallback(() => {
onClick?.(item)
}, [item, onClick])
@@ -127,7 +130,12 @@ const SceneElementListItem = ({ item, onClick, onContextMenu }: SceneElementList
return (
onContextMenu(event, item)}>
-
+
}
/>
-
+
)
}
@@ -202,7 +210,11 @@ export function ElementList() {
-
+ searchBarState.set(event?.target.value)}
+ placeholder={t('Search...')}
+ />
{Object.entries(validElements.get(NO_PROXY)).map(([category, items]) => (
diff --git a/packages/editor/src/components/graph/BehaveFlow.tsx b/packages/editor/src/components/graph/BehaveFlow.tsx
index 71667adda5..57ada1815d 100644
--- a/packages/editor/src/components/graph/BehaveFlow.tsx
+++ b/packages/editor/src/components/graph/BehaveFlow.tsx
@@ -26,13 +26,18 @@ Ethereal Engine. All Rights Reserved.
import { useForceUpdate } from '@etherealengine/common/src/utils/useForceUpdate'
import { BehaveGraphComponent } from '@etherealengine/engine/src/behave-graph/components/BehaveGraphComponent'
import { BehaveGraphState } from '@etherealengine/engine/src/behave-graph/state/BehaveGraphState'
-import { UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity'
-import { getMutableComponent, hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
+import { Entity, UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity'
+import {
+ getMutableComponent,
+ hasComponent,
+ setComponent
+} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
import React, { useEffect } from 'react'
import AutoSizer from 'react-virtualized-auto-sizer'
import 'reactflow/dist/style.css'
import { SelectionState } from '../../services/SelectionServices'
+import { PropertiesPanelButton } from '../inputs/Button'
import { Flow } from './ee-flow'
import './ee-flow/styles.css'
@@ -44,7 +49,10 @@ const BehaveFlow = () => {
const graphState = getMutableComponent(validEntity ? entity : UndefinedEntity, BehaveGraphComponent)
const forceUpdate = useForceUpdate()
const registry = getState(BehaveGraphState).registry
-
+ const addGraph = () => {
+ setComponent(entity as Entity, BehaveGraphComponent)
+ forceUpdate()
+ }
useEffect(() => {
forceUpdate()
}, [selectionState.objectChangeCounter])
@@ -53,6 +61,22 @@ const BehaveFlow = () => {
{({ width, height }) => (
+ {!validEntity && (
+
{
+ addGraph()
+ }}
+ >
+ {' '}
+ Add Graph
+
+ )}
{validEntity && (
,
areEqual
@@ -532,6 +536,20 @@ export default function HierarchyPanel({
{HierarchyList}
)}
+
diff --git a/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx b/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx
index 5ecdd0c3ae..04ad7c4046 100644
--- a/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx
+++ b/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx
@@ -27,15 +27,23 @@ import React from 'react'
import AccountTreeIcon from '@mui/icons-material/AccountTree'
+import { useTranslation } from 'react-i18next'
import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel'
+import { InfoTooltip } from '../layout/Tooltip'
import styles from '../styles.module.scss'
export const HierarchyPanelTitle = () => {
+ const { t } = useTranslation()
+
return (
- Hierarchy
+
+
+ {t('editor:hierarchy.lbl')}
+
+
)
diff --git a/packages/editor/src/components/inputs/InputGroup.css b/packages/editor/src/components/inputs/InputGroup.css
index 82adc64be7..5db8000148 100755
--- a/packages/editor/src/components/inputs/InputGroup.css
+++ b/packages/editor/src/components/inputs/InputGroup.css
@@ -12,7 +12,7 @@
opacity: 0.3;
}
-.tooltip {
+.label {
color: var(--textColor);
overflow: hidden;
text-overflow: ellipsis;
diff --git a/packages/editor/src/components/inputs/InputGroup.tsx b/packages/editor/src/components/inputs/InputGroup.tsx
index 609127f7db..699b1e47df 100755
--- a/packages/editor/src/components/inputs/InputGroup.tsx
+++ b/packages/editor/src/components/inputs/InputGroup.tsx
@@ -133,9 +133,7 @@ export function InputGroup({ name, children, disabled, info, label, ...rest }: I
-
-
-
+
{info && (
diff --git a/packages/editor/src/components/inputs/NumericInputGroup.tsx b/packages/editor/src/components/inputs/NumericInputGroup.tsx
index a3b6e5033f..0098964fdc 100755
--- a/packages/editor/src/components/inputs/NumericInputGroup.tsx
+++ b/packages/editor/src/components/inputs/NumericInputGroup.tsx
@@ -54,13 +54,15 @@ export interface NumericInputGroupProp {
}
function BaseNumericInputGroup({ name, className, label, ...rest }: NumericInputGroupProp) {
- const { displayPrecision, ...scrubberProps } = rest
+ const { displayPrecision, onChange, value, ...scrubberProps } = rest
return (
- {label}
+
+ {label}
+
diff --git a/packages/editor/src/components/inputs/Scrubber.css b/packages/editor/src/components/inputs/Scrubber.css
index 6b87c3c72c..9267d93b70 100755
--- a/packages/editor/src/components/inputs/Scrubber.css
+++ b/packages/editor/src/components/inputs/Scrubber.css
@@ -1,8 +1,3 @@
-.ScrubberContainer {
- cursor: ew-resize;
- user-select: none;
-}
-
.Cursor {
position: absolute;
width: 20px;
diff --git a/packages/editor/src/components/inputs/Scrubber.tsx b/packages/editor/src/components/inputs/Scrubber.tsx
index 186fd9fc5c..9a039fff50 100755
--- a/packages/editor/src/components/inputs/Scrubber.tsx
+++ b/packages/editor/src/components/inputs/Scrubber.tsx
@@ -37,18 +37,20 @@ import { useHookstate } from '@etherealengine/hyperflux'
import Portal from '../layout/Portal'
type ScrubberContainerProps = {
- tag?: keyof JSX.IntrinsicElements
+ tag?: any
children?: ReactNode
onMouseDown: any
}
-const ScrubberContainer = React.forwardRef(({ tag: Component = 'div', children, ...rest }: ScrubberContainerProps) => {
- return (
-
- {children}
-
- )
-})
+const ScrubberContainer = React.forwardRef(
+ ({ tag: Component = 'div', children, ...rest }: ScrubberContainerProps, ref: React.Ref) => {
+ return (
+
+ {children}
+
+ )
+ }
+)
type CursorProps = {
x: number
@@ -76,7 +78,7 @@ type ScrubberProps = {
onCommit?: (value: any) => void
}
-const Scrubber = ({
+const Scrubber: React.FC = ({
tag,
children,
smallStep,
@@ -92,7 +94,7 @@ const Scrubber = ({
onChange,
onCommit,
...rest
-}: ScrubberProps) => {
+}) => {
const state = useHookstate({
isDragging: false,
startValue: null as number | null,
@@ -110,7 +112,7 @@ const Scrubber = ({
const nextDelta = state.delta.value + event.movementX
const stepSize = getStepSize(event, smallStep, mediumStep, largeStep)
const nextValue = (state.startValue.value as number) + Math.round(nextDelta / (sensitivity || 1)) * stepSize
- const clampedValue = min != null && max != null ? clamp(nextValue, min, max) : nextValue
+ const clampedValue = clamp(nextValue, min ?? -Infinity, max ?? Infinity)
const roundedValue = precision ? toPrecision(clampedValue, precision) : clampedValue
const finalValue = convertTo(roundedValue)
onChange(finalValue)
@@ -153,9 +155,7 @@ const Scrubber = ({
state.delta.set(0)
state.mouseX.set(event.clientX)
state.mouseY.set(event.clientY)
-
scrubberEl?.current?.requestPointerLock()
-
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseup', handleMouseUp)
}
diff --git a/packages/editor/src/components/inputs/SelectInput.tsx b/packages/editor/src/components/inputs/SelectInput.tsx
index 19eedd989f..21ec9f6cbc 100755
--- a/packages/editor/src/components/inputs/SelectInput.tsx
+++ b/packages/editor/src/components/inputs/SelectInput.tsx
@@ -30,11 +30,12 @@ import FormControl from '@mui/material/FormControl'
import MenuItem from '@mui/material/MenuItem'
import Select, { SelectChangeEvent } from '@mui/material/Select'
+import { InfoTooltip } from '../layout/Tooltip'
import styles from './selectInput.module.scss'
interface SelectInputProp {
value: T | string
- options: Array<{ label: string; value: T }>
+ options: Array<{ label: string; value: T; info?: string }>
onChange?: (value: T | string) => void
placeholder?: string
disabled?: boolean
@@ -42,6 +43,7 @@ interface SelectInputProp {
className?: string
isSearchable?: boolean
}
+
export function SelectInput | number | undefined>({
value,
options,
@@ -124,8 +126,14 @@ export function SelectInput | number |
IconComponent={ExpandMoreIcon}
>
{options.map((option, index) => (
-
+ ) : (
+ props.title
+ )
+
const styles = useStyles({})
return (
-
+
{/* Span is required to trigger events like hover in safari for disabled elements */}
{props.children}
diff --git a/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx b/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx
index b4c28b84ec..f54ade0038 100644
--- a/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx
+++ b/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx
@@ -27,15 +27,23 @@ import React from 'react'
import MaterialLibraryIcon from '@mui/icons-material/Yard'
+import { useTranslation } from 'react-i18next'
import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel'
+import { InfoTooltip } from '../layout/Tooltip'
import styles from '../styles.module.scss'
export const MaterialLibraryPanelTitle = () => {
+ const { t } = useTranslation()
+
return (
- Material Library
+
+
+ {t('editor:materialLibrary.lbl')}
+
+
)
diff --git a/packages/editor/src/components/properties/EnvMapEditor.tsx b/packages/editor/src/components/properties/EnvMapEditor.tsx
index 7ac0c1eb53..24225827d5 100644
--- a/packages/editor/src/components/properties/EnvMapEditor.tsx
+++ b/packages/editor/src/components/properties/EnvMapEditor.tsx
@@ -43,6 +43,8 @@ import SelectInput from '../inputs/SelectInput'
import NodeEditor from './NodeEditor'
import { EditorComponentType, updateProperties, updateProperty } from './Util'
+import { SportsBarTwoTone } from '@mui/icons-material'
+
/**
* EnvMapSourceOptions array containing SourceOptions for Envmap
*/
@@ -157,5 +159,5 @@ export const EnvMapEditor: EditorComponentType = (props) => {
)
}
-
+EnvMapEditor.iconComponent = SportsBarTwoTone
export default EnvMapEditor
diff --git a/packages/editor/src/components/properties/NameInputGroup.tsx b/packages/editor/src/components/properties/NameInputGroup.tsx
index e888b5d3f9..cb5b96bf12 100755
--- a/packages/editor/src/components/properties/NameInputGroup.tsx
+++ b/packages/editor/src/components/properties/NameInputGroup.tsx
@@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/
-import React, { useEffect, useState } from 'react'
+import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -63,8 +63,8 @@ export const NameInputGroup: EditorComponentType = (props) => {
const nodeName = useComponent(props.entity, NameComponent)
// temp name is used to store the name of the entity, which is then updated upon onBlur event
- const [tempName, setTempName] = useState(nodeName.value)
- const [focusedNode, setFocusedNode] = useState()
+ const tempName = useHookstate(nodeName.value)
+ const focusedNode = useHookstate(undefined)
const { t } = useTranslation()
useEffect(() => {
@@ -72,32 +72,32 @@ export const NameInputGroup: EditorComponentType = (props) => {
}, [selectionState.objectChangeCounter])
const onObjectChange = (propertyName: string) => {
- if (propertyName === 'name') setTempName(getComponent(props.entity, NameComponent))
+ if (propertyName === 'name') tempName.set(getComponent(props.entity, NameComponent))
}
//function to handle change in name property
const updateName = () => {
- setComponent(props.entity, NameComponent, tempName)
+ setComponent(props.entity, NameComponent, tempName.value)
const group = getOptionalComponent(props.entity, GroupComponent)
- if (group) for (const obj3d of group) obj3d.name = tempName
+ if (group) for (const obj3d of group) obj3d.name = tempName.value
}
//function called when element get focused
const onFocus = () => {
- setFocusedNode(props.entity)
- setTempName(nodeName.value)
+ focusedNode.set(props.entity)
+ tempName.set(nodeName.value)
}
// function to handle onBlur event on name property
const onBlurName = () => {
// Check that the focused node is current node before setting the property.
// This can happen when clicking on another node in the HierarchyPanel
- if (nodeName.value !== tempName && props.entity === focusedNode) {
+ if (nodeName.value !== tempName.value && props.entity === focusedNode.value) {
updateName()
}
- setFocusedNode(undefined)
+ focusedNode.set(undefined)
}
//function to handle keyUp event on name property
@@ -111,8 +111,8 @@ export const NameInputGroup: EditorComponentType = (props) => {
return (
tempName.set(event?.target.value)}
onFocus={onFocus}
onBlur={onBlurName}
onKeyUp={onKeyUpName}
diff --git a/packages/editor/src/components/properties/PropertiesPanelTitle.tsx b/packages/editor/src/components/properties/PropertiesPanelTitle.tsx
index 7f5af81039..9cee6b2753 100644
--- a/packages/editor/src/components/properties/PropertiesPanelTitle.tsx
+++ b/packages/editor/src/components/properties/PropertiesPanelTitle.tsx
@@ -27,15 +27,23 @@ import React from 'react'
import TuneIcon from '@mui/icons-material/Tune'
+import { useTranslation } from 'react-i18next'
import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel'
+import { InfoTooltip } from '../layout/Tooltip'
import styles from '../styles.module.scss'
export const PropertiesPanelTitle = () => {
+ const { t } = useTranslation()
+
return (
- Properties
+
+
+ {t('editor:properties.title')}
+
+
)
diff --git a/packages/editor/src/components/realtime/EditorActiveInstanceService.ts b/packages/editor/src/components/realtime/EditorActiveInstanceService.ts
index 64818ec1a7..553af95742 100644
--- a/packages/editor/src/components/realtime/EditorActiveInstanceService.ts
+++ b/packages/editor/src/components/realtime/EditorActiveInstanceService.ts
@@ -28,10 +28,11 @@ import { AuthState } from '@etherealengine/client-core/src/user/services/AuthSer
import logger from '@etherealengine/common/src/logger'
import { Validator, matches } from '@etherealengine/engine/src/common/functions/MatchesUtils'
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
+import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema'
import { defineAction, defineState, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux'
export type ActiveInstance = {
- id: string
+ id: InstanceID
location: string
currentUsers: number
// todo: assignedAt so we can sort by most recent?
@@ -58,7 +59,7 @@ export const EditorActiveInstanceServiceReceptor = (action): any => {
//Service
export const EditorActiveInstanceService = {
- provisionServer: async (locationId: string, instanceId: string, sceneId: string) => {
+ provisionServer: async (locationId: string, instanceId: InstanceID, sceneId: string) => {
logger.info({ locationId, instanceId, sceneId }, 'Provision World Server Editor')
const token = getState(AuthState).authUser.accessToken
const provisionResult = await Engine.instance.api.service('instance-provision').find({
diff --git a/packages/editor/src/components/realtime/WorldInstanceConnection.tsx b/packages/editor/src/components/realtime/WorldInstanceConnection.tsx
index dcef1863bc..0d74face5a 100644
--- a/packages/editor/src/components/realtime/WorldInstanceConnection.tsx
+++ b/packages/editor/src/components/realtime/WorldInstanceConnection.tsx
@@ -79,7 +79,7 @@ export const WorldInstanceConnection = () => {
// const decrementPage = () => { }
// const incrementPage = () => { }
- const worldNetworkHostId = Engine.instance.worldNetwork?.hostId
+ const worldNetworkHostId = Engine.instance.worldNetwork?.id
const networkState = useWorldNetwork()
const getIcon = () => {
diff --git a/packages/editor/src/components/toolbar/tools/GridTool.tsx b/packages/editor/src/components/toolbar/tools/GridTool.tsx
index 4d76326c5f..ebb26f5ecf 100644
--- a/packages/editor/src/components/toolbar/tools/GridTool.tsx
+++ b/packages/editor/src/components/toolbar/tools/GridTool.tsx
@@ -50,7 +50,7 @@ const GridTool = () => {
return (
)
}
diff --git a/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx b/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx
index b7b7549fcc..649d09ccc5 100644
--- a/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx
+++ b/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx
@@ -31,10 +31,13 @@ import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import SelectAllIcon from '@mui/icons-material/SelectAll'
import SquareFootIcon from '@mui/icons-material/SquareFoot'
+import { useTranslation } from 'react-i18next'
import { InfoTooltip } from '../../layout/Tooltip'
import * as styles from '../styles.module.scss'
export const HelperToggleTool = () => {
+ const { t } = useTranslation()
+
const rendererState = useHookstate(getMutableState(RendererState))
const toggleDebug = () => {
@@ -48,7 +51,10 @@ export const HelperToggleTool = () => {
return (
<>
-
+