{props.message.text}
diff --git a/packages/ui/src/components/editor/input/Array/index.stories.tsx b/packages/ui/src/components/editor/input/Array/index.stories.tsx
new file mode 100644
index 0000000000..ef2f098940
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Array/index.stories.tsx
@@ -0,0 +1,51 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Array',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ArrayInput',
+ jest: 'Array.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = {
+ args: {
+ label: 'Source Path',
+ containerClassName: 'w-96',
+ values: ['test name 1', 'test value 2', 'test 3', 'test 4'],
+ inputLabel: 'Path'
+ }
+}
diff --git a/packages/ui/src/components/editor/input/Array/index.tsx b/packages/ui/src/components/editor/input/Array/index.tsx
new file mode 100644
index 0000000000..cbb58bf6ed
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Array/index.tsx
@@ -0,0 +1,83 @@
+/*
+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 React from 'react'
+import { HiPlus } from 'react-icons/hi2'
+import { PiTrashSimple } from 'react-icons/pi'
+import Input from '../../../../primitives/tailwind/Input'
+import Label from '../../../../primitives/tailwind/Label'
+import Text from '../../../../primitives/tailwind/Text'
+
+export interface ArrayInputProps {
+ name?: string
+ label: string
+ containerClassName?: string
+ values: string[]
+ onChange: (values: string[]) => void
+ inputLabel?: string
+}
+
+// TODO: file and drag and drop functionality
+
+export default function ArrayInputGroup({
+ name,
+ label,
+ containerClassName,
+ values,
+ onChange,
+ inputLabel
+}: ArrayInputProps) {
+ const handleChange = (value: string, index: number, addRemove?: 'add' | 'remove') => {
+ if (addRemove === 'add') {
+ onChange([...values, value])
+ } else if (addRemove === 'remove') {
+ onChange(values.filter((_, idx) => idx !== index))
+ } else {
+ onChange(values.map((v, idx) => (idx === index ? value : v)))
+ }
+ }
+
+ return (
+
+
+ {label}
+ handleChange('', 0, 'add')} />
+
+
+ {values.map((value, idx) => (
+
+ {inputLabel &&
}
+
handleChange(event.target.value, idx)}
+ />
+
handleChange('', idx, 'remove')} />
+
+ ))}
+
+
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Audio/index.stories.tsx b/packages/ui/src/components/editor/input/Audio/index.stories.tsx
new file mode 100644
index 0000000000..068f4c8f66
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Audio/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Audio',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'AudioInput',
+ jest: 'Audio.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component }
diff --git a/packages/ui/src/components/editor/input/Audio/index.tsx b/packages/ui/src/components/editor/input/Audio/index.tsx
new file mode 100644
index 0000000000..d5a0c279ea
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Audio/index.tsx
@@ -0,0 +1,36 @@
+/*
+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 { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { AudioFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import React from 'react'
+import { FileBrowserInput } from '../FileBrowser'
+import { StringInputProps } from '../String'
+
+export function AudioInput({ ...rest }: StringInputProps) {
+ return
+}
+
+export default AudioInput
diff --git a/packages/ui/src/components/editor/input/Behavior/index.tsx b/packages/ui/src/components/editor/input/Behavior/index.tsx
new file mode 100644
index 0000000000..876e388d53
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Behavior/index.tsx
@@ -0,0 +1,510 @@
+/*
+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 React, { useCallback } from 'react'
+import { Texture, Vector2, Vector3 } from 'three'
+
+import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
+import {
+ ApplyForceBehaviorJSON,
+ ApplySequencesJSON,
+ BehaviorJSON,
+ BehaviorJSONDefaults,
+ ChangeEmitDirectionBehaviorJSON,
+ ColorGeneratorJSON,
+ ColorOverLifeBehaviorJSON,
+ EmitSubParticleSystemBehaviorJSON,
+ FrameOverLifeBehaviorJSON,
+ GravityForceBehaviorJSON,
+ NoiseBehaviorJSON,
+ OrbitOverLifeBehaviorJSON,
+ Rotation3DOverLifeBehaviorJSON,
+ RotationGeneratorJSON,
+ RotationOverLifeBehaviorJSON,
+ SizeOverLifeBehaviorJSON,
+ SpeedOverLifeBehaviorJSON,
+ TextureSequencerJSON,
+ TurbulenceFieldBehaviorJSON,
+ ValueGeneratorJSON,
+ WidthOverLengthBehaviorJSON
+} from '@etherealengine/engine/src/scene/components/ParticleSystemComponent'
+import { State } from '@etherealengine/hyperflux'
+import createReadableTexture from '@etherealengine/spatial/src/renderer/functions/createReadableTexture'
+import BooleanInput from '../Boolean'
+import ColorGenerator from '../Generator/Color'
+import RotationGenerator from '../Generator/Rotation'
+import ValueGenerator from '../Generator/Value'
+import InputGroup from '../Group'
+import NumericInput from '../Numeric'
+import SelectInput from '../Select'
+import Vector3Input from '../Vector3'
+
+export default function BehaviorInput({
+ scope,
+ value,
+ onChange
+}: {
+ scope: State
+ value: BehaviorJSON
+ onChange: (scope: State) => (value: any) => void
+}) {
+ const onChangeBehaviorType = useCallback(() => {
+ const onChangeType = onChange(scope.type)
+ return (type: typeof value.type) => {
+ const nuVals = JSON.parse(JSON.stringify(BehaviorJSONDefaults[type]))
+ scope.set(nuVals)
+ onChangeType(type)
+ }
+ }, [])
+
+ const onChangeVec3 = useCallback((scope: State) => {
+ const thisOnChange = onChange(scope)
+ return (vec3: Vector3) => {
+ thisOnChange([...vec3.toArray()])
+ }
+ }, [])
+
+ const applyForceInput = useCallback(
+ (scope: State) => {
+ const forceScope = scope as State
+ const value = forceScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const noiseInput = useCallback(
+ (scope: State) => {
+ const noiseScope = scope as State
+ const value = noiseScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const turbulenceFieldInput = useCallback(
+ (scope: State) => {
+ const turbulenceScope = scope as State
+ const value = turbulenceScope.value
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const gravityForceInput = useCallback(
+ (scope: State) => {
+ const gravityScope = scope as State
+ const value = gravityScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const colorOverLifeInput = useCallback(
+ (scope: State) => {
+ const colorScope = scope as State
+ const value = colorScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const rotationOverLifeInput = useCallback(
+ (scope: State) => {
+ const rotationScope = scope as State
+ const value = rotationScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const rotation3DOverLifeInput = useCallback(
+ (scope: State) => {
+ const rotation3DScope = scope as State
+ const rotation3D = rotation3DScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const sizeOverLifeInput = useCallback(
+ (scope: State) => {
+ const sizeScope = scope as State
+ const value = sizeScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const speedOverLifeInput = useCallback(
+ (scope: State) => {
+ const speedScope = scope as State
+ const value = speedScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const frameOverLifeInput = useCallback(
+ (scope: State) => {
+ const frameScope = scope as State
+ const value = frameScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const orbitOverLifeInput = useCallback(
+ (scope: State) => {
+ const orbitScope = scope as State
+ const value = orbitScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const widthOverLength = useCallback(
+ (scope: State) => {
+ const widthScope = scope as State
+ const value = widthScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const changeEmitDirectionInput = useCallback(
+ (scope: State) => {
+ const changeEmitDirectionScope = scope as State
+ const value = changeEmitDirectionScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const emitSubParticleSystemInput = useCallback(
+ (scope: State) => {
+ const emitSubParticleSystemScope = scope as State
+ const value = emitSubParticleSystemScope.value
+ return (
+ <>
+
+ <>>
+ {/* @todo */}
+ {/* */}
+
+ >
+ )
+ },
+ [scope]
+ )
+
+ const onChangeSequenceTexture = useCallback(
+ (scope: State) => {
+ const thisOnChange = onChange(scope.src)
+ return (src: string) => {
+ AssetLoader.load(src, (texture: Texture) => {
+ createReadableTexture(texture, { canvas: true, flipY: true }).then((readableTexture: Texture) => {
+ const canvas = readableTexture.image as HTMLCanvasElement
+ const ctx = canvas.getContext('2d')!
+ const width = canvas.width
+ const height = canvas.height
+ const imageData = ctx.getImageData(0, 0, width, height)
+ const locations: Vector2[] = []
+ const threshold = scope.threshold.value
+ for (let i = 0; i < imageData.height; i++) {
+ for (let j = 0; j < imageData.width; j++) {
+ imageData.data[(i * imageData.width + j) * 4 + 3] > threshold &&
+ locations.push(new Vector2(j, imageData.height - i))
+ }
+ }
+ canvas.remove()
+ scope.locations.set(locations)
+ })
+ })
+ thisOnChange(src)
+ }
+ },
+ [scope]
+ )
+
+ const onAddTextureSequencer = useCallback(() => {
+ const sequencersScope = scope as State
+ const sequencers = sequencersScope.value
+ const thisOnChange = onChange(sequencersScope.sequencers)
+ return () => {
+ const nuSequencer = {
+ range: { a: 0, b: 1 },
+ sequencer: {
+ scaleX: 1,
+ scaleY: 1,
+ position: [0, 0, 0],
+ src: '',
+ locations: [],
+ threshold: 0.5
+ }
+ }
+ const nuSequencers = [...JSON.parse(JSON.stringify(sequencers.sequencers)), nuSequencer]
+ thisOnChange(nuSequencers)
+ }
+ }, [scope])
+
+ // const applySequencesInput = useCallback(
+ // (scope: State) => {
+ // const applySequencesScope = scope as State
+ // const value = applySequencesScope.value
+ // return (
+ // <>
+ //
+ //
+ // ) => {
+ // const sequencer = sequencerScope.value
+ // return (
+ // <>
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // >
+ // )
+ // }}
+ // />
+ // >
+ // )
+ // },
+ // [scope]
+ // )
+
+ const inputs = {
+ ApplyForce: applyForceInput,
+ Noise: noiseInput,
+ TurbulenceField: turbulenceFieldInput,
+ GravityForce: gravityForceInput,
+ ColorOverLife: colorOverLifeInput,
+ RotationOverLife: rotationOverLifeInput,
+ SizeOverLife: sizeOverLifeInput,
+ SpeedOverLife: speedOverLifeInput,
+ FrameOverLife: frameOverLifeInput,
+ OrbitOverLife: orbitOverLifeInput,
+ Rotation3DOverLife: rotation3DOverLifeInput,
+ WidthOverLength: widthOverLength,
+ ChangeEmitDirection: changeEmitDirectionInput,
+ EmitSubParticleSystem: emitSubParticleSystemInput
+ }
+
+ return (
+ <>
+
+
+
+ {inputs[value.type](scope)}
+ >
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Boolean/index.stories.tsx b/packages/ui/src/components/editor/input/Boolean/index.stories.tsx
new file mode 100644
index 0000000000..0486cd2abc
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Boolean/index.stories.tsx
@@ -0,0 +1,47 @@
+/*
+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 Component, { BooleanInputProp } from './index'
+
+const argTypes: BooleanInputProp = { value: false, onChange: () => {} }
+
+export default {
+ title: 'Editor/Input/Boolean',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'BooleanInput',
+ jest: 'Boolean.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = {
+ args: {
+ value: false
+ }
+}
diff --git a/packages/ui/src/components/editor/input/Boolean/index.tsx b/packages/ui/src/components/editor/input/Boolean/index.tsx
new file mode 100644
index 0000000000..a299ab4002
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Boolean/index.tsx
@@ -0,0 +1,59 @@
+/*
+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 React from 'react'
+import { twMerge } from 'tailwind-merge'
+import Checkbox from '../../../../primitives/tailwind/Checkbox'
+
+export interface BooleanInputProp {
+ value: boolean
+ onChange: (value: boolean) => void
+ onRelease?: (value: boolean) => void
+ disabled?: boolean
+ className?: string
+}
+
+export const BooleanInput = (props: BooleanInputProp) => {
+ const onBlur = () => {
+ if (props.onRelease) props.onRelease(props.value)
+ }
+
+ return (
+
+ )
+}
+
+export default BooleanInput
diff --git a/packages/ui/src/components/editor/input/Euler/index.stories.tsx b/packages/ui/src/components/editor/input/Euler/index.stories.tsx
new file mode 100644
index 0000000000..dce86acc04
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Euler/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Euler',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'EulerInput',
+ jest: 'Euler.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Euler/index.tsx b/packages/ui/src/components/editor/input/Euler/index.tsx
new file mode 100644
index 0000000000..4295d7e350
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Euler/index.tsx
@@ -0,0 +1,117 @@
+/*
+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 { useHookstate } from '@etherealengine/hyperflux'
+import { Q_IDENTITY } from '@etherealengine/spatial/src/common/constants/MathConstants'
+import React, { useCallback, useEffect } from 'react'
+import { Euler, Quaternion, MathUtils as _Math } from 'three'
+import NumericInput from '../Numeric'
+import { Vector3Scrubber } from '../Vector3'
+
+const { RAD2DEG, DEG2RAD } = _Math
+/**
+ * Type aliase created EulerInputProps.
+ *
+ * @type {Object}
+ */
+type EulerInputProps = {
+ quaternion: Quaternion
+ onChange?: (euler: Euler) => any
+ onRelease?: () => void
+ unit?: string
+}
+
+/**
+ * FileIEulerInputnput used to show EulerInput.
+ *
+ * @type {Object}
+ */
+export const EulerInput = (props: EulerInputProps) => {
+ const euler = useHookstate(new Euler().setFromQuaternion(props.quaternion, 'YXZ'))
+
+ useEffect(() => {
+ euler.value.setFromQuaternion(props.quaternion, 'YXZ')
+ }, [props])
+
+ const onSetEuler = useCallback(
+ (component: keyof typeof euler) => (value: number) => {
+ const radVal = value * DEG2RAD
+ euler[component].value !== radVal && (euler[component].set(radVal) || props.onChange?.(euler.value))
+ },
+ []
+ )
+
+ return (
+
+ props.onRelease?.()}
+ unit={props.unit}
+ prefix={
+
+ }
+ />
+ props.onRelease?.()}
+ unit={props.unit}
+ prefix={
+
+ }
+ />
+ props.onRelease?.()}
+ unit={props.unit}
+ prefix={
+
+ }
+ />
+
+ )
+}
+
+EulerInput.defaultProps = {
+ quaternion: Q_IDENTITY
+}
+export default EulerInput
diff --git a/packages/ui/src/components/editor/input/FileBrowser/index.stories.tsx b/packages/ui/src/components/editor/input/FileBrowser/index.stories.tsx
new file mode 100644
index 0000000000..e91a9de790
--- /dev/null
+++ b/packages/ui/src/components/editor/input/FileBrowser/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/FileBrowser',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'FileBrowserInput',
+ jest: 'FileBrowser.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/FileBrowser/index.tsx b/packages/ui/src/components/editor/input/FileBrowser/index.tsx
new file mode 100644
index 0000000000..67e9db394b
--- /dev/null
+++ b/packages/ui/src/components/editor/input/FileBrowser/index.tsx
@@ -0,0 +1,133 @@
+/*
+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 React from 'react'
+import { useDrop } from 'react-dnd'
+
+import config from '@etherealengine/common/src/config'
+//import useUpload from '../assets/useUpload'
+import useUpload from '@etherealengine/editor/src/components/assets/useUpload'
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { AllFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import { ControlledStringInput, StringInputProps } from '../String'
+
+export type FileBrowserInputProps = StringInputProps & { acceptFileTypes: string[]; acceptDropItems: string[] }
+
+/**
+ * Function component used for rendering FileBrowserInput.
+ */
+export function FileBrowserInput({
+ onRelease,
+ value,
+ acceptFileTypes,
+ acceptDropItems,
+ ...rest
+}: FileBrowserInputProps) {
+ const uploadOptions = {
+ multiple: false,
+ accepts: acceptFileTypes
+ }
+ const onUpload = useUpload(uploadOptions)
+
+ // todo fix for invalid URLs
+ const assetIsExternal = value && !value?.includes(config.client.fileServer) && !value.includes('blob:https://')
+ const uploadExternalAsset = () => {
+ onUpload([
+ {
+ isFile: true,
+ name: value?.split('/').pop(),
+ file: async (onSuccess, onFail) => {
+ try {
+ const asset = await fetch(value!)
+ const blob = await asset.blob()
+ const file = new File([blob], value!.split('/').pop()!)
+ onSuccess(file)
+ } catch (error) {
+ if (onFail) onFail(error)
+ else throw error
+ }
+ }
+ } as Partial
+ ] as any).then((assets) => {
+ if (assets) {
+ onRelease?.(assets[0])
+ }
+ })
+ }
+
+ const [{ canDrop, isOver }, dropRef] = useDrop({
+ accept: [...acceptDropItems, ItemTypes.File],
+ async drop(item: any, monitor) {
+ const isDropType = acceptDropItems.find((element) => element === item.type)
+ if (isDropType) {
+ // Below url fix is applied when item is folder
+ let url = item.url
+ if (!url.endsWith(item.fullName)) {
+ url += item.fullName
+ }
+
+ onRelease?.(url)
+ } else {
+ // https://github.com/react-dnd/react-dnd/issues/1345#issuecomment-538728576
+ const dndItem: any = monitor.getItem()
+ const entries = Array.from(dndItem.items).map((item: any) => item.webkitGetAsEntry())
+
+ onUpload(entries).then((assets) => {
+ if (assets) {
+ onRelease?.(assets[0])
+ }
+ })
+ }
+ },
+ collect: (monitor) => ({
+ canDrop: monitor.canDrop(),
+ isOver: monitor.isOver()
+ })
+ })
+
+ return (
+ <>
+
+ {/*assetIsExternal && (
+
+
+
+ )*/}
+ >
+ )
+}
+
+FileBrowserInput.defaultProps = {
+ acceptFileTypes: AllFileTypes,
+ acceptDropItems: AllFileTypes
+}
+
+export default FileBrowserInput
diff --git a/packages/ui/src/components/editor/input/Folder/index.stories.tsx b/packages/ui/src/components/editor/input/Folder/index.stories.tsx
new file mode 100644
index 0000000000..eed845941a
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Folder/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Folder',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'FolderInput',
+ jest: 'Folder.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Folder/index.tsx b/packages/ui/src/components/editor/input/Folder/index.tsx
new file mode 100644
index 0000000000..5cbc1daac8
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Folder/index.tsx
@@ -0,0 +1,38 @@
+/*
+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 { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { AllFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import React from 'react'
+import FileBrowserInput from '../FileBrowser'
+import { StringInputProps } from '../String'
+
+export function FolderInput({ ...rest }: StringInputProps) {
+ return
+}
+
+FolderInput.defaultProps = {}
+
+export default FolderInput
diff --git a/packages/ui/src/components/editor/input/Generator/Color/index.tsx b/packages/ui/src/components/editor/input/Generator/Color/index.tsx
new file mode 100644
index 0000000000..8078c8e485
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Generator/Color/index.tsx
@@ -0,0 +1,235 @@
+/*
+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 React, { useCallback } from 'react'
+import { Color } from 'three'
+
+import {
+ ColorGeneratorJSON,
+ ColorGeneratorJSONDefaults,
+ ColorGradientFunctionJSON,
+ ColorGradientJSON,
+ ColorJSON,
+ ColorRangeJSON,
+ ConstantColorJSON,
+ RandomColorJSON
+} from '@etherealengine/engine/src/scene/components/ParticleSystemComponent'
+import { State } from '@etherealengine/hyperflux/functions/StateFunctions'
+import Typography from '@etherealengine/ui/src/primitives/mui/Typography'
+
+import { Grid } from '@mui/material'
+import Button from '../../../../../primitives/tailwind/Button'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../Group'
+import NumericInput from '../../Numeric'
+import SelectInput from '../../Select'
+
+export function ColorJSONInput({ value, onChange }: { value: ColorJSON; onChange: (color: ColorJSON) => void }) {
+ return (
+ <>
+
+ {
+ onChange({ r: color.r, g: color.g, b: color.b, a: value.a })
+ }}
+ />
+
+
+ onChange({ r: value.r, g: value.g, b: value.b, a: alpha })}
+ />
+
+ >
+ )
+}
+
+export default function ColorGenerator({
+ scope,
+ value,
+ onChange
+}: {
+ scope: State
+ value: ColorGeneratorJSON
+ onChange: (scope: State) => (value: any) => void
+}) {
+ const onChangeType = useCallback(() => {
+ const thisOnChange = onChange(scope.type)
+ return (type: typeof value.type) => {
+ scope.set(ColorGeneratorJSONDefaults[type])
+ thisOnChange(type)
+ }
+ }, [])
+
+ const ConstantColorInput = useCallback(() => {
+ const constantScope = scope as State
+ const constant = constantScope.value
+ return
+ }, [scope])
+
+ const ColorRangeInput = useCallback(() => {
+ const rangeScope = scope as State
+ const range = rangeScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ }, [scope])
+
+ const RandomColorInput = useCallback(() => {
+ const randomScope = scope as State
+ const random = randomScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ }, [scope])
+
+ const onRemoveGradient = useCallback((element: State) => {
+ const gradientScope = scope as State
+ const gradient = gradientScope.value
+ const thisOnChange = onChange(gradientScope.functions)
+ return () => {
+ const nuFunctions = gradient.functions.filter((item) => item !== element.value)
+ thisOnChange(JSON.parse(JSON.stringify(nuFunctions)))
+ }
+ }, [])
+
+ const GradientInput = useCallback(() => {
+ const gradientScope = scope as State
+ const gradient = gradientScope.value
+ return (
+
+
+
+ {gradient.functions.map((item, index) => (
+
+
+
+ Start
+
+
+
+
+
+ A
+
+
+
+
+
+ B
+
+
+
+
+
+
+
+ ))}
+
+ )
+ }, [scope])
+
+ const colorInputs = {
+ ConstantColor: ConstantColorInput,
+ ColorRange: ColorRangeInput,
+ RandomColor: RandomColorInput,
+ Gradient: GradientInput
+ }
+
+ return (
+
+
+
+
+ {colorInputs[value.type]()}
+
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Generator/Rotation/index.tsx b/packages/ui/src/components/editor/input/Generator/Rotation/index.tsx
new file mode 100644
index 0000000000..135cb55bf0
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Generator/Rotation/index.tsx
@@ -0,0 +1,129 @@
+/*
+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 React, { useCallback } from 'react'
+import { Vector3 } from 'three'
+
+import {
+ AxisAngleGeneratorJSON,
+ EulerGeneratorJSON,
+ RotationGeneratorJSON,
+ RotationGeneratorJSONDefaults,
+ ValueGeneratorJSON
+} from '@etherealengine/engine/src/scene/components/ParticleSystemComponent'
+import { State } from '@etherealengine/hyperflux'
+import InputGroup from '../../Group'
+import SelectInput from '../../Select'
+import Vector3Input from '../../Vector3'
+import ValueGenerator from '../Value'
+
+export default function RotationGenerator({
+ scope,
+ value,
+ onChange
+}: {
+ scope: State
+ value: RotationGeneratorJSON
+ onChange: (scope: State) => (value: any) => void
+}) {
+ const onChangeVec3 = useCallback((scope: State) => {
+ const thisOnChange = onChange(scope)
+ return (vec3: Vector3) => {
+ thisOnChange([...vec3.toArray()])
+ }
+ }, [])
+
+ const AxisAngleGeneratorInput = useCallback(() => {
+ const axisAngleScope = scope as State
+ const axisAngle = axisAngleScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ }, [])
+
+ const EulerGeneratorInput = useCallback(() => {
+ const eulerScope = scope as State
+ const euler = eulerScope.value
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )
+ }, [])
+
+ const RandomQuatGeneratorInput = useCallback(() => {
+ return <>>
+ }, [])
+
+ const onChangeRotationType = useCallback(() => {
+ const thisOnChange = onChange(scope.type)
+ return (type: typeof value.type) => {
+ scope.set(RotationGeneratorJSONDefaults[type])
+ thisOnChange(type)
+ }
+ }, [])
+
+ const rotationInputs = {
+ AxisAngle: AxisAngleGeneratorInput,
+ Euler: EulerGeneratorInput,
+ RandomQuat: RandomQuatGeneratorInput
+ }
+
+ return (
+ <>
+
+
+
+ {rotationInputs[value.type]()}
+ >
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Generator/Value/index.tsx b/packages/ui/src/components/editor/input/Generator/Value/index.tsx
new file mode 100644
index 0000000000..e07ffabea6
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Generator/Value/index.tsx
@@ -0,0 +1,198 @@
+/*
+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 React, { useCallback } from 'react'
+
+import {
+ BezierFunctionJSON,
+ ConstantValueJSON,
+ IntervalValueJSON,
+ PiecewiseBezierValueJSON,
+ ValueGeneratorJSON,
+ ValueGeneratorJSONDefaults
+} from '@etherealengine/engine/src/scene/components/ParticleSystemComponent'
+import { State } from '@etherealengine/hyperflux/functions/StateFunctions'
+import Button from '../../../../../primitives/tailwind/Button'
+import InputGroup from '../../Group'
+import NumericInput from '../../Numeric'
+import SelectInput from '../../Select'
+
+export default function ValueGenerator({
+ scope,
+ value,
+ onChange
+}: {
+ scope: State
+ value: ValueGeneratorJSON
+ onChange: (scope: State) => (value: any) => void
+}) {
+ const onChangeType = useCallback(() => {
+ const thisOnChange = onChange(scope.type)
+ return (type: typeof value.type) => {
+ scope.set(JSON.parse(JSON.stringify(ValueGeneratorJSONDefaults[type])))
+ thisOnChange(type)
+ }
+ }, [])
+
+ const onAddBezier = useCallback(() => {
+ const bezierScope = scope as State
+ const thisOnChange = onChange(bezierScope.functions)
+ return () => {
+ const bezierJson = value as PiecewiseBezierValueJSON
+ const nuFunctions = [
+ ...JSON.parse(JSON.stringify(bezierJson.functions)),
+ {
+ function: {
+ p0: 0,
+ p1: 0,
+ p2: 1,
+ p3: 1
+ },
+ start: 0
+ } as BezierFunctionJSON
+ ]
+ thisOnChange(nuFunctions)
+ }
+ }, [])
+
+ const onRemoveBezier = useCallback((element: State) => {
+ const bezierScope = scope as State
+ const bezier = bezierScope.value
+ const thisOnChange = onChange(bezierScope.functions)
+ return () => {
+ const nuFunctions = bezier.functions.filter((f) => f !== element.value)
+ thisOnChange(JSON.parse(JSON.stringify(nuFunctions)))
+ }
+ }, [])
+
+ const ConstantInput = useCallback(() => {
+ const constantScope = scope as State
+ const constant = constantScope.value
+ return (
+ <>
+
+
+
+ >
+ )
+ }, [scope])
+
+ const IntervalInput = useCallback(() => {
+ const intervalScope = scope as State
+ const interval = intervalScope.value
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+ }, [scope])
+
+ const BezierInput = useCallback(() => {
+ const bezierScope = scope as State
+ return (
+
+
+
+ {/*
) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ />*/}
+
+ )
+ }, [scope])
+
+ const valueInputs = {
+ ConstantValue: ConstantInput,
+ IntervalValue: IntervalInput,
+ PiecewiseBezier: BezierInput
+ }
+
+ return (
+
+
+
+
+ {valueInputs[value.type]()}
+
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Group/index.stories.tsx b/packages/ui/src/components/editor/input/Group/index.stories.tsx
new file mode 100644
index 0000000000..848ad6283d
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Group/index.stories.tsx
@@ -0,0 +1,48 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Group',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'InputGroup',
+ jest: 'Group.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = {
+ args: {
+ label: 'group label',
+ info: 'input group info'
+ }
+}
diff --git a/packages/ui/src/components/editor/input/Group/index.tsx b/packages/ui/src/components/editor/input/Group/index.tsx
new file mode 100644
index 0000000000..5dfbdf22a0
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Group/index.tsx
@@ -0,0 +1,144 @@
+/*
+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 React from 'react'
+
+import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
+import { CiCircleInfo } from 'react-icons/ci'
+import { twMerge } from 'tailwind-merge'
+import Label from '../../../../primitives/tailwind/Label'
+import Tooltip from '../../../../primitives/tailwind/Tooltip'
+import { InfoTooltip } from '../../layout/Tooltip'
+
+/**
+ * Used to provide styles for InputGroupContainer div.
+ */
+export const InputGroupContainer = ({ disabled = false, children, ...rest }) => (
+
+ {children}
+
+)
+
+/**
+ * Used to provide styles for InputGroupContent div.
+ */
+export const InputGroupContent = ({ extraClassName = '', children }) => (
+ label]:block [&>label]:w-[35%] [&>label]:pb-0.5 [&>label]:pt-1 [&>label]:text-neutral-400',
+ "font-['Figtree'] text-xs font-normal text-neutral-400",
+ '[&>*:first-child]:max-w-[calc(100%_-_2px)]',
+ extraClassName
+ )}
+ >
+ {children}
+
+)
+
+export const InputGroupVerticalContainer = ({ disabled = false, children }) => (
+ label]:block [&>label]:w-[35%] [&>label]:pb-0.5 [&>label]:pt-1 [&>label]:text-[color:var(--textColor)]'
+ )}
+ >
+ {children}
+
+)
+
+export const InputGroupVerticalContainerWide = ({ disabled = false, children }) => (
+ label]:block [&>label]:w-full [&>label]:pb-0.5 [&>label]:pt-1 [&>label]:text-[color:var(--textColor)]'
+ )}
+ >
+ {children}
+
+)
+
+export const InputGroupVerticalContent = ({ children }) => {children}
+/**
+ * Used to provide styles for InputGroupInfoIcon div.
+ */
+// change later
+// .info text-[color:var(--textColor)] h-4 w-auto ml-[5px]
+export const InputGroupInfoIcon = ({ onClick = () => {} }) => (
+
+)
+
+interface InputGroupInfoProp {
+ info: string | JSX.Element
+}
+
+/**
+ * Used to render InfoTooltip component.
+ */
+export function InputGroupInfo({ info }: InputGroupInfoProp) {
+ return (
+
+
+
+ )
+}
+
+export interface InputGroupProps {
+ name?: string
+ label: string
+ info?: string
+ disabled?: boolean
+ children: React.ReactNode
+ className?: string
+ labelClassName?: string
+ infoClassName?: string
+}
+
+/**
+ * InputGroup used to render the view of component.
+ */
+export function InputGroup({ children, info, label, className, labelClassName, infoClassName }: InputGroupProps) {
+ return (
+
+
+ {info && (
+
+
+
+ )}
+ {children}
+
+ )
+}
+
+export default InputGroup
diff --git a/packages/ui/src/components/editor/input/Image/Preview/index.stories.tsx b/packages/ui/src/components/editor/input/Image/Preview/index.stories.tsx
new file mode 100644
index 0000000000..b78f3bfa0e
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Image/Preview/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/ImagePreview',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ImagePreviewInput',
+ jest: 'ImagePreview.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Image/Preview/index.tsx b/packages/ui/src/components/editor/input/Image/Preview/index.tsx
new file mode 100644
index 0000000000..2e16a58231
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Image/Preview/index.tsx
@@ -0,0 +1,77 @@
+/*
+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 React from 'react'
+
+import ImageInput from '..'
+import InputGroup from '../../Group'
+import { StringInputProps } from '../../String'
+
+export const ImageContainer = ({ children }) => {
+ return {children}
+}
+
+export default function ImagePreviewInput({
+ value,
+ onRelease,
+ label,
+ previewOnly,
+ ...rest
+}: StringInputProps & { label?: string; previewOnly?: boolean }) {
+ return (
+
+ {label && (
+ {label}
+ )}
+
+
+
+
+
+
+
+
+ {(previewOnly === undefined || previewOnly === false) && (
+
+
+
+ )}
+
+
+ )
+}
+
+ImagePreviewInput.defaultProps = {
+ value: 'https://fastly.picsum.photos/id/1065/200/300.jpg?hmac=WvioY_uR2xNPKNxQoR9y1HhWkuV6_v7rB23clZYh0Ks',
+ onRelease: () => {}
+}
+
+export function ImagePreviewInputGroup({ name, label, value, onRelease, ...rest }) {
+ return (
+
+
+
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Image/index.stories.tsx b/packages/ui/src/components/editor/input/Image/index.stories.tsx
new file mode 100644
index 0000000000..b9eafd8965
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Image/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Image',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ImageInput',
+ jest: 'Image.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component }
diff --git a/packages/ui/src/components/editor/input/Image/index.tsx b/packages/ui/src/components/editor/input/Image/index.tsx
new file mode 100644
index 0000000000..8d43816cb4
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Image/index.tsx
@@ -0,0 +1,36 @@
+/*
+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 { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { ImageFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import React from 'react'
+import FileBrowserInput from '../FileBrowser'
+import { StringInputProps } from '../String'
+
+export function ImageInput({ ...rest }: StringInputProps) {
+ return
+}
+
+export default ImageInput
diff --git a/packages/ui/src/components/editor/input/Material/index.stories.tsx b/packages/ui/src/components/editor/input/Material/index.stories.tsx
new file mode 100644
index 0000000000..1208b1b1fb
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Material/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Material',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'MaterialInput',
+ jest: 'Material.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Material/index.tsx b/packages/ui/src/components/editor/input/Material/index.tsx
new file mode 100644
index 0000000000..d179b7f3a2
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Material/index.tsx
@@ -0,0 +1,65 @@
+/*
+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 React from 'react'
+import { DropTargetMonitor, useDrop } from 'react-dnd'
+
+import { EntityUUID } from '@etherealengine/ecs'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { ControlledStringInput } from '../String'
+
+export function MaterialInput void }>({
+ value,
+ onChange,
+ ...rest
+}: T) {
+ function onDrop(item: { value?: Entity | Entity[] | undefined }, _monitor: DropTargetMonitor) {
+ let element = item.value
+ if (typeof element === 'undefined') return
+ if (Array.isArray(value)) {
+ element = element[0]
+ }
+ if (typeof element !== 'string') return
+ onChange(element)
+ }
+
+ const [{ canDrop, isOver }, dropRef] = useDrop({
+ accept: [ItemTypes.Material],
+ drop: onDrop,
+ collect: (monitor) => ({
+ canDrop: monitor.canDrop(),
+ isOver: monitor.isOver()
+ })
+ })
+
+ return (
+
+ )
+}
+
+MaterialInput.defaultProps = {}
+
+export default MaterialInput
diff --git a/packages/ui/src/components/editor/input/Model/index.stories.tsx b/packages/ui/src/components/editor/input/Model/index.stories.tsx
new file mode 100644
index 0000000000..8b6a58d235
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Model/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Model',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ModelInput',
+ jest: 'Model.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Model/index.tsx b/packages/ui/src/components/editor/input/Model/index.tsx
new file mode 100644
index 0000000000..0c4508e5b5
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Model/index.tsx
@@ -0,0 +1,45 @@
+/*
+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 { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { ModelFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import React from 'react'
+import FileBrowserInput from '../FileBrowser'
+import { StringInputProps } from '../String'
+
+export function ModelInput({ onRelease, ...rest }: StringInputProps) {
+ return (
+
+ )
+}
+
+ModelInput.defaultProps = {}
+
+export default ModelInput
diff --git a/packages/ui/src/components/editor/input/Numeric/Stepper/index.stories.tsx b/packages/ui/src/components/editor/input/Numeric/Stepper/index.stories.tsx
new file mode 100644
index 0000000000..985cf608ff
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Numeric/Stepper/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Numeric/Stepper',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'NumericStepperInput',
+ jest: 'NumericStepper.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Numeric/Stepper/index.tsx b/packages/ui/src/components/editor/input/Numeric/Stepper/index.tsx
new file mode 100644
index 0000000000..608e3c643e
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Numeric/Stepper/index.tsx
@@ -0,0 +1,109 @@
+/*
+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 React from 'react'
+
+import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'
+import ArrowRightIcon from '@mui/icons-material/ArrowRight'
+
+import { t } from 'i18next'
+import NumericInput, { NumericInputProp } from '..'
+import { InfoTooltip } from '../../../layout/Tooltip'
+
+const stepperInputContainerStyle = {
+ display: 'flex',
+ flex: '1',
+ width: '100%',
+ height: '24px'
+}
+
+const stepperButtonStyle = {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'var(--toolbar)',
+ border: '1px solid var(--inputOutline)',
+ color: 'var(--textColor)',
+ width: '20px',
+ padding: '0',
+ margin: 0
+}
+
+const leftStepperButtonStyle = {
+ ...stepperButtonStyle,
+ borderTopLeftRadius: '4px',
+ borderBottomLeftRadius: '4px'
+}
+
+const rightStepperButtonStyle = {
+ ...stepperButtonStyle,
+ borderTopRightRadius: '4px',
+ borderBottomRightRadius: '4px'
+}
+
+export function NumericStepperInput({
+ style,
+ className,
+ decrementTooltip,
+ incrementTooltip,
+ onChange,
+ value,
+ mediumStep,
+ ...rest
+}: {
+ style?: React.CSSProperties
+ className?: string
+ incrementTooltip?: string
+ decrementTooltip?: string
+ onChange: (val) => void
+ value: number
+ mediumStep: number
+} & NumericInputProp) {
+ const onIncrement = () => onChange(value + mediumStep)
+ const onDecrement = () => onChange(value - mediumStep)
+
+ return (
+
+ )
+}
+
+NumericStepperInput.defaultProps = {
+ incrementTooltip: t('editor:toolbar.grid.info-incrementHeight'),
+ decrementTooltip: t('editor:toolbar.grid.info-decrementHeight')
+}
+
+export default NumericStepperInput
diff --git a/packages/ui/src/components/editor/input/Numeric/index.stories.tsx b/packages/ui/src/components/editor/input/Numeric/index.stories.tsx
new file mode 100644
index 0000000000..42cc0a3097
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Numeric/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Numeric',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'NumericInput',
+ jest: 'Numeric.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Numeric/index.tsx b/packages/ui/src/components/editor/input/Numeric/index.tsx
new file mode 100644
index 0000000000..0c0dbf439f
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Numeric/index.tsx
@@ -0,0 +1,161 @@
+/*
+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 React from 'react'
+
+import { clamp } from '@etherealengine/spatial/src/common/functions/MathLerpFunctions'
+
+import { getStepSize, toPrecision } from '@etherealengine/editor/src/functions/utils'
+import { twMerge } from 'tailwind-merge'
+import Text from '../../../../primitives/tailwind/Text'
+
+function toPrecisionString(value: number, precision?: number) {
+ if (precision && precision <= 1) {
+ const numDigits = Math.abs(Math.log10(precision))
+ const minimumFractionDigits = Math.min(numDigits, 2)
+ const maximumFractionDigits = Math.max(minimumFractionDigits, numDigits)
+
+ return value.toLocaleString('fullwide', {
+ minimumFractionDigits,
+ maximumFractionDigits,
+ useGrouping: false
+ })
+ }
+ return value.toLocaleString('fullwide', { useGrouping: false })
+}
+
+export interface NumericInputProp extends Omit, 'onChange' | 'prefix'> {
+ value: number
+ onChange: (value: number) => void
+ onRelease?: (value: number) => void
+ className?: string
+ inputClassName?: string
+ unit?: string
+ prefix?: React.ReactNode
+ displayPrecision?: number
+ precision?: number
+ mediumStep?: number
+ smallStep?: number
+ largeStep?: number
+ min?: number
+ max?: number
+}
+
+const NumericInput = ({
+ className,
+ inputClassName,
+ unit,
+ prefix,
+ displayPrecision,
+ value,
+ precision,
+ mediumStep,
+ onChange,
+ onRelease,
+ smallStep,
+ largeStep,
+ min,
+ max,
+ ...rest
+}: NumericInputProp) => {
+ const handleStep = (event: React.KeyboardEvent, direction: number) => {
+ const stepSize = event ? getStepSize(event, smallStep, mediumStep, largeStep) : mediumStep
+
+ const nextValue = value + stepSize * direction
+ const clampedValue = min != null && max != null ? clamp(nextValue, min, max) : nextValue
+ const roundedValue = precision ? toPrecision(clampedValue, precision) : nextValue
+
+ if (onChange) {
+ onChange(roundedValue)
+ }
+ }
+
+ const handleKeyPress = (event: React.KeyboardEvent) => {
+ let direction: number
+ if (event.key === 'ArrowUp') {
+ direction = 1
+ } else if (event.key === 'ArrowDown') {
+ direction = -1
+ } else {
+ return
+ }
+
+ event.preventDefault()
+ handleStep(event, direction)
+ }
+
+ const handleChange = (event: React.ChangeEvent) => {
+ const newValue = event.target.value
+
+ const parsedValue = parseFloat(newValue)
+
+ if (!Number.isNaN(parsedValue)) {
+ const clampedValue = min != null && max != null ? clamp(parsedValue, min, max) : parsedValue
+ const roundedValue = precision ? toPrecision(clampedValue, precision) : clampedValue
+ onChange?.(roundedValue)
+ }
+ }
+
+ return (
+
+ {prefix}
+ onRelease?.(value)}
+ {...rest}
+ />
+ {unit && (
+
+ {unit}
+
+ )}
+
+ )
+}
+
+NumericInput.defaultProps = {
+ value: 0,
+ smallStep: 0.025,
+ mediumStep: 0.1,
+ largeStep: 0.25,
+ min: -Infinity,
+ max: Infinity,
+ displayPrecision: 0.001,
+ precision: Number.EPSILON
+}
+
+export default NumericInput
diff --git a/packages/ui/src/components/editor/input/Prefab/index.stories.tsx b/packages/ui/src/components/editor/input/Prefab/index.stories.tsx
new file mode 100644
index 0000000000..86fb5703ef
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Prefab/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Prefab',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PrefabInput',
+ jest: 'Prefab.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Prefab/index.tsx b/packages/ui/src/components/editor/input/Prefab/index.tsx
new file mode 100644
index 0000000000..ca00328544
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Prefab/index.tsx
@@ -0,0 +1,36 @@
+/*
+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 React from 'react'
+import FileBrowserInput from '../FileBrowser'
+import { StringInputProps } from '../String'
+
+function PrefabInput({ ...rest }: StringInputProps) {
+ return
+}
+
+PrefabInput.defaultProps = {}
+
+export default PrefabInput
diff --git a/packages/ui/src/components/editor/input/Progress/index.stories.tsx b/packages/ui/src/components/editor/input/Progress/index.stories.tsx
new file mode 100644
index 0000000000..d04714b39b
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Progress/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 ProgressBar from './index'
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Progress',
+ component: ProgressBar,
+ parameters: {
+ componentSubtitle: 'ProgressBar',
+ jest: 'ProgressBar.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: ProgressBar.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Progress/index.tsx b/packages/ui/src/components/editor/input/Progress/index.tsx
new file mode 100644
index 0000000000..cc4facbe31
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Progress/index.tsx
@@ -0,0 +1,52 @@
+/*
+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 React from 'react'
+
+import { HiPause, HiPlay } from 'react-icons/hi2'
+import Progress, { ProgressProps } from '../../../../primitives/tailwind/Progress'
+
+export interface ProgressBarProps extends ProgressProps {
+ paused: boolean
+ totalTime: number
+}
+
+export default function ProgressBar({ value, paused, totalTime, ...rest }: ProgressBarProps) {
+ return (
+
+ {paused ?
:
}
+
+
+ {paused
+ ? 'Paused'
+ : `${Math.floor((totalTime * value) / 100 / 60)}:${Math.floor(
+ ((totalTime * value) / 100) % 60
+ )} / ${Math.floor(totalTime / 60)}:${Math.floor(totalTime % 60)} `}
+
+
+ )
+}
+
+ProgressBar.defaultProps = {}
diff --git a/packages/ui/src/components/editor/input/Select/index.stories.tsx b/packages/ui/src/components/editor/input/Select/index.stories.tsx
new file mode 100644
index 0000000000..a2d11cb3ac
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Select/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 SelectInput from './index'
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Select',
+ component: SelectInput,
+ parameters: {
+ componentSubtitle: 'SelectInput',
+ jest: 'Select.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: SelectInput.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Select/index.tsx b/packages/ui/src/components/editor/input/Select/index.tsx
new file mode 100644
index 0000000000..92955ba538
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Select/index.tsx
@@ -0,0 +1,56 @@
+/*
+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 React from 'react'
+import { MdOutlineHeatPump, MdOutlineWatch, MdOutlineWindPower } from 'react-icons/md'
+import Select, { SelectProps } from '../../../../primitives/tailwind/Select'
+
+/**Tailwind `Select` styled for studio */
+const SelectInput = ({
+ value,
+ ...rest
+}: Omit, 'currentValue'> & { value: string | number }) => {
+ return (
+
+ )
+}
+
+SelectInput.displayName = 'SelectInput'
+SelectInput.defaultProps = {
+ options: [
+ { label: 'Cuboid', value: 'a', icon: },
+ { label: 'Cylinder', value: 'b', icon: },
+ { label: 'Cube', value: 'c', icon: }
+ ],
+ value: 'a',
+ onChange: () => {}
+}
+
+export default SelectInput
diff --git a/packages/ui/src/components/editor/input/String/index.stories.tsx b/packages/ui/src/components/editor/input/String/index.stories.tsx
new file mode 100644
index 0000000000..9ba7d5ce92
--- /dev/null
+++ b/packages/ui/src/components/editor/input/String/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 StringInput from './index'
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/String',
+ component: StringInput,
+ parameters: {
+ componentSubtitle: 'StringInput',
+ jest: 'String.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: StringInput.defaultProps }
diff --git a/packages/ui/src/components/editor/input/String/index.tsx b/packages/ui/src/components/editor/input/String/index.tsx
new file mode 100644
index 0000000000..6e18ae9c36
--- /dev/null
+++ b/packages/ui/src/components/editor/input/String/index.tsx
@@ -0,0 +1,111 @@
+/*
+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 React, { useEffect, useState } from 'react'
+import { twMerge } from 'tailwind-merge'
+import Input, { InputProps } from '../../../../primitives/tailwind/Input'
+
+export interface StringInputProps extends Omit {
+ value: string
+ onChange?: (value: string) => void
+ onRelease?: (value: string) => void
+}
+
+const StringInput = ({ value, onChange, onRelease, className, ...rest }: StringInputProps) => {
+ return (
+ {
+ onChange?.(e.target.value)
+ }}
+ onBlur={(e) => {
+ onRelease?.(e.target.value)
+ }}
+ onFocus={(e) => {
+ onRelease?.(e.target.value)
+ }}
+ {...rest}
+ />
+ )
+}
+
+StringInput.displayName = 'StringInput'
+StringInput.defaultProps = {
+ value: '',
+ onChange: () => {},
+ type: 'text',
+ required: false,
+ placeholder: ''
+}
+
+export default StringInput
+
+// do we really need a controlled string input? we could easily integrate this with string input itself
+export const ControlledStringInput = React.forwardRef((values, ref) => {
+ const { onChange, onRelease, value, placeholder, disabled, type, containerClassname, ...rest } = values
+ const [tempValue, setTempValue] = useState(value)
+
+ useEffect(() => {
+ setTempValue(value)
+ }, [value])
+
+ const onBlur = () => {
+ onRelease?.(tempValue)
+ }
+
+ const onChangeValue = (value: string) => {
+ setTempValue(value)
+ onChange?.(value)
+ }
+
+ return (
+ {
+ onChangeValue(e.target.value)
+ }}
+ onBlur={onBlur}
+ disabled={disabled}
+ placeholder={placeholder}
+ type="text"
+ />
+ )
+})
+
+ControlledStringInput.displayName = 'ControlledStringInput'
+
+ControlledStringInput.defaultProps = {
+ value: '',
+ onChange: () => {},
+ type: 'text'
+}
diff --git a/packages/ui/src/components/editor/input/Texture/index.stories.tsx b/packages/ui/src/components/editor/input/Texture/index.stories.tsx
new file mode 100644
index 0000000000..b8de703ce8
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Texture/index.stories.tsx
@@ -0,0 +1,48 @@
+/*
+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 image from '../../../../../../../PoweredByEE.png'
+import Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Texture',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'TextureInput',
+ jest: 'Texture.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = {
+ args: {
+ value: image
+ }
+}
diff --git a/packages/ui/src/components/editor/input/Texture/index.tsx b/packages/ui/src/components/editor/input/Texture/index.tsx
new file mode 100644
index 0000000000..67b7ab5553
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Texture/index.tsx
@@ -0,0 +1,185 @@
+/*
+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 React, { Fragment, useEffect } from 'react'
+import { ColorSpace, DisplayP3ColorSpace, LinearSRGBColorSpace, SRGBColorSpace, Texture, Vector2 } from 'three'
+
+import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
+import { ImageFileTypes, VideoFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import { AssetClass } from '@etherealengine/engine/src/assets/enum/AssetClass'
+import { useHookstate } from '@etherealengine/hyperflux'
+
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import Button from '../../../../primitives/tailwind/Button'
+import FileBrowserInput from '../FileBrowser'
+import InputGroup from '../Group'
+import { ImageContainer } from '../Image/Preview'
+import SelectInput from '../Select'
+import { StringInputProps } from '../String'
+import { Vector2Input } from '../Vector2'
+
+/**
+ * VideoInput used to render component view for video inputs.
+ */
+export function TextureInput({ ...rest }: StringInputProps) {
+ return (
+
+ )
+}
+
+export default function TexturePreviewInput({
+ value,
+ onRelease,
+ ...rest
+}: {
+ value: string | Texture
+ onRelease: (value: any) => void
+ preview?: string
+}) {
+ const { preview } = rest
+ const validSrcValue =
+ typeof value === 'string' && [AssetClass.Image, AssetClass.Video].includes(AssetLoader.getAssetClass(value))
+
+ const srcState = useHookstate(value)
+ const texture = srcState.value as Texture
+ const src = srcState.value as string
+ const showPreview = preview !== undefined || validSrcValue
+ const previewSrc = validSrcValue ? value : preview
+ const inputSrc = validSrcValue
+ ? value
+ : texture?.isTexture
+ ? texture.source?.data?.src ?? texture?.userData?.src ?? (preview ? 'BLOB' : '')
+ : src
+ const offset = useHookstate(typeof texture?.offset?.clone === 'function' ? texture.offset.clone() : new Vector2(0, 0))
+ const scale = useHookstate(typeof texture?.repeat?.clone === 'function' ? texture.repeat.clone() : new Vector2(1, 1))
+ const colorspace = useHookstate(
+ texture?.colorSpace ? texture?.colorSpace : (new String(LinearSRGBColorSpace) as ColorSpace)
+ )
+
+ useEffect(() => {
+ if (texture?.isTexture && !texture.isRenderTargetTexture) {
+ offset.set(texture.offset)
+ scale.set(texture.repeat)
+ colorspace.set(texture.colorSpace)
+ }
+ }, [srcState])
+
+ return (
+
+
+ {showPreview && (
+
+
+
+
+ {(typeof preview === 'string' ||
+ (typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Image)) && (
+
+ )}
+ {typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Video && (
+
+ )}
+
+
+
+
+ )}
+
+
+
+ {texture?.isTexture && !texture.isRenderTargetTexture && (
+ <>
+
{
+ offset.set(_offset)
+ texture.offset.copy(_offset)
+ }}
+ uniformScaling={false}
+ />
+ {
+ scale.set(_scale)
+ texture.repeat.copy(_scale)
+ }}
+ uniformScaling={false}
+ />
+ >
+ )}
+ {texture?.isTexture && (
+ <>
+
+ {
+ colorspace.set(value)
+ texture.colorSpace = value
+ texture.needsUpdate = true
+ console.log('DEBUG changed space', texture.colorSpace)
+ }}
+ />
+
+ >
+ )}
+ {value && (
+ <>
+
+
+
+ >
+ )}
+
+
+ )
+}
+
+export function TexturePreviewInputGroup({ name, label, value, onRelease, ...rest }) {
+ return (
+
+
+
+ )
+}
diff --git a/packages/ui/src/components/editor/input/Vector2/index.stories.tsx b/packages/ui/src/components/editor/input/Vector2/index.stories.tsx
new file mode 100644
index 0000000000..98bc69a6d9
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Vector2/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Vector2',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'Vector2Input',
+ jest: 'Vector2.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Vector2/index.tsx b/packages/ui/src/components/editor/input/Vector2/index.tsx
new file mode 100644
index 0000000000..b48d470719
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Vector2/index.tsx
@@ -0,0 +1,123 @@
+/*
+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 { useHookstate } from '@etherealengine/hyperflux'
+import React from 'react'
+import { Vector2 } from 'three'
+
+import { Vector2_Zero } from '@etherealengine/spatial/src/common/constants/MathConstants'
+import NumericInput from '../Numeric'
+import { Vector3Scrubber } from '../Vector3'
+
+interface Vector2InputProp {
+ uniformScaling?: boolean
+ smallStep?: number
+ mediumStep?: number
+ largeStep?: number
+ value: Vector2
+ hideLabels?: boolean
+ onChange: (v: Vector2) => void
+ onRelease?: (v: Vector2) => void
+}
+
+export const Vector2Input = ({
+ uniformScaling,
+ smallStep,
+ mediumStep,
+ largeStep,
+ value,
+ hideLabels,
+ onChange,
+ onRelease,
+ ...rest
+}: Vector2InputProp) => {
+ const uniformEnabled = useHookstate(uniformScaling)
+
+ const processChange = (field: string, fieldValue: number) => {
+ if (uniformEnabled.value) {
+ value.set(fieldValue, fieldValue)
+ } else {
+ value[field] = fieldValue
+ }
+ }
+
+ const onChangeX = (x: number) => {
+ processChange('x', x)
+ onChange(value)
+ }
+
+ const onChangeY = (y: number) => {
+ processChange('y', y)
+ onChange(value)
+ }
+
+ const onReleaseX = (x: number) => {
+ processChange('x', x)
+ onRelease?.(value)
+ }
+
+ const onReleaseY = (y: number) => {
+ processChange('y', y)
+ onRelease?.(value)
+ }
+
+ const vx = value.x
+ const vy = value.y
+
+ return (
+
+
+ )
+ }
+ />
+
+ )
+ }
+ />
+
+ )
+}
+
+Vector2Input.defaultProps = {
+ value: Vector2_Zero,
+ hideLabels: false,
+ onChange: () => {}
+}
+
+export default Vector2Input
diff --git a/packages/ui/src/components/editor/input/Vector3/index.stories.tsx b/packages/ui/src/components/editor/input/Vector3/index.stories.tsx
new file mode 100644
index 0000000000..e5e3a6ca1e
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Vector3/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Input/Vector3',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'Vector3Input',
+ jest: 'Vector3.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/input/Vector3/index.tsx b/packages/ui/src/components/editor/input/Vector3/index.tsx
new file mode 100644
index 0000000000..078395e177
--- /dev/null
+++ b/packages/ui/src/components/editor/input/Vector3/index.tsx
@@ -0,0 +1,164 @@
+/*
+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 { useHookstate } from '@etherealengine/hyperflux'
+import { Vector3_Zero } from '@etherealengine/spatial/src/common/constants/MathConstants'
+import React from 'react'
+import { twMerge } from 'tailwind-merge'
+import { Vector3 } from 'three'
+import Scrubber from '../../layout/Scrubber'
+import NumericInput from '../Numeric'
+
+interface Vector3ScrubberProps {
+ axis?: 'x' | 'y' | 'z' | string
+ value: number
+ onChange: any
+ onPointerUp?: any
+ children?: any
+ className?: string
+}
+
+export const Vector3Scrubber = ({ axis, onChange, value, children, ...props }: Vector3ScrubberProps) => {
+ const color = (() => {
+ switch (axis) {
+ case 'x':
+ return 'red-500'
+ case 'y':
+ return 'green-400' // must be fushsia-400 , but these colors doesnt show up
+ case 'z':
+ return 'blue-400' //must be teal-400 , but this color doesnt show up
+ default:
+ return 'inherit'
+ }
+ })()
+
+ props.className = twMerge(`text-${color}`)
+ const content = children ?? axis?.toUpperCase()
+ return (
+
+ {content}
+
+ )
+}
+
+export const UniformButtonContainer: React.FC<{ children?: any }> = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+interface Vector3InputProp {
+ uniformScaling?: boolean
+ smallStep?: number
+ mediumStep?: number
+ largeStep?: number
+ value: Vector3
+ hideLabels?: boolean
+ onChange: (v: Vector3) => void
+ onRelease?: (v: Vector3) => void
+}
+
+export const Vector3Input = ({
+ uniformScaling,
+ smallStep,
+ mediumStep,
+ largeStep,
+ value,
+ hideLabels,
+ onChange,
+ onRelease,
+ ...rest
+}: Vector3InputProp) => {
+ const uniformEnabled = useHookstate(uniformScaling)
+ const processChange = (field: string, fieldValue: number) => {
+ if (uniformEnabled.value) {
+ value.set(fieldValue, fieldValue, fieldValue)
+ } else {
+ value[field] = fieldValue
+ }
+ }
+
+ const onChangeAxis = (axis: 'x' | 'y' | 'z') => (axisValue: number) => {
+ processChange(axis, axisValue)
+ onChange(value)
+ }
+
+ const onReleaseAxis = (axis: 'x' | 'y' | 'z') => (axisValue: number) => {
+ processChange(axis, axisValue)
+ onRelease?.(value)
+ }
+
+ const vx = value.x
+ const vy = value.y
+ const vz = value.z
+
+ return (
+
+
+ )
+ }
+ />
+
+ )
+ }
+ />
+
+ )
+ }
+ />
+
+ )
+}
+
+Vector3Input.defaultProps = {
+ value: Vector3_Zero,
+ hideLabels: false,
+ onChange: () => {}
+}
+
+export default Vector3Input
diff --git a/packages/ui/src/components/editor/layout/ClickAwayListener.tsx b/packages/ui/src/components/editor/layout/ClickAwayListener.tsx
new file mode 100644
index 0000000000..2763ed987d
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/ClickAwayListener.tsx
@@ -0,0 +1,48 @@
+/*
+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 React, { useEffect, useRef } from 'react'
+
+const ClickAwayListener = ({ onClickAway, children }) => {
+ const wrapperRef = useRef(null)
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (wrapperRef.current && !(wrapperRef.current! as HTMLElement).contains(event.target)) {
+ onClickAway()
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside)
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside)
+ }
+ }, [onClickAway])
+
+ return {children}
+}
+
+export default ClickAwayListener
diff --git a/packages/ui/src/components/editor/layout/ContextMenu.tsx b/packages/ui/src/components/editor/layout/ContextMenu.tsx
new file mode 100644
index 0000000000..8b81a1cfab
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/ContextMenu.tsx
@@ -0,0 +1,112 @@
+/*
+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 React, { useEffect, useRef, useState } from 'react'
+import { twMerge } from 'tailwind-merge'
+import ClickAwayListener from './ClickAwayListener'
+
+type ContextMenuProps = {
+ open: boolean
+ anchorEl: null | HTMLElement
+ panelId: string
+ anchorPosition: { left: number; top: number }
+ onClose: () => void
+ className?: string
+}
+
+export const ContextMenu = ({
+ children,
+ open,
+ anchorEl,
+ panelId,
+ anchorPosition,
+ onClose,
+ className
+}: React.PropsWithChildren) => {
+ const panel = document.getElementById(panelId)
+ const menuRef = useRef(null)
+
+ // Calculate the Y position of the context menu based on the menu height and space to the bottom of the viewport in order to avoid overflow
+ const calculatePositionY = () => {
+ let positionY = open ? anchorPosition.top - panel?.getBoundingClientRect().top! : 0
+
+ if (open && menuRef.current) {
+ const menuHeight = menuRef.current.offsetHeight
+
+ // The amount of space that the menu can fill based on the current anchor position
+ const spaceToBottomFromAnchor = window.innerHeight - anchorPosition.top
+ // We want to reposition the context menu whenever it will overflow the bottom of the screen
+ const shouldRepositionMenu = menuHeight > spaceToBottomFromAnchor
+
+ if (shouldRepositionMenu) {
+ // Align the menu bottom with the bottom of the viewport
+ positionY = window.innerHeight - menuHeight - (panel?.getBoundingClientRect().top || 0) + 30
+ }
+ }
+
+ return positionY
+ }
+
+ const positionX = open ? anchorPosition.left - panel?.getBoundingClientRect().left! : 0
+ const [positionY, setPositionY] = useState(calculatePositionY())
+
+ const [isScrollable, setIsScrollable] = useState(false)
+ const parentRect = panel?.getBoundingClientRect()
+
+ useEffect(() => {
+ if (open && menuRef.current) {
+ const menuHeight = menuRef.current.offsetHeight
+ const parentHeight = parentRect?.height || 0
+
+ // Make the menu scrollable if it is too tall for the parent component
+ setIsScrollable(parentHeight <= menuHeight + 1)
+
+ setPositionY(calculatePositionY())
+ }
+ }, [open])
+
+ return (
+ onClose()}>
+
+ {open && anchorEl && (
+
+ )}
+
+
+ )
+}
+
+export default ContextMenu
diff --git a/packages/ui/src/components/editor/layout/Overlay.tsx b/packages/ui/src/components/editor/layout/Overlay.tsx
new file mode 100755
index 0000000000..53218d87a3
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/Overlay.tsx
@@ -0,0 +1,34 @@
+/*
+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 React from 'react'
+import { twMerge } from 'tailwind-merge'
+
+const Overlay = ({ pointerEvents, children }) => {
+ const overlayClassName = twMerge('fixed inset-0', `pointer-events-${pointerEvents} ?? 'auto'`)
+ return {children}
+}
+
+export default Overlay
diff --git a/packages/ui/src/components/editor/layout/Panel.tsx b/packages/ui/src/components/editor/layout/Panel.tsx
new file mode 100755
index 0000000000..04dbd6926f
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/Panel.tsx
@@ -0,0 +1,130 @@
+/*
+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 React, { ReactNode } from 'react'
+import Text from '../../../primitives/tailwind/Text'
+
+const panelIconStyles = {
+ color: 'var(--textColor)',
+ marginRight: '6px',
+ width: '18px'
+}
+
+const panelCheckboxStyles = {
+ color: 'var(--textColor)',
+ position: 'relative',
+ padding: '0px'
+}
+
+const panelContainerStyles = {
+ position: 'relative',
+ display: 'flex',
+ flex: 1,
+ flexDirection: 'column',
+ borderRadius: '4px',
+ backgroundColor: 'var(--dockBackground)',
+ overflow: 'hidden',
+ userSelect: 'none'
+}
+
+const panelToolbarStyles = {
+ display: 'flex',
+ padding: '4px',
+ height: '24px',
+ alignItems: 'center',
+ borderBottom: '1px solid rgba(0, 0, 0, 0.2)'
+}
+
+const panelContentStyles = {
+ display: 'flex',
+ flex: 1,
+ flexDirection: 'column',
+ position: 'relative',
+ overflow: 'hidden'
+}
+
+export const PanelIcon = ({ as: IconComponent, size = 12 }) => {
+ return
+}
+
+export const PanelTitle = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export const PanelCheckbox = ({ children }) => {
+ return {children}
+}
+
+export const PanelDragContainer = ({ children }) => {
+ // .dock-tab styled in Editor2Container.css
+ return {children}
+}
+
+export const PanelContainer = ({ children, ...rest }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export const PanelToolbar = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export const PanelContent = ({ children }) => {
+ return {children}
+}
+
+interface PanelProps {
+ icon?: React.ElementType
+ title: string
+ toolbarContent?: React.ReactNode
+ children?: ReactNode
+ // Add any other props you want to accept
+}
+
+const Panel: React.FC = ({ icon, title, children, toolbarContent, ...rest }) => {
+ return (
+
+
+ {icon && }
+ {title}
+ {toolbarContent}
+
+ {children}
+
+ )
+}
+
+export default Panel
diff --git a/packages/ui/src/components/editor/layout/Popover.tsx b/packages/ui/src/components/editor/layout/Popover.tsx
new file mode 100644
index 0000000000..6cddcaeabe
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/Popover.tsx
@@ -0,0 +1,67 @@
+/*
+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 React from 'react'
+import { twMerge } from 'tailwind-merge'
+import ClickAwayListener from './ClickAwayListener'
+
+type ContextMenuProps = {
+ className?: string
+ open: boolean
+ anchorEl: null | HTMLElement
+ panelId: string
+ anchorPosition: any
+ onClose: () => void
+}
+
+export const PopOver = ({
+ children,
+ open,
+ anchorEl,
+ panelId,
+ anchorPosition,
+ className,
+ onClose
+}: React.PropsWithChildren) => {
+ const panel = document.getElementById(panelId)
+ const positionX = anchorPosition?.left - panel?.getBoundingClientRect().left!
+ const positionY = anchorPosition?.top - panel?.getBoundingClientRect().top!
+ return (
+ onClose()}>
+
+ {open && anchorEl && (
+
+ {children}
+
+ )}
+
+
+ )
+}
+
+export default PopOver
diff --git a/packages/ui/src/components/editor/layout/Scrubber.tsx b/packages/ui/src/components/editor/layout/Scrubber.tsx
new file mode 100644
index 0000000000..a106c1ca17
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/Scrubber.tsx
@@ -0,0 +1,168 @@
+/*
+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 Portal from '@etherealengine/editor/src/components/layout/Portal'
+import { getStepSize, toPrecision } from '@etherealengine/editor/src/functions/utils'
+import { useHookstate } from '@etherealengine/hyperflux'
+import React, { useRef } from 'react'
+import { twMerge } from 'tailwind-merge'
+import { MathUtils } from 'three'
+import Overlay from './Overlay'
+
+type ScrubberProps = {
+ className?: string
+ children?: React.ReactNode
+ smallStep?: number
+ mediumStep?: number
+ largeStep?: number
+ sensitivity?: number
+ min?: number
+ max?: number
+ precision?: number
+ convertFrom?: any
+ convertTo?: any
+ value?: any
+ onChange: (value: any) => void
+ onRelease?: (value: any) => void
+}
+
+const Scrubber: React.FC = ({
+ className,
+ children,
+ smallStep,
+ mediumStep,
+ largeStep,
+ sensitivity,
+ min,
+ max,
+ precision,
+ convertFrom,
+ convertTo,
+ value,
+ onChange,
+ onRelease,
+ ...rest
+}) => {
+ const containerClassName = twMerge(
+ 'flex items-center',
+ 'cursor-ew-resize p-1',
+ "font-['Figtree'] text-xs font-normal",
+ className
+ )
+
+ const state = useHookstate({
+ isDragging: false,
+ startValue: 0,
+ delta: 0,
+ mouseX: 0,
+ mouseY: 0,
+ currentValue: 0
+ })
+
+ const scrubberEl = useRef(null)
+
+ const handleMouseMove = (event: React.MouseEvent) => {
+ if (state.isDragging.value) {
+ const mX = state.mouseX.value + event.movementX
+ const mY = state.mouseY.value + event.movementY
+ 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 = MathUtils.clamp(nextValue, min ?? -Infinity, max ?? Infinity)
+ const roundedValue = precision ? toPrecision(clampedValue, precision) : clampedValue
+ const finalValue = convertTo(roundedValue)
+ onChange(finalValue)
+
+ state.merge({
+ currentValue: finalValue,
+ delta: nextDelta,
+ mouseX: mX,
+ mouseY: mY
+ })
+ }
+ }
+
+ const handleMouseUp = () => {
+ if (state.isDragging.value) {
+ state.merge({
+ isDragging: false,
+ startValue: 0,
+ delta: 0,
+ mouseX: 0,
+ mouseY: 0
+ })
+
+ if (onRelease) {
+ onRelease(state.currentValue.value)
+ }
+
+ document.exitPointerLock()
+ }
+ }
+
+ const handleMouseDown = (event: React.MouseEvent) => {
+ state.merge({
+ isDragging: true,
+ startValue: convertFrom(value),
+ delta: 0,
+ mouseX: event.clientX,
+ mouseY: event.clientY
+ })
+ scrubberEl?.current?.requestPointerLock()
+ }
+
+ return (
+
+ {children}
+ {state.isDragging.value && (
+
+
+
+
+
+ )}
+
+ )
+}
+
+Scrubber.defaultProps = {
+ smallStep: 0.025,
+ mediumStep: 0.1,
+ largeStep: 0.25,
+ sensitivity: 5,
+ min: -Infinity,
+ max: Infinity,
+ convertFrom: (value) => value,
+ convertTo: (value) => value
+}
+
+export default React.memo(Scrubber)
diff --git a/packages/ui/src/components/editor/layout/Tooltip.tsx b/packages/ui/src/components/editor/layout/Tooltip.tsx
new file mode 100755
index 0000000000..873a4dacd2
--- /dev/null
+++ b/packages/ui/src/components/editor/layout/Tooltip.tsx
@@ -0,0 +1,67 @@
+/*
+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 React from 'react'
+
+import { Tooltip, TooltipProps } from '@mui/material'
+import createStyles from '@mui/styles/createStyles'
+import makeStyles from '@mui/styles/makeStyles'
+
+const useStyles = makeStyles((theme: any) => {
+ return createStyles({
+ tooltip: {
+ background: theme.panel
+ }
+ })
+})
+
+/**
+ * @param {Object} props
+ * @param {string} props.info additional info added to the tooltip label
+ */
+export function InfoTooltip(props: TooltipProps & { info?: string }) {
+ if (!props.title) return <>{props.children}>
+
+ const title = props.info ? (
+
+ {props.title}
+
+ {props.info}
+
+ ) : (
+ props.title
+ )
+
+ const styles = useStyles({})
+
+ return (
+
+ {/* Span is required to trigger events like hover in safari for disabled elements */}
+ {props.children}
+
+ )
+}
+
+export default Tooltip
diff --git a/packages/ui/src/components/editor/panels/Assets/container/index.tsx b/packages/ui/src/components/editor/panels/Assets/container/index.tsx
new file mode 100644
index 0000000000..3c5ad4f257
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Assets/container/index.tsx
@@ -0,0 +1,286 @@
+/* eslint-disable no-case-declarations */
+/*
+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 { debounce } from 'lodash'
+import React, { createContext, useContext, useEffect, useRef } from 'react'
+import { useDrag } from 'react-dnd'
+import { getEmptyImage } from 'react-dnd-html5-backend'
+import { useTranslation } from 'react-i18next'
+
+import { staticResourcePath, StaticResourceType } from '@etherealengine/common/src/schema.type.module'
+import { Engine } from '@etherealengine/ecs/src/Engine'
+import { AssetsPanelCategories } from '@etherealengine/editor/src/components/assets/AssetsPanelCategories'
+import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel'
+import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
+import { getState, NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux'
+import { BiSolidDownArrow, BiSolidRightArrow } from 'react-icons/bi'
+import { HiMagnifyingGlass } from 'react-icons/hi2'
+import { twMerge } from 'tailwind-merge'
+import Button from '../../../../../primitives/tailwind/Button'
+import Input from '../../../../../primitives/tailwind/Input'
+import LoadingView from '../../../../../primitives/tailwind/LoadingView'
+import Text from '../../../../../primitives/tailwind/Text'
+import { FileIcon } from '../../Files/icon'
+
+type FolderType = { folderType: 'folder'; assetClass: string }
+type ResourceType = { folderType: 'staticResource' } & StaticResourceType
+
+type CategorizedStaticResourceType = FolderType | ResourceType
+
+const AssetsPreviewContext = createContext({ onAssetSelectionChanged: (props: AssetSelectionChangePropsType) => {} })
+
+const ResourceFile = ({ resource }: { resource: StaticResourceType }) => {
+ const { onAssetSelectionChanged } = useContext(AssetsPreviewContext)
+
+ const assetType = AssetLoader.getAssetType(resource.key)
+ const [_, drag, preview] = useDrag(() => ({
+ type: assetType,
+ item: {
+ url: resource.url
+ },
+ multiple: false
+ }))
+
+ useEffect(() => {
+ if (preview) preview(getEmptyImage(), { captureDraggingState: true })
+ }, [preview])
+
+ const fullName = resource.key.split('/').at(-1)!
+ const name = fullName.length > 15 ? `${fullName.substring(0, 12)}...` : fullName
+
+ return (
+
+ onAssetSelectionChanged?.({
+ contentType: assetType,
+ name: fullName,
+ resourceUrl: resource.url,
+ size: 'unknown size'
+ })
+ }
+ className="mt-[10px] flex cursor-pointer flex-col items-center justify-center align-middle"
+ >
+
+
+
+ {name}
+
+ )
+}
+
+type Category = {
+ name: string
+ object: object
+ collapsed: boolean
+ isLeaf: boolean
+ depth: number
+}
+
+function iterativelyListTags(obj: object): string[] {
+ const tags: string[] = []
+ for (const key in obj) {
+ tags.push(key)
+ if (typeof obj[key] === 'object') {
+ tags.push(...iterativelyListTags(obj[key]))
+ }
+ }
+ return tags
+}
+
+const AssetCategory = (props: {
+ data: {
+ categories: Category[]
+ onClick: (resource: Category) => void
+ selectedCategory: Category | null
+ collapsedCategories: State<{ [key: string]: boolean }>
+ }
+ index: number
+}) => {
+ const { categories, onClick, selectedCategory, collapsedCategories } = props.data
+ const index = props.index
+ const resource = categories[index]
+
+ return (
+
+
:
}
+ onClick={() => !resource.isLeaf && collapsedCategories[resource.name].set(!resource.collapsed)}
+ />
+
onClick(resource)} />
+ onClick(resource)}>
+ {resource.name}
+
+
+ )
+}
+
+const AssetPanel = () => {
+ const { t } = useTranslation()
+ const collapsedCategories = useHookstate<{ [key: string]: boolean }>({})
+ const categories = useHookstate
([])
+ const selectedCategory = useHookstate(null)
+ const loading = useHookstate(false)
+ const searchText = useHookstate('')
+ const searchTimeoutCancelRef = useRef<(() => void) | null>(null)
+ const searchedStaticResources = useHookstate([])
+
+ const CategoriesList = () => {
+ return (
+
+ {categories.map((category, index) => (
+
{
+ selectedCategory.set(JSON.parse(JSON.stringify(resource)))
+ },
+ collapsedCategories
+ }}
+ index={index}
+ />
+ ))}
+
+ )
+ }
+
+ useEffect(() => {
+ const result: Category[] = []
+ const generateCategories = (node: object, depth = 0) => {
+ for (const key in node) {
+ const isLeaf = Object.keys(node[key]).length === 0
+ const category = {
+ name: key,
+ object: node[key],
+ collapsed: collapsedCategories[key].value ?? true,
+ depth,
+ isLeaf
+ }
+ result.push(category)
+ if (typeof node[key] === 'object' && !category.collapsed) {
+ generateCategories(node[key], depth + 1)
+ }
+ }
+ }
+ generateCategories(getState(AssetsPanelCategories))
+ categories.set(result)
+ }, [collapsedCategories])
+
+ useEffect(() => {
+ const staticResourcesFindApi = () => {
+ const query = {
+ key: { $like: `%${searchText.value}%` || undefined },
+ $sort: { mimeType: 1 },
+ $limit: 10000
+ }
+
+ if (selectedCategory.value) {
+ const tags = [selectedCategory.value.name, ...iterativelyListTags(selectedCategory.value.object)]
+ query['tags'] = {
+ $or: tags.flatMap((tag) => [
+ { tags: { $like: `%${tag.toLowerCase()}%` } },
+ { tags: { $like: `%${tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase()}%` } }
+ ])
+ }
+ }
+ Engine.instance.api
+ .service(staticResourcePath)
+ .find({ query })
+ .then((resources) => {
+ searchedStaticResources.set(resources.data)
+ })
+ .then(() => {
+ loading.set(false)
+ })
+ }
+
+ loading.set(true)
+
+ searchTimeoutCancelRef.current?.()
+ const debouncedSearchQuery = debounce(staticResourcesFindApi, 500)
+ debouncedSearchQuery()
+
+ searchTimeoutCancelRef.current = debouncedSearchQuery.cancel
+
+ return () => searchTimeoutCancelRef.current?.()
+ }, [searchText, selectedCategory])
+
+ const ResourceItems = () => {
+ if (loading.value) {
+ return (
+
+
+
+ )
+ }
+ return searchedStaticResources.value ? (
+ <>
+ {searchedStaticResources.map((resource) => (
+
+ ))}
+ >
+ ) : (
+ {t('editor:layout.scene-assets.no-search-results')}
+ )
+ }
+
+ return (
+ <>
+
+
+
+ {
+ searchText.set(e.target.value)
+ }}
+ className="w-full rounded bg-theme-primary text-white"
+ startComponent={}
+ />
+
+
+
+
+
+
+ >
+ )
+}
+
+export default AssetPanel
diff --git a/packages/ui/src/components/editor/panels/Assets/index.stories.tsx b/packages/ui/src/components/editor/panels/Assets/index.stories.tsx
new file mode 100644
index 0000000000..1734a23a1c
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Assets/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Assets',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'AssetsPanelTitle',
+ jest: 'AssetsPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Assets/index.tsx b/packages/ui/src/components/editor/panels/Assets/index.tsx
new file mode 100644
index 0000000000..68f8ec91f8
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Assets/index.tsx
@@ -0,0 +1,53 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import AssetsPanel from './container'
+
+export const AssetsPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
+
+export default AssetsPanelTitle
+
+export const AssetsPanelTab: TabData = {
+ id: 'assetsPanel',
+ closable: true,
+ title: ,
+ content:
+}
diff --git a/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx
new file mode 100644
index 0000000000..93553b17c9
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx
@@ -0,0 +1,466 @@
+/*
+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 { fileBrowserPath, staticResourcePath } from '@etherealengine/common/src/schema.type.module'
+import {
+ FilesViewModeSettings,
+ availableTableColumns
+} from '@etherealengine/editor/src/components/assets/FileBrowser/FileBrowserState'
+import { FileDataType } from '@etherealengine/editor/src/components/assets/FileBrowser/FileDataType'
+import { SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
+import { getSpawnPositionAtCenter } from '@etherealengine/editor/src/functions/screenSpaceFunctions'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { useFind, useMutation } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
+import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
+import React, { MouseEventHandler, MutableRefObject, useEffect, useState } from 'react'
+import { ConnectDragSource, ConnectDropTarget, useDrag, useDrop } from 'react-dnd'
+import { getEmptyImage } from 'react-dnd-html5-backend'
+import { useTranslation } from 'react-i18next'
+import { IoIosArrowForward } from 'react-icons/io'
+import { VscBlank } from 'react-icons/vsc'
+import { Vector3 } from 'three'
+import Button from '../../../../../primitives/tailwind/Button'
+import Input from '../../../../../primitives/tailwind/Input'
+import { ContextMenu } from '../../../layout/ContextMenu'
+import { FileIcon } from '../icon'
+
+const RenameInput = ({ fileName, onNameChanged }: { fileName: string; onNameChanged: (newName: string) => void }) => {
+ const newFileName = useHookstate(fileName)
+
+ return (
+ newFileName.set(event.target.value)}
+ onKeyUp={async (e) => {
+ if (e.key == 'Enter') {
+ onNameChanged(newFileName.value)
+ }
+ }}
+ />
+ )
+}
+
+export const canDropItemOverFolder = (folderName: string) =>
+ folderName.endsWith('/assets') ||
+ folderName.indexOf('/assets/') !== -1 ||
+ folderName.endsWith('/public') ||
+ folderName.indexOf('/public/') !== -1
+
+/**
+ * if `wrap` is enabled, wraps the `children` inside a `TableBody` with Table Heading and Table Component attached
+ */
+export const FileTableWrapper = ({ wrap, children }: { wrap: boolean; children: JSX.Element }): JSX.Element => {
+ if (!wrap) {
+ return children
+ }
+ const { t } = useTranslation()
+ const selectedTableColumns = useHookstate(getMutableState(FilesViewModeSettings).list.selectedTableColumns).value
+ const fontSize = useHookstate(getMutableState(FilesViewModeSettings).list.fontSize).value
+ return (
+
+
+
+
+ {availableTableColumns
+ .filter((header) => selectedTableColumns[header])
+ .map((header) => (
+
+ {t(`editor:layout.filebrowser.table-list.headers.${header}`)}
+ |
+ ))}
+
+
+ {children}
+
+
+ )
+}
+
+export const FileTableListBody = ({
+ file,
+ onContextMenu,
+ isRenaming,
+ onNameChanged,
+ onClick,
+ onDoubleClick,
+ modifiedDate,
+ drop,
+ isOver,
+ drag
+}: {
+ file: FileDataType
+ onContextMenu: React.MouseEventHandler
+ isRenaming: boolean
+ onNameChanged: (newName: string) => void
+ onClick?: MouseEventHandler
+ onDoubleClick?: MouseEventHandler
+ modifiedDate?: string
+ drop?: ConnectDropTarget
+ isOver: boolean
+ drag?: ConnectDragSource
+}) => {
+ const selectedTableColumns = useHookstate(getMutableState(FilesViewModeSettings).list.selectedTableColumns).value
+ const fontSize = useHookstate(getMutableState(FilesViewModeSettings).list.fontSize).value
+ const dragFn = drag ?? ((input) => input)
+ const dropFn = drop ?? ((input) => input)
+
+ const staticResource = useFind(staticResourcePath, { query: { key: file.key } })
+ const thumbnailURL = staticResource.data[0]?.thumbnailURL
+
+ const tableColumns = {
+ name: (
+
+ {file.isFolder ? : }
+
+ {isRenaming ? : file.fullName}
+
+ ),
+ type: file.type.toUpperCase(),
+ dateModified: modifiedDate || '',
+ size: file.size
+ }
+ return (
+ {} : onClick}
+ onDoubleClick={isRenaming ? () => {} : onDoubleClick}
+ ref={(ref) => dragFn(dropFn(ref))}
+ >
+ {availableTableColumns
+ .filter((header) => selectedTableColumns[header])
+ .map((header, idx) => (
+
+ {tableColumns[header]}
+ |
+ ))}
+
+ )
+}
+
+type FileGridItemProps = {
+ item: FileDataType
+ isRenaming: boolean
+ onDoubleClick?: MouseEventHandler
+ onClick?: MouseEventHandler
+ onNameChanged: (newName: string) => void
+}
+
+export const FileGridItem: React.FC = (props) => {
+ const iconSize = useHookstate(getMutableState(FilesViewModeSettings).icons.iconSize).value
+ const staticResource = useFind(staticResourcePath, { query: { key: props.item.key } })
+ const thumbnailURL = staticResource.data[0]?.thumbnailURL
+ return (
+
+
+
+
+ {props.isRenaming ? (
+ <>>
+ ) : (
+ /*
*/
+
+ {props.item.fullName}
+
+ )}
+
+ )
+}
+
+type FileBrowserItemType = {
+ item: FileDataType
+ disableDnD?: boolean
+ currentContent: MutableRefObject<{ item: FileDataType; isCopy: boolean }>
+ setFileProperties: any
+ setOpenPropertiesModal: any
+ setOpenCompress: any
+ setOpenConvert: any
+ isFilesLoading: boolean
+ deleteContent: (contentPath: string, type: string) => void
+ onClick: (params: FileDataType) => void
+ dropItemsOnPanel: (data: any, dropOn?: FileDataType) => void
+ moveContent: (oldName: string, newName: string, oldPath: string, newPath: string, isCopy?: boolean) => Promise
+ addFolder: () => void
+ isListView: boolean
+ staticResourceModifiedDates: Record
+}
+
+export function FileBrowserItem({
+ item,
+ disableDnD,
+ currentContent,
+ setOpenPropertiesModal,
+ setFileProperties,
+ setOpenCompress,
+ setOpenConvert,
+ deleteContent,
+ onClick,
+ dropItemsOnPanel,
+ moveContent,
+ isFilesLoading,
+ addFolder,
+ isListView,
+ staticResourceModifiedDates
+}: FileBrowserItemType) {
+ const { t } = useTranslation()
+ const [anchorPosition, setAnchorPosition] = React.useState(undefined)
+ const [anchorEl, setAnchorEl] = React.useState(null)
+ const open = Boolean(anchorEl)
+ const [renamingAsset, setRenamingAsset] = useState(false)
+
+ const fileService = useMutation(fileBrowserPath)
+
+ const handleContextMenu = (event: React.MouseEvent) => {
+ event.preventDefault()
+ event.stopPropagation()
+ setAnchorEl(event.currentTarget)
+ setAnchorPosition({
+ left: event.clientX + 2,
+ top: event.clientY - 6
+ })
+ }
+
+ const handleClose = () => {
+ setAnchorEl(null)
+ setAnchorPosition({ left: 0, top: 0 })
+ }
+
+ const onClickItem = () => onClick(item)
+
+ const placeObjectAtOrigin = () => {
+ addMediaNode(item.url)
+ handleClose()
+ }
+
+ const placeObject = async () => {
+ const vec3 = new Vector3()
+ getSpawnPositionAtCenter(vec3)
+ addMediaNode(item.url, undefined, undefined, [{ name: TransformComponent.jsonID, props: { position: vec3 } }])
+ handleClose()
+ }
+
+ const copyURL = () => {
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(item.url)
+ }
+ handleClose()
+ }
+
+ const openURL = () => {
+ window.open(item.url)
+ handleClose()
+ }
+
+ const Copy = () => {
+ currentContent.current = { item: item, isCopy: true }
+ handleClose()
+ }
+
+ const Cut = () => {
+ currentContent.current = { item: item, isCopy: false }
+ handleClose()
+ }
+
+ const pasteContent = async () => {
+ handleClose()
+
+ if (isFilesLoading) return
+ fileService.update(null, {
+ oldName: currentContent.current.item.fullName,
+ newName: currentContent.current.item.fullName,
+ oldPath: currentContent.current.item.path,
+ newPath: item.isFolder ? item.path + item.fullName : item.path,
+ isCopy: currentContent.current.isCopy
+ })
+ }
+
+ const viewAssetProperties = () => {
+ setFileProperties(item)
+
+ setOpenPropertiesModal(true)
+ handleClose()
+ }
+
+ const viewCompress = () => {
+ setFileProperties(item)
+ setOpenCompress(true)
+ handleClose()
+ }
+
+ const viewConvert = () => {
+ setFileProperties(item)
+ setOpenConvert(true)
+ handleClose()
+ }
+
+ const deleteContentCallback = () => {
+ deleteContent(item.key, item.type)
+ handleClose()
+ }
+
+ const onNameChanged = async (fileName: string): Promise => {
+ setRenamingAsset(false)
+
+ await moveContent(item.fullName, item.isFolder ? fileName : `${fileName}.${item.type}`, item.path, item.path, false)
+ }
+
+ const rename = () => {
+ setRenamingAsset(true)
+ handleClose()
+ }
+
+ const [_dragProps, drag, preview] = disableDnD
+ ? [undefined, undefined, undefined]
+ : useDrag(() => ({
+ type: item.type,
+ item,
+ multiple: false
+ }))
+
+ const [{ isOver }, drop] = disableDnD
+ ? [{ isOver: false }, undefined]
+ : useDrop({
+ accept: [...SupportedFileTypes],
+ drop: (dropItem) => dropItemsOnPanel(dropItem, item),
+ canDrop: (dropItem: Record) =>
+ item.isFolder && ('key' in dropItem || canDropItemOverFolder(item.key)),
+ collect: (monitor) => ({
+ isOver: monitor.canDrop() && monitor.isOver()
+ })
+ })
+
+ useEffect(() => {
+ if (preview) preview(getEmptyImage(), { captureDraggingState: true })
+ }, [preview])
+
+ return (
+ <>
+ {isListView ? (
+
+ ) : (
+
+ )}
+
+
+
+ {!item.isFolder && (
+
+ )}
+ {!item.isFolder && (
+
+ )}
+ {!item.isFolder && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Files/container/index.stories.tsx b/packages/ui/src/components/editor/panels/Files/container/index.stories.tsx
new file mode 100644
index 0000000000..b37f34cdd0
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/container/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Files/Container',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'FilesPanel',
+ jest: 'FilesPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Files/container/index.tsx b/packages/ui/src/components/editor/panels/Files/container/index.tsx
new file mode 100644
index 0000000000..6e8382816a
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/container/index.tsx
@@ -0,0 +1,645 @@
+/*
+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 { FileThumbnailJobState } from '@etherealengine/client-core/src/common/services/FileThumbnailJobState'
+import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService'
+import { uploadToFeathersService } from '@etherealengine/client-core/src/util/upload'
+import config from '@etherealengine/common/src/config'
+import {
+ FileBrowserContentType,
+ archiverPath,
+ fileBrowserPath,
+ fileBrowserUploadPath,
+ staticResourcePath
+} from '@etherealengine/common/src/schema.type.module'
+import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes'
+import { processFileName } from '@etherealengine/common/src/utils/processFileName'
+import { Engine } from '@etherealengine/ecs'
+import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel'
+import {
+ FilesViewModeSettings,
+ FilesViewModeState,
+ availableTableColumns
+} from '@etherealengine/editor/src/components/assets/FileBrowser/FileBrowserState'
+import { FileDataType } from '@etherealengine/editor/src/components/assets/FileBrowser/FileDataType'
+import { DndWrapper } from '@etherealengine/editor/src/components/dnd/DndWrapper'
+import { SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { downloadBlobAsZip, inputFileWithAddToScene } from '@etherealengine/editor/src/functions/assetFunctions'
+import { bytesToSize, unique } from '@etherealengine/editor/src/functions/utils'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
+import {
+ ImageConvertDefaultParms,
+ ImageConvertParms
+} from '@etherealengine/engine/src/assets/constants/ImageConvertParms'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { useFind, useMutation, useSearch } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
+import React, { useEffect, useRef } from 'react'
+import { useDrop } from 'react-dnd'
+import { useTranslation } from 'react-i18next'
+import { FaList } from 'react-icons/fa'
+import { FiDownload, FiGrid, FiRefreshCcw } from 'react-icons/fi'
+import { HiOutlinePlusCircle } from 'react-icons/hi'
+import { HiMagnifyingGlass } from 'react-icons/hi2'
+import { IoArrowBack, IoSettingsSharp } from 'react-icons/io5'
+import { PiFolderPlusBold } from 'react-icons/pi'
+import { twMerge } from 'tailwind-merge'
+import { FilesPanelTab } from '..'
+import Button from '../../../../../primitives/tailwind/Button'
+import Input from '../../../../../primitives/tailwind/Input'
+import LoadingView from '../../../../../primitives/tailwind/LoadingView'
+import Slider from '../../../../../primitives/tailwind/Slider'
+import Tooltip from '../../../../../primitives/tailwind/Tooltip'
+import BooleanInput from '../../../input/Boolean'
+import InputGroup from '../../../input/Group'
+import Popover from '../../../layout/Popover'
+import { FileBrowserItem, FileTableWrapper, canDropItemOverFolder } from '../browserGrid'
+
+type FileBrowserContentPanelProps = {
+ onSelectionChanged: (assetSelectionChange: AssetSelectionChangePropsType) => void
+ disableDnD?: boolean
+ selectedFile?: string
+ folderName?: string
+ nestingDirectory?: string
+}
+
+type DnDFileType = {
+ dataTransfer: DataTransfer
+ files: File[]
+ items: DataTransferItemList
+}
+
+export const FILES_PAGE_LIMIT = 100
+
+export type FileType = {
+ fullName: string
+ isFolder: boolean
+ key: string
+ name: string
+ path: string
+ size: string
+ type: string
+ url: string
+}
+
+const fileConsistsOfContentType = function (file: FileType, contentType: string): boolean {
+ if (file.isFolder) {
+ return contentType.startsWith('image')
+ } else {
+ const guessedType: string = CommonKnownContentTypes[file.type]
+ return guessedType?.startsWith(contentType)
+ }
+}
+
+export function isFileDataType(value: any): value is FileDataType {
+ return value && value.key
+}
+
+/**
+ * FileBrowserPanel used to render view for AssetsPanel.
+ */
+const FileBrowserContentPanel: React.FC = (props) => {
+ const { t } = useTranslation()
+
+ const originalPath = `/${props.folderName || 'projects'}/${props.selectedFile ? props.selectedFile + '/' : ''}`
+ const selectedDirectory = useHookstate(originalPath)
+ const nestingDirectory = useHookstate(props.nestingDirectory || 'projects')
+ const fileProperties = useHookstate(null)
+ const anchorEl = useHookstate(null)
+
+ const openProperties = useHookstate(false)
+ const openCompress = useHookstate(false)
+ const openConvert = useHookstate(false)
+ const convertProperties = useHookstate(ImageConvertDefaultParms)
+
+ const openConfirm = useHookstate(false)
+ const contentToDeletePath = useHookstate('')
+
+ const filesViewMode = useHookstate(getMutableState(FilesViewModeState).viewMode)
+ const [anchorPosition, setAnchorPosition] = React.useState(undefined)
+
+ const page = useHookstate(0)
+
+ const fileQuery = useFind(fileBrowserPath, {
+ query: {
+ $skip: page.value,
+ $limit: FILES_PAGE_LIMIT * 100,
+ directory: selectedDirectory.value
+ }
+ })
+
+ const searchText = useHookstate('')
+
+ useSearch(
+ fileQuery,
+ {
+ key: {
+ $like: `%${searchText.value}%`
+ }
+ },
+ searchText.value
+ )
+
+ const fileService = useMutation(fileBrowserPath)
+
+ const isLoading = fileQuery.status === 'pending'
+
+ const files = fileQuery.data.map((file: FileBrowserContentType) => {
+ const isFolder = file.type === 'folder'
+ const fullName = isFolder ? file.name : file.name + '.' + file.type
+
+ return {
+ ...file,
+ size: file.size ? bytesToSize(file.size) : '0',
+ path: isFolder ? file.key.split(file.name)[0] : file.key.split(fullName)[0],
+ fullName,
+ isFolder
+ }
+ })
+
+ useEffect(() => {
+ FileThumbnailJobState.processFiles(fileQuery.data as FileBrowserContentType[])
+ }, [fileQuery.data])
+
+ useEffect(() => {
+ refreshDirectory()
+ }, [selectedDirectory])
+
+ const refreshDirectory = async () => {
+ fileQuery.refetch()
+ }
+
+ const changeDirectoryByPath = (path: string) => {
+ selectedDirectory.set(path)
+ fileQuery.setPage(0)
+ }
+
+ const onSelect = (params: FileDataType) => {
+ if (params.type !== 'folder') {
+ props.onSelectionChanged({
+ resourceUrl: params.url,
+ name: params.name,
+ contentType: params.type,
+ size: params.size
+ })
+ } else {
+ const newPath = `${selectedDirectory.value}${params.name}/`
+ changeDirectoryByPath(newPath)
+ }
+ }
+
+ const handlePageChange = async (_event, newPage: number) => {
+ page.set(newPage)
+ }
+
+ const createNewFolder = async () => {
+ fileService.create(`${selectedDirectory.value}New_Folder`)
+ }
+
+ const dropItemsOnPanel = async (data: FileDataType | DnDFileType, dropOn?: FileDataType) => {
+ if (isLoading) return
+
+ const path = dropOn?.isFolder ? dropOn.key : selectedDirectory.value
+
+ if (isFileDataType(data)) {
+ if (dropOn?.isFolder) {
+ moveContent(data.fullName, data.fullName, data.path, path, false)
+ }
+ } else {
+ await Promise.all(
+ data.files.map(async (file) => {
+ const assetType = !file.type ? AssetLoader.getAssetType(file.name) : file.type
+ if (!assetType) {
+ // file is directory
+ fileService.create(`${path}${file.name}`)
+ } else {
+ try {
+ const name = processFileName(file.name)
+ await uploadToFeathersService(fileBrowserUploadPath, [file], {
+ fileName: name,
+ path,
+ contentType: file.type
+ }).promise
+ } catch (err) {
+ NotificationService.dispatchNotify(err.message, { variant: 'error' })
+ }
+ }
+ })
+ )
+ }
+
+ await refreshDirectory()
+ }
+
+ const onBackDirectory = () => {
+ const pattern = /([^/]+)/g
+ const result = selectedDirectory.value.match(pattern)
+ if (!result || result.length === 1) return
+ let newPath = '/'
+ for (let i = 0; i < result.length - 1; i++) {
+ newPath += result[i] + '/'
+ }
+ changeDirectoryByPath(newPath)
+ }
+
+ const moveContent = async (
+ oldName: string,
+ newName: string,
+ oldPath: string,
+ newPath: string,
+ isCopy = false
+ ): Promise => {
+ if (isLoading) return
+ fileService.update(null, { oldName, newName, oldPath, newPath, isCopy })
+ }
+
+ const handleConfirmDelete = (contentPath: string, type: string) => {
+ contentToDeletePath.set(contentPath)
+
+ openConfirm.set(true)
+ }
+
+ const handleConfirmClose = () => {
+ contentToDeletePath.set('')
+
+ openConfirm.set(false)
+ }
+
+ const deleteContent = async (): Promise => {
+ if (isLoading) return
+ openConfirm.set(false)
+ fileService.remove(contentToDeletePath.value)
+ props.onSelectionChanged({ resourceUrl: '', name: '', contentType: '', size: '' })
+ }
+
+ const currentContentRef = useRef(null! as { item: FileDataType; isCopy: boolean })
+
+ const showUploadAndDownloadButtons =
+ selectedDirectory.value.slice(1).startsWith('projects/') &&
+ !['projects', 'projects/'].includes(selectedDirectory.value.slice(1))
+ const showBackButton = selectedDirectory.value.split('/').length > originalPath.split('/').length
+
+ const handleDownloadProject = async () => {
+ const url = selectedDirectory.value
+ const data = await Engine.instance.api
+ .service(archiverPath)
+ .get(null, { query: { directory: url } })
+ .catch((err: Error) => {
+ NotificationService.dispatchNotify(err.message, { variant: 'warning' })
+ return null
+ })
+ if (!data) return
+ const blob = await (await fetch(`${config.client.fileServer}/${data}`)).blob()
+
+ let fileName: string
+ if (selectedDirectory.value[selectedDirectory.value.length - 1] === '/') {
+ fileName = selectedDirectory.value.split('/').at(-2) as string
+ } else {
+ fileName = selectedDirectory.value.split('/').at(-1) as string
+ }
+
+ downloadBlobAsZip(blob, fileName)
+ }
+
+ const BreadcrumbItems = () => {
+ const handleBreadcrumbDirectoryClick = (targetFolder: string) => {
+ const pattern = /([^/]+)/g
+ const result = selectedDirectory.value.match(pattern)
+ if (!result) return
+ let newPath = '/'
+ for (const folder of result) {
+ newPath += folder + '/'
+ if (folder === targetFolder) {
+ break
+ }
+ }
+ changeDirectoryByPath(newPath)
+ }
+ let breadcrumbDirectoryFiles = selectedDirectory.value.slice(1, -1).split('/')
+
+ const nestedIndex = breadcrumbDirectoryFiles.indexOf(nestingDirectory.value)
+
+ breadcrumbDirectoryFiles = breadcrumbDirectoryFiles.filter((_, idx) => idx >= nestedIndex)
+
+ return (
+
+ )
+ }
+
+ const DropArea = () => {
+ const [{ isFileDropOver }, fileDropRef] = useDrop({
+ accept: [...SupportedFileTypes],
+ canDrop: (item: Record) => 'key' in item || canDropItemOverFolder(selectedDirectory.value),
+ drop: (dropItem) => dropItemsOnPanel(dropItem as any),
+ collect: (monitor) => ({ isFileDropOver: monitor.canDrop() && monitor.isOver() })
+ })
+
+ const isListView = filesViewMode.value === 'list'
+ const staticResourceData = useFind(staticResourcePath, {
+ query: {
+ key: {
+ $in: isListView ? files.map((file) => file.key) : []
+ },
+ $select: ['key', 'updatedAt'] as any,
+ $limit: FILES_PAGE_LIMIT
+ }
+ })
+ const staticResourceModifiedDates = useHookstate>({})
+
+ useEffect(() => {
+ const modifiedDates: Record = {}
+ staticResourceData.data.forEach((data) => {
+ modifiedDates[data.key] = new Date(data.updatedAt).toLocaleString()
+ })
+ staticResourceModifiedDates.set(modifiedDates)
+ }, [staticResourceData.data])
+
+ return (
+
+
+
+ <>
+ {unique(files, (file) => file.key).map((file, i) => (
+
+ ))}
+ >
+
+ {/*
+ {total > 0 && validFiles.value.length < total && (
+
+ )}*/}
+
+
+ )
+ }
+
+ const ViewModeSettings = () => {
+ const viewModeSettings = useHookstate(getMutableState(FilesViewModeSettings))
+ return (
+ <>
+
+
+ }
+ className="p-0"
+ onClick={(event) => {
+ setAnchorPosition({ left: event.clientX, top: event.clientY })
+ anchorEl.set(event.currentTarget)
+ }}
+ />
+
+
+ {
+ anchorEl.set(null)
+ setAnchorPosition(undefined)
+ }}
+ panelId={FilesPanelTab.id!}
+ anchorPosition={anchorPosition}
+ className="w-45 flex min-w-[300px] flex-col p-2"
+ >
+ {filesViewMode.value === 'icons' ? (
+
+
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+ {availableTableColumns.map((column) => (
+
+ viewModeSettings.list.selectedTableColumns[column].set(value)}
+ />
+
+ ))}
+
+
+ >
+ )}
+
+ >
+ )
+ }
+
+ const viewModes = [
+ { mode: 'list', icon: },
+ { mode: 'icons', icon: }
+ ]
+
+ return (
+ <>
+
+
+
+ } className={`p-0`} onClick={onBackDirectory} />
+
+
+
+
+
+ } className="p-0" onClick={refreshDirectory} />
+
+
+
+
+
+
+ {viewModes.map(({ mode, icon }) => (
+
+
+
+
+
+
+
{
+ searchText.set(e.target.value)
+ }}
+ labelClassname="text-sm text-red-500"
+ containerClassname="flex h-full bg-theme-primary rounded-[4px] w-full"
+ className="h-7 w-full rounded-[4px] bg-theme-primary py-0 text-xs text-[#A3A3A3] placeholder:text-[#A3A3A3] focus-visible:ring-0"
+ startComponent={
}
+ />
+
+
+
+
+ } className="p-0" onClick={createNewFolder} />
+
+
+
+
+
+ }
+ className="p-0"
+ onClick={handleDownloadProject}
+ />
+
+
+
+
}
+ variant="transparent"
+ disabled={!showUploadAndDownloadButtons}
+ rounded="none"
+ className="h-full whitespace-nowrap bg-theme-highlight px-2"
+ size="small"
+ onClick={async () => {
+ await inputFileWithAddToScene({ directoryPath: selectedDirectory.value })
+ .then(refreshDirectory)
+ .catch((err) => {
+ NotificationService.dispatchNotify(err.message, { variant: 'error' })
+ })
+ }}
+ >
+ {t('editor:layout.filebrowser.uploadAssets')}
+
+
+ {isLoading && }
+
+
+
+
+
+ >
+ )
+}
+
+export default function FilesPanelContainer() {
+ const assetsPreviewPanelRef = React.useRef()
+ const projectName = useHookstate(getMutableState(EditorState).projectName).value
+
+ const onSelectionChanged = (props: AssetSelectionChangePropsType) => {
+ ;(assetsPreviewPanelRef as any).current?.onSelectionChanged?.(props)
+ }
+
+ return (
+ <>
+
+ >
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Files/icon/index.tsx b/packages/ui/src/components/editor/panels/Files/icon/index.tsx
new file mode 100644
index 0000000000..95034f9036
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/icon/index.tsx
@@ -0,0 +1,95 @@
+/*
+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 React from 'react'
+//import styles from '../styles.module.scss'
+import { HiFolder } from 'react-icons/hi2'
+import { IoAccessibilityOutline } from 'react-icons/io5'
+
+import { MdOutlineAudioFile, MdOutlinePhotoSizeSelectActual, MdOutlineViewInAr } from 'react-icons/md'
+import { PiVideoCameraBold } from 'react-icons/pi'
+import { TbFileDescription } from 'react-icons/tb'
+
+const FileIconType = {
+ gltf: MdOutlineViewInAr,
+ 'gltf-binary': MdOutlineViewInAr,
+ glb: MdOutlineViewInAr,
+ vrm: IoAccessibilityOutline,
+ usdz: MdOutlineViewInAr,
+ fbx: MdOutlineViewInAr,
+ png: MdOutlinePhotoSizeSelectActual,
+ jpeg: MdOutlinePhotoSizeSelectActual,
+ jpg: MdOutlinePhotoSizeSelectActual,
+ ktx2: MdOutlinePhotoSizeSelectActual,
+ m3u8: PiVideoCameraBold,
+ mp4: PiVideoCameraBold,
+ mpeg: MdOutlineAudioFile,
+ mp3: MdOutlineAudioFile,
+ 'model/gltf-binary': MdOutlineViewInAr,
+ 'model/gltf': MdOutlineViewInAr,
+ 'model/glb': MdOutlineViewInAr,
+ 'model/vrm': IoAccessibilityOutline,
+ 'model/usdz': MdOutlineViewInAr,
+ 'model/fbx': MdOutlineViewInAr,
+ 'image/png': MdOutlinePhotoSizeSelectActual,
+ 'image/jpeg': MdOutlinePhotoSizeSelectActual,
+ 'image/jpg': MdOutlinePhotoSizeSelectActual,
+ 'application/pdf': null,
+ 'application/vnd.apple.mpegurl': PiVideoCameraBold,
+ 'video/mp4': PiVideoCameraBold,
+ 'audio/mpeg': MdOutlineAudioFile,
+ 'audio/mp3': MdOutlineAudioFile
+}
+
+export const FileIcon = ({
+ thumbnailURL,
+ type,
+ isFolder,
+ color = 'text-white'
+}: {
+ thumbnailURL: string | null
+ type: string
+ isFolder?: boolean
+ color?: string
+}) => {
+ const FallbackIcon = FileIconType[type ?? '']
+
+ return (
+ <>
+ {isFolder ? (
+
+ ) : thumbnailURL != null ? (
+
+ ) : FallbackIcon ? (
+
+ ) : (
+ <>
+
+ {/* type && type.length > 0 && showRibbon && {type} */}
+ >
+ )}
+ >
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Files/index.stories.tsx b/packages/ui/src/components/editor/panels/Files/index.stories.tsx
new file mode 100644
index 0000000000..dab8343489
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Files',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'FilesPanelTitle',
+ jest: 'FilesPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Files/index.tsx b/packages/ui/src/components/editor/panels/Files/index.tsx
new file mode 100644
index 0000000000..fefd67a1f2
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Files/index.tsx
@@ -0,0 +1,54 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import FilesPanelContainer from './container'
+
+export const FilesPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
+
+export default FilesPanelTitle
+
+export const FilesPanelTab: TabData = {
+ id: 'filesPanel',
+ closable: true,
+ title: ,
+ content:
+}
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/container/index.stories.tsx b/packages/ui/src/components/editor/panels/Hierarchy/container/index.stories.tsx
new file mode 100644
index 0000000000..663581b1d8
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/container/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Hierarchy/Container',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'HierarchyPanel',
+ jest: 'HierarchyPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx b/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx
new file mode 100644
index 0000000000..4aa53be61d
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/container/index.tsx
@@ -0,0 +1,607 @@
+/*
+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 { getComponent, getMutableComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { AllFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
+import { getMutableState, getState, none, useHookstate, useMutableState } from '@etherealengine/hyperflux'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import {
+ EntityTreeComponent,
+ isAncestor,
+ traverseEntityNode
+} from '@etherealengine/spatial/src/transform/components/EntityTree'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useDrop } from 'react-dnd'
+import Hotkeys from 'react-hot-keys'
+import { useTranslation } from 'react-i18next'
+import AutoSizer from 'react-virtualized-auto-sizer'
+import { FixedSizeList } from 'react-window'
+
+import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService'
+import { Engine, EntityUUID, UUIDComponent, entityExists } from '@etherealengine/ecs'
+import { CameraOrbitComponent } from '@etherealengine/spatial/src/camera/components/CameraOrbitComponent'
+
+import useUpload from '@etherealengine/editor/src/components/assets/useUpload'
+import {
+ HeirarchyTreeNodeType,
+ heirarchyTreeWalker
+} from '@etherealengine/editor/src/components/hierarchy/HeirarchyTreeWalker'
+import { ItemTypes, SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { CopyPasteFunctions } from '@etherealengine/editor/src/functions/CopyPasteFunctions'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
+import { cmdOrCtrlString } from '@etherealengine/editor/src/functions/utils'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { GLTFAssetState, GLTFSnapshotState } from '@etherealengine/engine/src/gltf/GLTFState'
+import { HiMagnifyingGlass, HiOutlinePlusCircle } from 'react-icons/hi2'
+import Button from '../../../../../primitives/tailwind/Button'
+import Input from '../../../../../primitives/tailwind/Input'
+import ContextMenu from '../../../layout/ContextMenu'
+import HierarchyTreeNode, { HierarchyTreeNodeProps, RenameNodeData, getNodeElId } from '../node'
+
+const uploadOptions = {
+ multiple: true,
+ accepts: AllFileTypes
+}
+
+/**
+ * HierarchyPanel function component provides view for hierarchy tree.
+ */
+function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: EntityUUID; index: number }) {
+ const { sceneURL, rootEntityUUID, index } = props
+ const { t } = useTranslation()
+ const [contextSelectedItem, setContextSelectedItem] = React.useState(undefined)
+ const [anchorEl, setAnchorEl] = React.useState(null)
+ const [anchorPosition, setAnchorPosition] = React.useState({ left: 0, top: 0 })
+ const [prevClickedNode, setPrevClickedNode] = useState(null)
+ const onUpload = useUpload(uploadOptions)
+ const [renamingNode, setRenamingNode] = useState(null)
+ const expandedNodes = useHookstate(getMutableState(EditorState).expandedNodes)
+ const entityHierarchy = useHookstate([])
+ const [selectedNode, _setSelectedNode] = useState(null)
+ const lockPropertiesPanel = useHookstate(getMutableState(EditorState).lockPropertiesPanel)
+ const searchHierarchy = useHookstate('')
+
+ const rootEntity = UUIDComponent.useEntityByUUID(rootEntityUUID)
+ const rootEntityTree = useComponent(rootEntity, EntityTreeComponent)
+
+ const MemoTreeNode = useCallback(
+ (props: HierarchyTreeNodeProps) => (
+
+ ),
+ [entityHierarchy]
+ )
+
+ const nodeSearch: HeirarchyTreeNodeType[] = []
+ if (searchHierarchy.value.length > 0) {
+ const condition = new RegExp(searchHierarchy.value.toLowerCase())
+ entityHierarchy.value.forEach((node) => {
+ if (node.entity && condition.test(getComponent(node.entity, NameComponent)?.toLowerCase() ?? ''))
+ nodeSearch.push(node)
+ })
+ }
+
+ useEffect(() => {
+ if (!expandedNodes.value[sceneURL]) {
+ expandedNodes.set({ [sceneURL]: { [rootEntity]: true } })
+ }
+ }, [])
+
+ useEffect(() => {
+ entityHierarchy.set(Array.from(heirarchyTreeWalker(sceneURL, rootEntity)))
+ }, [expandedNodes, index, rootEntityTree.children])
+
+ const setSelectedNode = (selection) => !lockPropertiesPanel.value && _setSelectedNode(selection)
+
+ /* Expand & Collapse Functions */
+ const expandNode = useCallback(
+ (node: HeirarchyTreeNodeType) => {
+ expandedNodes[sceneURL][node.entity].set(true)
+ },
+ [expandedNodes]
+ )
+
+ const collapseNode = useCallback(
+ (node: HeirarchyTreeNodeType) => {
+ expandedNodes[sceneURL][node.entity].set(none)
+ },
+ [expandedNodes]
+ )
+
+ const expandChildren = useCallback(
+ (node: HeirarchyTreeNodeType) => {
+ handleClose()
+ traverseEntityNode(node.entity, (child) => {
+ expandedNodes[sceneURL][child].set(true)
+ })
+ },
+ [expandedNodes]
+ )
+
+ const collapseChildren = useCallback(
+ (node: HeirarchyTreeNodeType) => {
+ handleClose()
+ traverseEntityNode(node.entity, (child) => {
+ expandedNodes[sceneURL][child].set(none)
+ })
+ },
+ [expandedNodes]
+ )
+
+ /* Event handlers */
+ const onMouseDown = useCallback(
+ (e: MouseEvent, node: HeirarchyTreeNodeType) => {
+ if (e.detail === 1) {
+ if (e.ctrlKey) {
+ EditorControlFunctions.toggleSelection([getComponent(node.entity, UUIDComponent)])
+ setSelectedNode(null)
+ } else if (e.shiftKey && prevClickedNode) {
+ const startIndex = entityHierarchy.value.findIndex((n) => n.entity === prevClickedNode.entity)
+ const endIndex = entityHierarchy.value.findIndex((n) => n.entity === node.entity)
+ const range = entityHierarchy.value.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex) + 1)
+ const entityUuids = range.filter((n) => n.entity).map((n) => getComponent(n.entity!, UUIDComponent))
+ EditorControlFunctions.replaceSelection(entityUuids)
+ setSelectedNode(node)
+ } else {
+ const selected = getState(SelectionState).selectedEntities.includes(getComponent(node.entity, UUIDComponent))
+ if (!selected) {
+ EditorControlFunctions.replaceSelection([getComponent(node.entity, UUIDComponent)])
+ setSelectedNode(node)
+ }
+ }
+ setPrevClickedNode(node)
+ }
+ },
+ [prevClickedNode, entityHierarchy]
+ )
+
+ const onContextMenu = (event: React.MouseEvent, item: HeirarchyTreeNodeType) => {
+ event.preventDefault()
+ event.stopPropagation()
+
+ setContextSelectedItem(item)
+ setAnchorEl(event.currentTarget)
+ setAnchorPosition({
+ left: event.clientX + 2,
+ top: event.clientY - 6
+ })
+ }
+
+ const handleClose = () => {
+ setContextSelectedItem(undefined)
+ setAnchorEl(null)
+ setAnchorPosition({ left: 0, top: 0 })
+ }
+
+ const onClick = useCallback((e: MouseEvent, node: HeirarchyTreeNodeType) => {
+ if (e.detail === 2) {
+ const editorCameraState = getMutableComponent(Engine.instance.cameraEntity, CameraOrbitComponent)
+ editorCameraState.focusedEntities.set([node.entity])
+ editorCameraState.refocus.set(true)
+ }
+ }, [])
+
+ const onToggle = useCallback(
+ (_, node: HeirarchyTreeNodeType) => {
+ if (expandedNodes.value[sceneURL][node.entity]) collapseNode(node)
+ else expandNode(node)
+ },
+ [expandedNodes, expandNode, collapseNode]
+ )
+
+ const onKeyDown = useCallback(
+ (e: KeyboardEvent, node: HeirarchyTreeNodeType) => {
+ const nodeIndex = entityHierarchy.value.indexOf(node)
+ const entityTree = getComponent(node.entity, EntityTreeComponent)
+ switch (e.key) {
+ case 'ArrowDown': {
+ e.preventDefault()
+
+ const nextNode = nodeIndex !== -1 && entityHierarchy.value[nodeIndex + 1]
+ if (!nextNode) return
+
+ if (e.shiftKey) {
+ EditorControlFunctions.addToSelection([getComponent(nextNode.entity, UUIDComponent)])
+ }
+
+ const nextNodeEl = document.getElementById(getNodeElId(nextNode))
+ if (nextNodeEl) {
+ nextNodeEl.focus()
+ }
+ break
+ }
+
+ case 'ArrowUp': {
+ e.preventDefault()
+
+ const prevNode = nodeIndex !== -1 && entityHierarchy.value[nodeIndex - 1]
+ if (!prevNode) return
+
+ if (e.shiftKey) {
+ EditorControlFunctions.addToSelection([getComponent(prevNode.entity, UUIDComponent)])
+ }
+
+ const prevNodeEl = document.getElementById(getNodeElId(prevNode))
+ if (prevNodeEl) {
+ prevNodeEl.focus()
+ }
+ break
+ }
+
+ case 'ArrowLeft':
+ if (entityTree && (!entityTree.children || entityTree.children.length === 0)) return
+
+ if (e.shiftKey) collapseChildren(node)
+ else collapseNode(node)
+ break
+
+ case 'ArrowRight':
+ if (entityTree && (!entityTree.children || entityTree.children.length === 0)) return
+
+ if (e.shiftKey) expandChildren(node)
+ else expandNode(node)
+ break
+
+ case 'Enter':
+ if (e.shiftKey) {
+ EditorControlFunctions.toggleSelection([getComponent(node.entity, UUIDComponent)])
+ setSelectedNode(null)
+ } else {
+ EditorControlFunctions.replaceSelection([getComponent(node.entity, UUIDComponent)])
+ setSelectedNode(node)
+ }
+ break
+
+ case 'Delete':
+ case 'Backspace':
+ if (selectedNode && !renamingNode) onDeleteNode(selectedNode!)
+ break
+ }
+ },
+ [entityHierarchy, expandNode, collapseNode, expandChildren, collapseChildren, renamingNode, selectedNode]
+ )
+
+ const onDeleteNode = useCallback((node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ const selected = getState(SelectionState).selectedEntities.includes(getComponent(node.entity, UUIDComponent))
+ const objs = selected ? SelectionState.getSelectedEntities() : [node.entity]
+ EditorControlFunctions.removeObject(objs)
+ }, [])
+
+ const onDuplicateNode = useCallback((node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ const selected = getState(SelectionState).selectedEntities.includes(getComponent(node.entity, UUIDComponent))
+ const objs = selected ? SelectionState.getSelectedEntities() : [node.entity]
+ EditorControlFunctions.duplicateObject(objs)
+ }, [])
+
+ const onGroupNodes = useCallback((node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ const selected = getState(SelectionState).selectedEntities.includes(getComponent(node.entity, UUIDComponent))
+ const objs = selected ? SelectionState.getSelectedEntities() : [node.entity]
+
+ EditorControlFunctions.groupObjects(objs)
+ }, [])
+
+ const onCopyNode = useCallback((node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ const selected = getState(SelectionState).selectedEntities.includes(getComponent(node.entity, UUIDComponent))
+ const nodes = selected ? SelectionState.getSelectedEntities() : [node.entity]
+ CopyPasteFunctions.copyEntities(nodes)
+ }, [])
+
+ const onPasteNode = useCallback(async (node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ CopyPasteFunctions.getPastedEntities()
+ .then((nodeComponentJSONs) => {
+ nodeComponentJSONs.forEach((componentJSONs) => {
+ EditorControlFunctions.createObjectFromSceneElement(componentJSONs, undefined, node.entity)
+ })
+ })
+ .catch(() => {
+ NotificationService.dispatchNotify(t('editor:hierarchy.copy-paste.no-hierarchy-nodes'), { variant: 'error' })
+ })
+ }, [])
+ /* Event handlers */
+
+ /* Rename functions */
+ const onRenameNode = useCallback((node: HeirarchyTreeNodeType) => {
+ handleClose()
+
+ if (node.entity) {
+ const entity = node.entity
+ setRenamingNode({ entity, name: getComponent(entity, NameComponent) })
+ } else {
+ // todo
+ }
+ }, [])
+
+ const onChangeName = useCallback(
+ (node: HeirarchyTreeNodeType, name: string) => setRenamingNode({ entity: node.entity, name }),
+ []
+ )
+
+ const onRenameSubmit = useCallback((node: HeirarchyTreeNodeType, name: string) => {
+ if (name) {
+ EditorControlFunctions.modifyName([node.entity], name)
+ }
+
+ setRenamingNode(null)
+ }, [])
+ /* Rename functions */
+
+ const [, treeContainerDropTarget] = useDrop({
+ accept: [ItemTypes.Node, ItemTypes.File, ...SupportedFileTypes],
+ drop(item: any, monitor) {
+ if (monitor.didDrop()) return
+
+ // check if item contains files
+ if (item.files) {
+ const dndItem: any = monitor.getItem()
+ const entries = Array.from(dndItem.items).map((item: any) => item.webkitGetAsEntry())
+
+ //uploading files then adding to editor media
+ onUpload(entries).then((assets) => {
+ if (!assets) return
+ for (const asset of assets) addMediaNode(asset)
+ })
+
+ return
+ }
+
+ if (item.url) {
+ addMediaNode(item.url)
+ return
+ }
+
+ if (item.type === ItemTypes.Component) {
+ EditorControlFunctions.createObjectFromSceneElement([{ name: item!.componentJsonID }])
+ return
+ }
+
+ EditorControlFunctions.reparentObject(Array.isArray(item.value) ? item.value : [item.value])
+ },
+ canDrop(item: any, monitor) {
+ if (!monitor.isOver({ shallow: true })) return false
+
+ // check if item is of node type
+ if (item.type === ItemTypes.Node) {
+ const sceneEntity = getState(GLTFAssetState)[sceneURL]
+ return !(item.multiple
+ ? item.value.some((otherObject) => isAncestor(otherObject, sceneEntity))
+ : isAncestor(item.value, sceneEntity))
+ }
+
+ return true
+ }
+ })
+
+ let validNodes = nodeSearch?.length > 0 ? nodeSearch : entityHierarchy.value
+ validNodes = validNodes.filter((node) => entityExists(node.entity))
+
+ const HierarchyList = ({ height, width }) => (
+ index}
+ outerRef={treeContainerDropTarget}
+ innerElementType="ul"
+ >
+ {MemoTreeNode}
+
+ )
+
+ return (
+ <>
+
+ {
+ searchHierarchy.set(event.target.value)
+ }}
+ className="m-1 rounded bg-theme-primary text-white"
+ startComponent={}
+ />
+ }
+ variant="transparent"
+ rounded="none"
+ className="ml-auto w-32 bg-theme-highlight px-2 py-3"
+ size="small"
+ textContainerClassName="mx-0"
+ onClick={() => EditorControlFunctions.createObjectFromSceneElement()}
+ >
+ {t('editor:hierarchy.lbl-addEntity')}
+
+
+
+
+
+ {
+ e.preventDefault()
+ e.stopPropagation()
+ selectedNode && onDuplicateNode(selectedNode!)
+ }}
+ >
+
+
+ {
+ e.preventDefault()
+ e.stopPropagation()
+ selectedNode && onGroupNodes(selectedNode!)
+ }}
+ >
+
+
+ {
+ e.preventDefault()
+ e.stopPropagation()
+ selectedNode && onCopyNode(selectedNode)
+ }}
+ >
+
+
+ {
+ e.preventDefault()
+ e.stopPropagation()
+ selectedNode && onPasteNode(selectedNode)
+ }}
+ >
+
+
+
+
+
+
+ >
+ )
+}
+
+export default function HierarchyPanel() {
+ const sceneID = useHookstate(getMutableState(EditorState).scenePath).value
+ const gltfEntity = useMutableState(EditorState).rootEntity.value
+ if (!sceneID || !gltfEntity) return null
+
+ const GLTFHierarchySub = () => {
+ const rootEntityUUID = getComponent(gltfEntity, UUIDComponent)
+ const sourceID = `${rootEntityUUID}-${sceneID}`
+ const index = GLTFSnapshotState.useSnapshotIndex(sourceID)
+
+ return (
+
+ )
+ }
+
+ return
+}
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/index.stories.tsx b/packages/ui/src/components/editor/panels/Hierarchy/index.stories.tsx
new file mode 100644
index 0000000000..4310662881
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Hierarchy',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'HierarchyPanelTitle',
+ jest: 'HierarchyPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/index.tsx b/packages/ui/src/components/editor/panels/Hierarchy/index.tsx
new file mode 100644
index 0000000000..fcd7913228
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/index.tsx
@@ -0,0 +1,51 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import HierarchyPanel from './container'
+
+export const HierarchyPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+ {t('editor:hierarchy.lbl')}
+
+
+ )
+}
+
+export default HierarchyPanelTitle
+
+export const HierarchyPanelTab: TabData = {
+ id: 'hierarchyPanel',
+ closable: true,
+ title: ,
+ content:
+}
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/node/index.stories.tsx b/packages/ui/src/components/editor/panels/Hierarchy/node/index.stories.tsx
new file mode 100644
index 0000000000..63e19090d3
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/node/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Hierarchy/Node',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'HierarchyPanelTitle',
+ jest: 'HierarchyPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Hierarchy/node/index.tsx b/packages/ui/src/components/editor/panels/Hierarchy/node/index.tsx
new file mode 100644
index 0000000000..f77a15010d
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Hierarchy/node/index.tsx
@@ -0,0 +1,381 @@
+/*
+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 React, { KeyboardEvent, StyleHTMLAttributes, useCallback, useEffect } from 'react'
+import { useDrag, useDrop } from 'react-dnd'
+import { getEmptyImage } from 'react-dnd-html5-backend'
+
+import {
+ getAllComponents,
+ getComponent,
+ getOptionalComponent,
+ hasComponent,
+ useComponent,
+ useOptionalComponent
+} from '@etherealengine/ecs/src/ComponentFunctions'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import { entityExists } from '@etherealengine/ecs/src/EntityFunctions'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import { EntityTreeComponent, isAncestor } from '@etherealengine/spatial/src/transform/components/EntityTree'
+import { PiEyeBold, PiEyeClosedBold } from 'react-icons/pi'
+
+import { MdKeyboardArrowDown, MdKeyboardArrowRight } from 'react-icons/md'
+
+import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
+
+import { UUIDComponent } from '@etherealengine/ecs'
+import useUpload from '@etherealengine/editor/src/components/assets/useUpload'
+import { HeirarchyTreeNodeType } from '@etherealengine/editor/src/components/hierarchy/HeirarchyTreeWalker'
+import { ItemTypes, SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
+import { ComponentEditorsState } from '@etherealengine/editor/src/services/ComponentEditors'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { ResourcePendingComponent } from '@etherealengine/engine/src/gltf/ResourcePendingComponent'
+import { ErrorComponent } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { VisibleComponent, setVisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent'
+import { twMerge } from 'tailwind-merge'
+import TransformPropertyGroup from '../../../properties/transform'
+
+//import styles from './styles.module.scss'
+
+/**
+ * getNodeElId function provides id for node.
+ *
+ * @param {object} node
+ * @return {string}
+ */
+export const getNodeElId = (node: HeirarchyTreeNodeType) => {
+ return 'hierarchy-node-' + node.entity
+}
+
+export type RenameNodeData = {
+ entity: Entity
+ name: string
+}
+
+export type HierarchyTreeNodeData = {
+ nodes: HeirarchyTreeNodeType[]
+ renamingNode: RenameNodeData
+ onToggle: (e: Event, node: HeirarchyTreeNodeType) => void
+ onKeyDown: (e: Event, node: HeirarchyTreeNodeType) => void
+ onMouseDown: (e: MouseEvent, node: HeirarchyTreeNodeType) => void
+ onClick: (e: MouseEvent, node: HeirarchyTreeNodeType) => void
+ onChangeName: (node: HeirarchyTreeNodeType, name: string) => void
+ onRenameSubmit: (node: HeirarchyTreeNodeType, name: string) => void
+ onUpload: ReturnType
+}
+
+export type HierarchyTreeNodeProps = {
+ index: number
+ data: HierarchyTreeNodeData
+ style: StyleHTMLAttributes
+ onContextMenu: (event: React.MouseEvent, item: HeirarchyTreeNodeType) => void
+}
+
+export const HierarchyTreeNode = (props: HierarchyTreeNodeProps) => {
+ const node = props.data.nodes[props.index]
+ const data = props.data
+
+ const uuid = useComponent(node.entity, UUIDComponent)
+
+ const selected = useHookstate(getMutableState(SelectionState).selectedEntities).value.includes(uuid.value)
+
+ const nodeName = useOptionalComponent(node.entity, NameComponent)?.value
+
+ const visible = useOptionalComponent(node.entity, VisibleComponent)
+
+ const errors = useOptionalComponent(node.entity, ErrorComponent)
+
+ const sceneAssetLoading = useOptionalComponent(node.entity, ResourcePendingComponent)
+
+ const toggleVisible = () => {
+ setVisibleComponent(node.entity, !hasComponent(node.entity, VisibleComponent))
+ }
+
+ const onClickToggle = useCallback(
+ (e: MouseEvent) => {
+ e.stopPropagation()
+ if (data.onToggle) data.onToggle(e, node)
+ },
+ [data.onToggle, node]
+ )
+
+ const onNodeKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ e.stopPropagation()
+ if (data.onKeyDown) data.onKeyDown(e as any, node)
+ },
+ [data.onKeyDown, node]
+ )
+
+ const onKeyDownNameInput = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === 'Escape') data.onRenameSubmit(node, null!)
+ else if (e.key === 'Enter') data.onRenameSubmit(node, (e.target as any).value)
+ },
+ [data.onRenameSubmit, node]
+ )
+
+ const onClickNode = useCallback((e) => data.onClick(e, node), [node, data.onClick])
+ const onMouseDownNode = useCallback((e) => data.onMouseDown(e, node), [node, data.onMouseDown])
+
+ const onChangeNodeName = useCallback((e) => data.onChangeName(node, e.target.value), [node, data.onChangeName])
+
+ const [, drag, preview] = useDrag({
+ type: ItemTypes.Node,
+ item() {
+ const selectedEntities = SelectionState.getSelectedEntities()
+ const multiple = selectedEntities.length > 1
+
+ return {
+ type: ItemTypes.Node,
+ multiple,
+ value: multiple ? selectedEntities : selectedEntities[0]
+ }
+ },
+ canDrag() {
+ return !SelectionState.getSelectedEntities().some(
+ (entity) => !getOptionalComponent(entity, EntityTreeComponent)?.parentEntity
+ )
+ },
+ collect: (monitor) => ({
+ isDragging: !!monitor.isDragging()
+ })
+ })
+
+ const dropItem = (node: HeirarchyTreeNodeType, place: 'On' | 'Before' | 'After') => {
+ let parentNode: Entity | undefined
+ let beforeNode: Entity
+
+ if (place === 'Before') {
+ const entityTreeComponent = getOptionalComponent(node.entity, EntityTreeComponent)
+ parentNode = entityTreeComponent?.parentEntity
+ beforeNode = node.entity
+ } else if (place === 'After') {
+ const entityTreeComponent = getOptionalComponent(node.entity, EntityTreeComponent)
+ parentNode = entityTreeComponent?.parentEntity
+ const parentTreeComponent = getOptionalComponent(entityTreeComponent?.parentEntity!, EntityTreeComponent)
+ if (
+ parentTreeComponent &&
+ !node.lastChild &&
+ parentNode &&
+ parentTreeComponent?.children.length > node.childIndex + 1
+ ) {
+ beforeNode = parentTreeComponent.children[node.childIndex + 1]
+ }
+ } else {
+ parentNode = node.entity
+ }
+
+ if (!parentNode)
+ return () => {
+ console.warn('parent is not defined')
+ }
+
+ return (item: any, monitor): void => {
+ if (parentNode) {
+ if (item.files) {
+ const dndItem: any = monitor.getItem()
+ const entries = Array.from(dndItem.items).map((item: any) => item.webkitGetAsEntry())
+
+ //uploading files then adding as media to the editor
+ data.onUpload(entries).then((assets) => {
+ if (!assets) return
+ for (const asset of assets) {
+ addMediaNode(asset, parentNode, beforeNode)
+ }
+ })
+ return
+ }
+
+ if (item.url) {
+ addMediaNode(item.url, parentNode, beforeNode)
+ return
+ }
+
+ if (item.type === ItemTypes.Component) {
+ EditorControlFunctions.createObjectFromSceneElement([{ name: item!.componentJsonID }], parentNode, beforeNode)
+ return
+ }
+ }
+
+ EditorControlFunctions.reparentObject(
+ Array.isArray(item.value) ? item.value : [item.value],
+ beforeNode,
+ parentNode === null ? undefined : parentNode
+ )
+ }
+ }
+
+ const canDropItem = (entityNode: Entity, dropOn?: boolean) => {
+ return (item, monitor): boolean => {
+ //check if monitor is over or object is not parent element
+ if (!monitor.isOver()) return false
+
+ if (!dropOn) {
+ const entityTreeComponent = getComponent(entityNode, EntityTreeComponent)
+ if (!entityTreeComponent) return false
+ }
+ if (item.type === ItemTypes.Node) {
+ const entityTreeComponent = getComponent(entityNode, EntityTreeComponent)
+ return (
+ (dropOn || !!entityTreeComponent.parentEntity) &&
+ !(item.multiple
+ ? item.value.some((otherObject) => isAncestor(otherObject, entityNode))
+ : isAncestor(item.value, entityNode))
+ )
+ }
+ return true
+ }
+ }
+
+ const [{ canDropBefore, isOverBefore }, beforeDropTarget] = useDrop({
+ accept: [ItemTypes.Node, ItemTypes.File, ItemTypes.Component, ...SupportedFileTypes],
+ drop: dropItem(node, 'Before'),
+ canDrop: canDropItem(node.entity),
+ collect: (monitor) => ({
+ canDropBefore: monitor.canDrop(),
+ isOverBefore: monitor.isOver()
+ })
+ })
+
+ const [{ canDropAfter, isOverAfter }, afterDropTarget] = useDrop({
+ accept: [ItemTypes.Node, ItemTypes.File, ItemTypes.Component, ...SupportedFileTypes],
+ drop: dropItem(node, 'After'),
+ canDrop: canDropItem(node.entity),
+ collect: (monitor) => ({
+ canDropAfter: monitor.canDrop(),
+ isOverAfter: monitor.isOver()
+ })
+ })
+
+ const [{ canDropOn, isOverOn }, onDropTarget] = useDrop({
+ accept: [ItemTypes.Node, ItemTypes.File, ItemTypes.Component, ...SupportedFileTypes],
+ drop: dropItem(node, 'On'),
+ canDrop: canDropItem(node.entity, true),
+ collect: (monitor) => ({
+ canDropOn: monitor.canDrop(),
+ isOverOn: monitor.isOver()
+ })
+ })
+
+ useEffect(() => {
+ preview(getEmptyImage(), { captureDraggingState: true })
+ }, [preview])
+
+ const icons = entityExists(node.entity)
+ ? getAllComponents(node.entity)
+ .map((c) => getState(ComponentEditorsState)[c.name]?.iconComponent)
+ .filter((icon) => !!icon)
+ : []
+ const IconComponent = icons.length ? icons[icons.length - 1] : TransformPropertyGroup.iconComponent
+ const renaming = data.renamingNode && data.renamingNode.entity === node.entity
+ const marginLeft = node.depth > 0 ? node.depth * 2 + 2 : 0
+
+ return (
+
+ props.onContextMenu(event, node)}
+ >
+
+
+ {node.isLeaf ? (
+
+ ) : (
+
+ )}
+
+
+ {IconComponent &&
}
+
+ {renaming ? (
+
+
+
+ ) : (
+
+ {nodeName}
+
+ )}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default HierarchyTreeNode
diff --git a/packages/ui/src/components/editor/panels/Materials/container/index.tsx b/packages/ui/src/components/editor/panels/Materials/container/index.tsx
new file mode 100644
index 0000000000..e654e57b87
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Materials/container/index.tsx
@@ -0,0 +1,154 @@
+/*
+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 React, { useEffect } from 'react'
+import AutoSizer from 'react-virtualized-auto-sizer'
+import { FixedSizeList } from 'react-window'
+import { MeshBasicMaterial } from 'three'
+
+import { pathJoin } from '@etherealengine/common/src/utils/miscUtils'
+import { EntityUUID, getComponent, UndefinedEntity, useQuery, UUIDComponent } from '@etherealengine/ecs'
+import { ImportSettingsState } from '@etherealengine/editor/src/components/assets/ImportSettingsPanel'
+import { uploadProjectFiles } from '@etherealengine/editor/src/functions/assetFunctions'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import exportMaterialsGLTF from '@etherealengine/engine/src/assets/functions/exportMaterialsGLTF'
+import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent'
+import {
+ createMaterialEntity,
+ getMaterialsFromSource
+} from '@etherealengine/engine/src/scene/materials/functions/materialSourcingFunctions'
+import { MaterialSelectionState } from '@etherealengine/engine/src/scene/materials/MaterialLibraryState'
+import { getMutableState, getState, useHookstate, useState } from '@etherealengine/hyperflux'
+import { MaterialComponent, MaterialComponents } from '@etherealengine/spatial/src/renderer/materials/MaterialComponent'
+import { useTranslation } from 'react-i18next'
+import Button from '../../../../../primitives/tailwind/Button'
+import InputGroup from '../../../input/Group'
+import StringInput from '../../../input/String'
+import { MaterialPreviewPanel } from '../../preview/material'
+import MaterialLibraryEntry, { MaterialLibraryEntryType } from '../node'
+
+export default function MaterialLibraryPanel() {
+ const { t } = useTranslation()
+ const srcPath = useState('/mat/material-test')
+ const materialPreviewPanelRef = React.useRef()
+
+ const materialQuery = useQuery([MaterialComponent[MaterialComponents.State]])
+ const nodes = useHookstate([] as MaterialLibraryEntryType[])
+ const selected = useHookstate(getMutableState(SelectionState).selectedEntities)
+
+ useEffect(() => {
+ const materials = selected.value.length
+ ? getMaterialsFromSource(UUIDComponent.getEntityByUUID(selected.value[0]))
+ : materialQuery.map((entity) => getComponent(entity, UUIDComponent))
+ const result = materials.flatMap((uuid): MaterialLibraryEntryType[] => {
+ const source = getComponent(UUIDComponent.getEntityByUUID(uuid as EntityUUID), SourceComponent)
+ return [
+ {
+ uuid: uuid,
+ path: source
+ }
+ ]
+ })
+ nodes.set(result)
+ }, [materialQuery.length, selected])
+
+ const onClick = (e: MouseEvent, node: MaterialLibraryEntryType) => {
+ getMutableState(MaterialSelectionState).selectedMaterial.set(node.uuid)
+ }
+
+ const MaterialList = ({ height, width }) => (
+ index}
+ innerElementType="ul"
+ >
+ {MaterialLibraryEntry}
+
+ )
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Materials/index.stories.tsx b/packages/ui/src/components/editor/panels/Materials/index.stories.tsx
new file mode 100644
index 0000000000..a60b28d096
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Materials/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Materials',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'MaterialsPanelTitle',
+ jest: 'MaterialsPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Materials/index.tsx b/packages/ui/src/components/editor/panels/Materials/index.tsx
new file mode 100644
index 0000000000..2c3d78c857
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Materials/index.tsx
@@ -0,0 +1,54 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import MaterialLibraryPanel from './container'
+
+export const MaterialsPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+ {'Materials'}
+
+
+
+ )
+}
+
+export default MaterialsPanelTitle
+
+export const MaterialsPanelTab: TabData = {
+ id: 'materialsPanel',
+ closable: true,
+ title: ,
+ content:
+}
diff --git a/packages/ui/src/components/editor/panels/Materials/node/index.tsx b/packages/ui/src/components/editor/panels/Materials/node/index.tsx
new file mode 100644
index 0000000000..cca3fa889f
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Materials/node/index.tsx
@@ -0,0 +1,174 @@
+/*
+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 React, { MouseEvent, StyleHTMLAttributes, useCallback } from 'react'
+import { useDrag } from 'react-dnd'
+
+import { EntityUUID, getOptionalComponent, UUIDComponent } from '@etherealengine/ecs'
+import { MaterialSelectionState } from '@etherealengine/engine/src/scene/materials/MaterialLibraryState'
+import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
+import { MaterialComponent, MaterialComponents } from '@etherealengine/spatial/src/renderer/materials/MaterialComponent'
+
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { SiRoundcube } from 'react-icons/si'
+import { twMerge } from 'tailwind-merge'
+
+export type MaterialLibraryEntryType = {
+ uuid: EntityUUID
+ path: string
+ selected?: boolean
+}
+
+export type MaterialLibraryEntryData = {
+ nodes: MaterialLibraryEntryType[]
+ onClick: (e: MouseEvent, node: MaterialLibraryEntryType) => void
+ onCollapse: (e: MouseEvent, node: MaterialLibraryEntryType) => void
+}
+
+export type MaterialLibraryEntryProps = {
+ index: number
+ data: MaterialLibraryEntryData
+ style: StyleHTMLAttributes
+}
+
+const nodeDisplayName = (node: MaterialLibraryEntryType) => {
+ return (
+ getOptionalComponent(
+ UUIDComponent.getEntityByUUID(node.uuid as EntityUUID),
+ MaterialComponent[MaterialComponents.State]
+ )?.material?.name ?? ''
+ )
+}
+
+export default function MaterialLibraryEntry(props: MaterialLibraryEntryProps) {
+ const data = props.data
+ const node = data.nodes[props.index]
+
+ const selectionState = useMutableState(SelectionState)
+
+ const onClickNode = (e) => {
+ data.onClick(e, node)
+ }
+
+ const onCollapseNode = useCallback(
+ (e: MouseEvent) => {
+ e.stopPropagation()
+ data.onCollapse(e, node)
+ },
+ [node, data.onCollapse]
+ )
+
+ const [_dragProps, drag] = useDrag({
+ type: ItemTypes.Material,
+ item() {
+ const selectedEntities = selectionState.selectedEntities.value
+ const multiple = selectedEntities.length > 1
+ return {
+ type: ItemTypes.Material,
+ multiple,
+ value: node.uuid
+ }
+ },
+ collect: (monitor) => ({
+ isDragging: !!monitor.isDragging()
+ })
+ })
+
+ const materialSelection = useHookstate(getMutableState(MaterialSelectionState).selectedMaterial)
+ return (
+
+
+
+ {/* node.isLeaf ? (
+
+ ) : (
+
+ )*/}
+
+
+
+
+
+ {nodeDisplayName(node)}
+
+
+ {/*
*/}
+
+
+
+
+ {/*
+
+
+
+
+
+
+
+
+ {nodeDisplayName(node)}
+
+
+
*/}
+
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Properties/container/index.stories.tsx b/packages/ui/src/components/editor/panels/Properties/container/index.stories.tsx
new file mode 100644
index 0000000000..691b38b294
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/container/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Properties/Container',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PropertiesPanelContainer',
+ jest: 'PropertiesPanelContainer.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Properties/container/index.tsx b/packages/ui/src/components/editor/panels/Properties/container/index.tsx
new file mode 100644
index 0000000000..1fdec5cc4f
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/container/index.tsx
@@ -0,0 +1,154 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { UUIDComponent } from '@etherealengine/ecs'
+import { Component, ComponentJSONIDMap, useOptionalComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { NO_PROXY, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
+
+import { EntityUUID } from '@etherealengine/ecs'
+import { ComponentEditorsState } from '@etherealengine/editor/src/services/ComponentEditors'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { GLTFNodeState } from '@etherealengine/engine/src/gltf/GLTFDocumentState'
+import { MaterialSelectionState } from '@etherealengine/engine/src/scene/materials/MaterialLibraryState'
+import { PopoverPosition } from '@mui/material'
+import { HiOutlinePlusCircle } from 'react-icons/hi'
+import { PropertiesPanelTab } from '..'
+import Button from '../../../../../primitives/tailwind/Button'
+import Popover from '../../../layout/Popover'
+import TransformPropertyGroup from '../../../properties/transform'
+import { PopoverContext } from '../../../util/PopoverContext'
+import ElementList from '../elementList'
+import MaterialEditor from '../material'
+
+const EntityComponentEditor = (props: { entity; component; multiEdit }) => {
+ const { entity, component, multiEdit } = props
+ const componentMounted = useOptionalComponent(entity, component)
+ const Editor = getState(ComponentEditorsState)[component.name]!
+ if (!componentMounted) return null
+ // nodeEntity is used as key here to signal to React when the entity has changed,
+ // and to prevent state from being recycled between editor instances, which
+ // can cause hookstate to throw errors.
+ return
+}
+
+const EntityEditor = (props: { entityUUID: EntityUUID; multiEdit: boolean }) => {
+ const { t } = useTranslation()
+ const { entityUUID, multiEdit } = props
+ const anchorEl = useHookstate(null)
+ const [anchorPosition, setAnchorPosition] = React.useState(undefined)
+
+ const entity = UUIDComponent.getEntityByUUID(entityUUID)
+ const componentEditors = useHookstate(getMutableState(ComponentEditorsState)).get(NO_PROXY)
+ const node = useHookstate(GLTFNodeState.getMutableNode(entity))
+ const components: Component[] = []
+ for (const jsonID of Object.keys(node.extensions.value!)) {
+ const component = ComponentJSONIDMap.get(jsonID)!
+ if (!componentEditors[component.name]) continue
+ components.push(component)
+ }
+
+ const open = !!anchorEl.value
+ const panel = document.getElementById('propertiesPanel')
+
+ return (
+ {
+ anchorEl.set(null)
+ }
+ }}
+ >
+
+ }
+ variant="transparent"
+ rounded="none"
+ className="ml-auto w-40 bg-theme-highlight px-2"
+ size="small"
+ onClick={(event) => {
+ setAnchorPosition({ top: event.clientY - 10, left: panel?.getBoundingClientRect().left! + 10 })
+ anchorEl.set(event.currentTarget)
+ }}
+ >
+ {t('editor:properties.lbl-addComponent')}
+
+
+ {
+ anchorEl.set(null)
+ setAnchorPosition(undefined)
+ }}
+ panelId={PropertiesPanelTab.id!}
+ anchorPosition={anchorPosition}
+ className="h-[60%] w-full min-w-[300px] overflow-y-auto"
+ >
+ {}
+
+
+ {components.map((c, i) => (
+
+ ))}
+
+ )
+}
+
+/**
+ * PropertiesPanelContainer used to render editor view to customize property of selected element.
+ */
+export const PropertiesPanelContainer = () => {
+ const selectedEntities = useHookstate(getMutableState(SelectionState).selectedEntities).value
+ const lockedNode = useHookstate(getMutableState(EditorState).lockPropertiesPanel)
+ const multiEdit = selectedEntities.length > 1
+ const uuid = lockedNode.value ? lockedNode.value : selectedEntities[selectedEntities.length - 1]
+ const { t } = useTranslation()
+ const materialUUID = useHookstate(getMutableState(MaterialSelectionState).selectedMaterial).value
+
+ return (
+
+ {materialUUID ? (
+
+ ) : uuid ? (
+
+ ) : (
+
+ {t('editor:properties.noNodeSelected')}
+
+ )}
+
+ )
+}
+
+export default PropertiesPanelContainer
diff --git a/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx
new file mode 100644
index 0000000000..9cbb57c3c7
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx
@@ -0,0 +1,188 @@
+/*
+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 { startCase } from 'lodash'
+import React, { useRef } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { Component } from '@etherealengine/ecs/src/ComponentFunctions'
+import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
+
+import PlaceHolderIcon from '@mui/icons-material/GroupAddOutlined'
+
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { ComponentEditorsState } from '@etherealengine/editor/src/services/ComponentEditors'
+import { ComponentShelfCategoriesState } from '@etherealengine/editor/src/services/ComponentShelfCategoriesState'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io'
+import StringInput from '../../../input/String'
+import { usePopoverContextClose } from '../../../util/PopoverContext'
+
+export type SceneElementType = {
+ componentJsonID: string
+ label: string
+ Icon: any
+ type: typeof ItemTypes.Component
+}
+
+const ComponentListItem = ({ item }: { item: Component }) => {
+ const { t } = useTranslation()
+ useHookstate(getMutableState(ComponentEditorsState).keys).value // ensure reactively updates new components
+ const Icon = getState(ComponentEditorsState)[item.name]?.iconComponent ?? PlaceHolderIcon
+ const handleClosePopover = usePopoverContextClose()
+
+ return (
+
+ )
+}
+
+const SceneElementListItem = ({
+ categoryTitle,
+ categoryItems,
+ isCollapsed
+}: {
+ categoryTitle: string
+ categoryItems: Component[]
+ isCollapsed: boolean
+}) => {
+ const open = useHookstate(categoryTitle === 'Misc')
+ return (
+ <>
+
+
+
+ {categoryItems.map((item) => (
+
+ ))}
+
+
+ >
+ )
+}
+
+const useComponentShelfCategories = (search: string) => {
+ useHookstate(getMutableState(ComponentShelfCategoriesState)).value
+
+ if (!search) {
+ return Object.entries(getState(ComponentShelfCategoriesState))
+ }
+
+ const searchRegExp = new RegExp(search, 'gi')
+
+ return Object.entries(getState(ComponentShelfCategoriesState))
+ .map(([category, items]) => {
+ const filteredItems = items.filter((item) => item.name.match(searchRegExp)?.length)
+ return [category, filteredItems] as [string, Component[]]
+ })
+ .filter(([_, items]) => !!items.length)
+}
+
+export function ElementList() {
+ const { t } = useTranslation()
+ const search = useHookstate({ local: '', query: '' })
+ const searchTimeout = useRef | null>(null)
+
+ const shelves = useComponentShelfCategories(search.query.value)
+
+ const onSearch = (text: string) => {
+ search.local.set(text)
+ if (searchTimeout.current) clearTimeout(searchTimeout.current)
+ searchTimeout.current = setTimeout(() => {
+ search.query.set(text)
+ }, 50)
+ }
+
+ return (
+ <>
+
+
+
{t('editor:layout.assetGrid.components')}
+ onSearch(val)}
+ />
+
+
+ {shelves.map(([category, items]) => (
+
+ ))}
+ >
+ )
+ {
+ /*
+
+ {t('editor:layout.assetGrid.components')}
+
+ onSearch(e.target.value)}
+ inputRef={inputReference}
+ />
+
+ }
+ >
+ {shelves.map(([category, items]) => (
+
+ ))}
+ */
+ }
+}
+
+export default ElementList
diff --git a/packages/ui/src/components/editor/panels/Properties/index.stories.tsx b/packages/ui/src/components/editor/panels/Properties/index.stories.tsx
new file mode 100644
index 0000000000..37bf2160e2
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Properties',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PropertiesPanelTitle',
+ jest: 'PropertiesPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Properties/index.tsx b/packages/ui/src/components/editor/panels/Properties/index.tsx
new file mode 100644
index 0000000000..f669c9af61
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/index.tsx
@@ -0,0 +1,58 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import { InfoTooltip } from '../../layout/Tooltip'
+//import styles from '../styles.module.scss'
+import PropertiesPanelContainer from './container'
+
+export const PropertiesPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+ {t('editor:properties.title')}
+
+
+
+
+ )
+}
+
+export const PropertiesPanelTab: TabData = {
+ id: 'propertiesPanel',
+ closable: true,
+ cached: true,
+ title: ,
+ content:
+}
+
+export default PropertiesPanelTitle
diff --git a/packages/ui/src/components/editor/panels/Properties/material/index.tsx b/packages/ui/src/components/editor/panels/Properties/material/index.tsx
new file mode 100644
index 0000000000..dcdaa5ba9e
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Properties/material/index.tsx
@@ -0,0 +1,321 @@
+/*
+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 MaterialLibraryIcon from '@mui/icons-material/Yard'
+import React, { useCallback, useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { Texture, Uniform } from 'three'
+
+import {
+ EntityUUID,
+ getComponent,
+ setComponent,
+ UndefinedEntity,
+ useComponent,
+ useOptionalComponent,
+ UUIDComponent
+} from '@etherealengine/ecs'
+import styles from '@etherealengine/editor/src/components/layout/styles.module.scss'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { getTextureAsync } from '@etherealengine/engine/src/assets/functions/resourceLoaderHooks'
+import { TransparencyDitheringPlugin } from '@etherealengine/engine/src/avatar/components/TransparencyDitheringComponent'
+import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent'
+import { setMaterialName } from '@etherealengine/engine/src/scene/materials/functions/materialSourcingFunctions'
+import { NO_PROXY, none, State, useHookstate } from '@etherealengine/hyperflux'
+import createReadableTexture from '@etherealengine/spatial/src/renderer/functions/createReadableTexture'
+import { getDefaultType } from '@etherealengine/spatial/src/renderer/materials/constants/DefaultArgs'
+import {
+ MaterialComponent,
+ MaterialComponents,
+ pluginByName,
+ prototypeByName
+} from '@etherealengine/spatial/src/renderer/materials/MaterialComponent'
+import { formatMaterialArgs } from '@etherealengine/spatial/src/renderer/materials/materialFunctions'
+import Button from '../../../../../primitives/tailwind/Button'
+import InputGroup from '../../../input/Group'
+import SelectInput from '../../../input/Select'
+import StringInput from '../../../input/String'
+import { PanelDragContainer, PanelIcon, PanelTitle } from '../../../layout/Panel'
+import { InfoTooltip } from '../../../layout/Tooltip'
+import ParameterInput from '../../../properties/parameter'
+
+type ThumbnailData = {
+ src: string
+ blob: string
+}
+
+const toBlobs = (thumbnails: Record): Record => {
+ const blobs = {}
+ Object.entries(thumbnails).map(([k, { blob }]) => {
+ blobs[k] = blob
+ })
+ return blobs
+}
+
+export function MaterialEditor(props: { materialUUID: EntityUUID }) {
+ const { t } = useTranslation()
+ const prototypes = Object.keys(prototypeByName).map((prototype) => ({
+ label: prototype,
+ value: prototype
+ }))
+
+ const entity = UUIDComponent.getEntityByUUID(props.materialUUID)
+ const materialComponent = useComponent(entity, MaterialComponent[MaterialComponents.State])
+ const material = materialComponent.material.value!
+ const thumbnails = useHookstate>({})
+ const textureUnloadMap = useHookstate void) | undefined>>({})
+ const selectedPlugin = useHookstate(TransparencyDitheringPlugin.id)
+
+ const createThumbnail = async (field: string, texture: Texture) => {
+ if (texture?.isTexture) {
+ try {
+ const blob: string = (await createReadableTexture(texture, {
+ maxDimensions: { width: 256, height: 256 },
+ url: true
+ })) as string
+ const thumbData: ThumbnailData = {
+ src: texture.image?.src ?? 'BLOB',
+ blob
+ }
+ thumbnails[field].set(thumbData)
+ return Promise.resolve()
+ } catch (e) {
+ console.warn('failed loading thumbnail: ' + e)
+ }
+ }
+ }
+
+ const createThumbnails = async () => {
+ const promises = Object.entries(material).map(([field, texture]: [string, Texture]) =>
+ createThumbnail(field, texture)
+ )
+ return Promise.all(promises)
+ }
+
+ const checkThumbs = async () => {
+ thumbnails.promised && (await thumbnails.promise)
+ const thumbnailVals = thumbnails.value
+ Object.entries(thumbnailVals).map(([k, { blob }]) => {
+ if (!material[k]) {
+ URL.revokeObjectURL(blob)
+ thumbnails[k].set(none)
+ }
+ })
+ await Promise.all(
+ Object.entries(material).map(async ([field, texture]: [string, Texture]) => {
+ if (texture?.isTexture) {
+ if (!thumbnails[field]?.value || thumbnails[field]?.value?.src !== texture.image?.src)
+ await createThumbnail(field, texture)
+ }
+ })
+ )
+ }
+
+ const clearThumbs = useCallback(async () => {
+ Object.values(thumbnails.value).map(({ blob }) => URL.revokeObjectURL(blob))
+ thumbnails.set({})
+ }, [materialComponent, materialComponent.prototypeEntity])
+
+ const prototypeName = useHookstate('')
+ const materialName = useHookstate('')
+ materialName.set(material.name)
+ prototypeName.set(material.type)
+
+ useEffect(() => {
+ clearThumbs().then(createThumbnails).then(checkThumbs)
+ }, [materialName, prototypeName])
+
+ const prototypeEntity = materialComponent.prototypeEntity.value!
+ const prototype = useComponent(prototypeEntity, MaterialComponent[MaterialComponents.Prototype])
+
+ const shouldLoadTexture = async (value, key: string, parametersObject: State) => {
+ let prop
+ if (parametersObject[key].type.value === 'texture') {
+ if (value) {
+ const priorUnload = textureUnloadMap.get(NO_PROXY)[key]
+ if (priorUnload) {
+ priorUnload()
+ }
+ const [texture, unload] = await getTextureAsync(value)
+ textureUnloadMap.merge({ [key]: unload })
+ prop = texture
+ } else {
+ prop = null
+ }
+ } else {
+ prop = value
+ }
+ return prop
+ }
+
+ const pluginEntity = useHookstate(UndefinedEntity)
+ const pluginState = useOptionalComponent(pluginEntity.value, MaterialComponent[MaterialComponents.Plugin])
+ /**@todo plugin UI parameter values are autogenerated - autogenerate for prototype values rather than storing in component */
+ //for each parameter type, default values
+ const pluginParameters = useHookstate({})
+ //for the current values of the parameters
+ const pluginValues = useHookstate({})
+
+ useEffect(() => {
+ pluginValues.set({})
+ pluginParameters.set({})
+ }, [materialName])
+
+ useEffect(() => {
+ if (pluginState?.pluginEntities.value?.length) return
+ const uniformParameters = pluginState?.parameters?.value
+ const pluginParameterValues = {}
+ Object.entries(
+ uniformParameters && uniformParameters[materialName.value] ? uniformParameters[materialName.value] : {}
+ ).map(([key, uniform]) => {
+ const value = (uniform as Uniform).value
+ pluginParameterValues[key] = { type: getDefaultType(value), default: value }
+ })
+
+ pluginParameters.set(formatMaterialArgs(pluginParameterValues))
+
+ if (!pluginState?.parameters.value || !pluginState.parameters[materialName.value].value) return
+ for (const key in pluginState.parameters[materialName.value].value) {
+ pluginValues[key].set(pluginState.parameters[materialName.value].value[key].value)
+ }
+ }, [materialName, selectedPlugin, pluginState?.parameters[materialName.value]])
+
+ return (
+
+
+ setMaterialName(entity, name)}
+ />
+
+
+
+
+
+
+
{getComponent(entity, SourceComponent)}
+
+
+
+
+ {
+ if (materialComponent.prototypeEntity.value) materialComponent.prototypeEntity.set(prototypeByName[protoId])
+ prototypeName.set(protoId as string)
+ }}
+ />
+
+
+
async (value) => {
+ const property = await shouldLoadTexture(value, key, prototype.prototypeArguments)
+ EditorControlFunctions.modifyMaterial(
+ [materialComponent.material.value!.uuid],
+ materialComponent.material.value!.uuid as EntityUUID,
+ [{ [key]: property }]
+ )
+ if (materialComponent.parameters.value) materialComponent.parameters[key].set(property)
+ }}
+ defaults={prototype.prototypeArguments!.value}
+ thumbnails={toBlobs(thumbnails.value)}
+ />
+
+
+
+ ({ label: key, value: key }))}
+ onChange={(value) => selectedPlugin.set(value as string)}
+ />
+
+
+ {!!materialComponent.pluginEntities.value?.length && (
+
+
async (value) => {
+ const property = await shouldLoadTexture(value, key, pluginParameters)
+ getComponent(pluginEntity.value, MaterialComponent[MaterialComponents.Plugin]).parameters![
+ materialName.value
+ ][key].value = property
+ pluginValues[key].set(property)
+ }}
+ defaults={pluginParameters.value}
+ />
+
+
+ )}
+
+ )
+}
+
+export const MaterialPropertyTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+ {t('editor:properties.mesh.materialProperties.title')}
+
+
+
+
+ )
+}
+
+export default MaterialEditor
diff --git a/packages/ui/src/components/editor/panels/Scenes/container/index.stories.tsx b/packages/ui/src/components/editor/panels/Scenes/container/index.stories.tsx
new file mode 100644
index 0000000000..679096220e
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Scenes/container/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Scene/Container',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ScenePanelContainer',
+ jest: 'ScenePanelContainer.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Scenes/container/index.tsx b/packages/ui/src/components/editor/panels/Scenes/container/index.tsx
new file mode 100644
index 0000000000..447a2be228
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Scenes/container/index.tsx
@@ -0,0 +1,160 @@
+/*
+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 { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
+import config from '@etherealengine/common/src/config'
+import { AssetType, assetPath } from '@etherealengine/common/src/schema.type.module'
+import { useClickOutside } from '@etherealengine/common/src/utils/useClickOutside'
+import { deleteScene, onNewScene } from '@etherealengine/editor/src/functions/sceneFunctions'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
+import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
+import React, { useRef } from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiDotsHorizontal } from 'react-icons/hi'
+import { HiOutlinePlusCircle } from 'react-icons/hi2'
+import Button from '../../../../../primitives/tailwind/Button'
+import LoadingView from '../../../../../primitives/tailwind/LoadingView'
+import Text from '../../../../../primitives/tailwind/Text'
+import ConfirmDialog from '../../../../tailwind/ConfirmDialog'
+import RenameSceneModal from '../modals/RenameScene'
+
+export default function ScenesPanel() {
+ const { t } = useTranslation()
+ const editorState = useMutableState(EditorState)
+ const scenesQuery = useFind(assetPath, { query: { project: editorState.projectName.value } })
+ const scenes = scenesQuery.data
+
+ const contextMenuRef = useRef(null)
+ const isContextMenuOpen = useHookstate('')
+ const scenesLoading = scenesQuery.status === 'pending'
+ const onCreateScene = async () => onNewScene()
+
+ const onClickScene = (scene: AssetType) => {
+ getMutableState(EditorState).scenePath.set(scene.assetURL)
+ }
+
+ const deleteSelectedScene = async (scene: AssetType) => {
+ if (scene) {
+ await deleteScene(scene.id)
+ if (editorState.sceneAssetID.value === scene.id) {
+ editorState.sceneName.set(null)
+ editorState.sceneAssetID.set(null)
+ }
+ }
+ PopoverState.hidePopupover()
+ }
+
+ const getSceneName = (scene: AssetType) =>
+ scene.assetURL.split('/').pop()!.replace('.gltf', '').replace('.scene.json', '')
+
+ useClickOutside(contextMenuRef, () => isContextMenuOpen.set(''))
+
+ return (
+
+
+ }
+ variant="transparent"
+ rounded="none"
+ className="ml-auto w-32 bg-theme-highlight px-2"
+ size="small"
+ onClick={onCreateScene}
+ >
+ {t('editor:newScene')}
+
+
+
+ {scenesLoading ? (
+
+ ) : (
+
+
+ {scenes.map((scene: AssetType) => (
+
+
{
+ e.currentTarget.src = 'static/etherealengine_logo.png'
+ }}
+ crossOrigin="anonymous"
+ className="block h-[100%] w-auto cursor-pointer rounded-t-lg object-cover"
+ onClick={() => onClickScene(scene)}
+ />
+
+
{getSceneName(scene)}
+
+
}
+ iconContainerClassName="mx-0"
+ onClick={() => isContextMenuOpen.set(scene.id)}
+ />
+ {isContextMenuOpen.value === scene.id ? (
+
+
+
+
+ ) : null}
+
+
+
+ ))}
+
+
+ )}
+
+
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Scenes/index.stories.tsx b/packages/ui/src/components/editor/panels/Scenes/index.stories.tsx
new file mode 100644
index 0000000000..730472a933
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Scenes/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Scene',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ScenePanelTitle',
+ jest: 'ScenePanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Scenes/index.tsx b/packages/ui/src/components/editor/panels/Scenes/index.tsx
new file mode 100644
index 0000000000..d7150a74d4
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Scenes/index.tsx
@@ -0,0 +1,55 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import ScenesPanel from './container'
+
+/**
+ * Displays the scenes that exist in the current project.
+ */
+export const ScenePanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+ {t('editor:properties.scene.name')}
+
+
+ )
+}
+
+export const ScenePanelTab: TabData = {
+ id: 'scenePanel',
+ closable: true,
+ cached: true,
+ title: ,
+ content:
+}
+
+export default ScenePanelTitle
diff --git a/packages/ui/src/components/editor/panels/Scenes/modals/RenameScene.tsx b/packages/ui/src/components/editor/panels/Scenes/modals/RenameScene.tsx
new file mode 100644
index 0000000000..0b91c98c2f
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Scenes/modals/RenameScene.tsx
@@ -0,0 +1,60 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
+
+import { AssetType } from '@etherealengine/common/src/schema.type.module'
+import { renameScene } from '@etherealengine/editor/src/functions/sceneFunctions'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import Input from '../../../../../primitives/tailwind/Input'
+import Modal from '../../../../../primitives/tailwind/Modal'
+
+export default function RenameSceneModal({ sceneName, scene }: { sceneName: string; scene: AssetType }) {
+ const { t } = useTranslation()
+ const newSceneName = useHookstate(sceneName)
+
+ const handleSubmit = async () => {
+ const currentURL = scene.assetURL
+ const newURL = currentURL.replace(currentURL.split('/').pop()!, newSceneName.value + '.gltf')
+ const newData = await renameScene(scene.id, newURL, scene.projectName)
+ getMutableState(EditorState).scenePath.set(newData.assetURL)
+ PopoverState.hidePopupover()
+ }
+
+ return (
+
+ newSceneName.set(event.target.value)} />
+
+ )
+}
diff --git a/packages/ui/src/components/editor/panels/Viewport/container/index.stories.tsx b/packages/ui/src/components/editor/panels/Viewport/container/index.stories.tsx
new file mode 100644
index 0000000000..11cee1984a
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/container/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Viewport/Container',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ViewportPanelContainer',
+ jest: 'ViewportPanelContainer.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Viewport/container/index.tsx b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx
new file mode 100644
index 0000000000..89bb80d05f
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx
@@ -0,0 +1,126 @@
+/*
+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 { Engine, getComponent } from '@etherealengine/ecs'
+import { SceneElementType } from '@etherealengine/editor/src/components/element/ElementList'
+import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { getCursorSpawnPosition } from '@etherealengine/editor/src/functions/screenSpaceFunctions'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { TransformComponent } from '@etherealengine/spatial'
+import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem'
+import React, { useEffect, useRef } from 'react'
+import { useDrop } from 'react-dnd'
+import { useTranslation } from 'react-i18next'
+import { twMerge } from 'tailwind-merge'
+import { Vector2, Vector3 } from 'three'
+import Text from '../../../../../primitives/tailwind/Text'
+import GridTool from '../tools/GridTool'
+import PlayModeTool from '../tools/PlayModeTool'
+import RenderModeTool from '../tools/RenderTool'
+import TransformPivotTool from '../tools/TransformPivotTool'
+import TransformSnapTool from '../tools/TransformSnapTool'
+import TransformSpaceTool from '../tools/TransformSpaceTool'
+
+const ViewportDnD = () => {
+ const ref = useRef(null as null | HTMLDivElement)
+
+ const [{ isDragging, isOver }, dropRef] = useDrop({
+ accept: [ItemTypes.Component],
+ collect: (monitor) => ({
+ isDragging: monitor.getItem() !== null && monitor.canDrop(),
+ isOver: monitor.isOver()
+ }),
+ drop(item: SceneElementType, monitor) {
+ const vec3 = new Vector3()
+ getCursorSpawnPosition(monitor.getClientOffset() as Vector2, vec3)
+ EditorControlFunctions.createObjectFromSceneElement([
+ { name: item!.componentJsonID },
+ { name: TransformComponent.jsonID, props: { position: vec3 } }
+ ])
+ }
+ })
+
+ useEffect(() => {
+ if (!ref?.current) return
+
+ const canvas = getComponent(Engine.instance.viewerEntity, RendererComponent).renderer.domElement
+ ref.current.appendChild(canvas)
+
+ getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true
+
+ const observer = new ResizeObserver(() => {
+ getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true
+ })
+
+ observer.observe(ref.current)
+ return () => {
+ observer.disconnect()
+ // const canvas = document.getElementById('engine-renderer-canvas')!
+ // parent.removeChild(canvas)
+ }
+ }, [ref])
+
+ return (
+ dropRef(el)) && ref}
+ className={twMerge(
+ 'h-full w-full border border-white',
+ isDragging && isOver ? 'border-4' : 'border-none',
+ isDragging ? 'pointer-events-auto' : 'pointer-events-none'
+ )}
+ >
+ )
+}
+
+const ViewPortPanelContainer = () => {
+ const { t } = useTranslation()
+ const sceneName = useHookstate(getMutableState(EditorState).sceneName).value
+ return (
+
+
+ {sceneName ? (
+
+ ) : (
+
+
+
{t('editor:selectSceneMsg')}
+
+ )}
+
+ )
+}
+
+export default ViewPortPanelContainer
diff --git a/packages/ui/src/components/editor/panels/Viewport/index.stories.tsx b/packages/ui/src/components/editor/panels/Viewport/index.stories.tsx
new file mode 100644
index 0000000000..6a504077ad
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/Viewport',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ViewportPanelTitle',
+ jest: 'ViewportPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/Viewport/index.tsx b/packages/ui/src/components/editor/panels/Viewport/index.tsx
new file mode 100644
index 0000000000..0f48e7829c
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/index.tsx
@@ -0,0 +1,50 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+import ViewPortPanelContainer from './container'
+
+export const ViewportPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t('editor:viewport.title')}
+
+ )
+}
+
+export default ViewportPanelTitle
+
+export const ViewportPanelTab: TabData = {
+ id: 'viewPanel',
+ closable: true,
+ title: ,
+ content:
+}
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/GridTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/GridTool.tsx
new file mode 100644
index 0000000000..e7195bd5ce
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/GridTool.tsx
@@ -0,0 +1,69 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { RendererState } from '@etherealengine/spatial/src/renderer/RendererState'
+import { MdBorderClear } from 'react-icons/md'
+
+import Button from '../../../../../primitives/tailwind/Button'
+import NumericInput from '../../../input/Numeric'
+
+const GridTool = () => {
+ const { t } = useTranslation()
+
+ const rendererState = useHookstate(getMutableState(RendererState))
+
+ const onToggleGridVisible = () => {
+ rendererState.gridVisibility.set(!rendererState.gridVisibility.value)
+ }
+
+ return (
+
+ }
+ onClick={onToggleGridVisible}
+ variant="transparent"
+ title={t('editor:toolbar.transformPivot.info-toggleGridVisibility')}
+ className="px-0"
+ />
+ rendererState.gridHeight.set(value)}
+ className="h-6 w-16 rounded-sm bg-transparent px-2 py-1"
+ inputClassName="text-[#A3A3A3]"
+ precision={0.01}
+ smallStep={0.5}
+ mediumStep={1}
+ largeStep={5}
+ unit="m"
+ />
+
+ )
+}
+
+export default GridTool
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/PlayModeTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/PlayModeTool.tsx
new file mode 100644
index 0000000000..75c7893b30
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/PlayModeTool.tsx
@@ -0,0 +1,115 @@
+/*
+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 { AuthState } from '@etherealengine/client-core/src/user/services/AuthService'
+import { UUIDComponent } from '@etherealengine/ecs'
+import { getComponent, getOptionalComponent, removeComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { Engine } from '@etherealengine/ecs/src/Engine'
+import { removeEntity } from '@etherealengine/ecs/src/EntityFunctions'
+import { TransformGizmoControlledComponent } from '@etherealengine/editor/src/classes/TransformGizmoControlledComponent'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { transformGizmoControlledQuery } from '@etherealengine/editor/src/systems/GizmoSystem'
+import { VisualScriptActions, visualScriptQuery } from '@etherealengine/engine'
+import { AvatarComponent } from '@etherealengine/engine/src/avatar/components/AvatarComponent'
+import { getRandomSpawnPoint } from '@etherealengine/engine/src/avatar/functions/getSpawnPoint'
+import { spawnLocalAvatarInWorld } from '@etherealengine/engine/src/avatar/functions/receiveJoinWorld'
+import { GLTFComponent } from '@etherealengine/engine/src/gltf/GLTFComponent'
+import { dispatchAction, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
+import { WorldNetworkAction } from '@etherealengine/network'
+import { EngineState } from '@etherealengine/spatial/src/EngineState'
+import { FollowCameraComponent } from '@etherealengine/spatial/src/camera/components/FollowCameraComponent'
+import { TargetCameraRotationComponent } from '@etherealengine/spatial/src/camera/components/TargetCameraRotationComponent'
+import { ComputedTransformComponent } from '@etherealengine/spatial/src/transform/components/ComputedTransformComponent'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiOutlinePause, HiOutlinePlay } from 'react-icons/hi2'
+import Button from '../../../../../primitives/tailwind/Button'
+import Tooltip from '../../../../../primitives/tailwind/Tooltip'
+
+const PlayModeTool = () => {
+ const { t } = useTranslation()
+
+ const isEditing = useHookstate(getMutableState(EngineState).isEditing)
+ const authState = useHookstate(getMutableState(AuthState))
+
+ const sceneEntity = useHookstate(getMutableState(EditorState).rootEntity)
+ const gltfComponent = getOptionalComponent(sceneEntity.value, GLTFComponent)
+
+ const onTogglePlayMode = () => {
+ const entity = AvatarComponent.getSelfAvatarEntity()
+ if (entity) {
+ dispatchAction(WorldNetworkAction.destroyEntity({ entityUUID: getComponent(entity, UUIDComponent) }))
+ removeEntity(entity)
+ removeComponent(Engine.instance.cameraEntity, ComputedTransformComponent)
+ removeComponent(Engine.instance.cameraEntity, FollowCameraComponent)
+ removeComponent(Engine.instance.cameraEntity, TargetCameraRotationComponent)
+ getMutableState(EngineState).isEditing.set(true)
+ visualScriptQuery().forEach((entity) => dispatchAction(VisualScriptActions.stop({ entity })))
+ // stop all visual script logic
+ } else {
+ const avatarDetails = authState.user.avatar.value
+
+ const avatarSpawnPose = getRandomSpawnPoint(Engine.instance.userID)
+ const currentScene = getComponent(getState(EditorState).rootEntity, UUIDComponent)
+
+ if (avatarDetails)
+ spawnLocalAvatarInWorld({
+ parentUUID: currentScene,
+ avatarSpawnPose,
+ avatarID: avatarDetails.id!,
+ name: authState.user.name.value
+ })
+
+ // todo
+ getMutableState(EngineState).isEditing.set(false)
+ // run all visual script logic
+ visualScriptQuery().forEach((entity) => dispatchAction(VisualScriptActions.execute({ entity })))
+ transformGizmoControlledQuery().forEach((entity) => removeComponent(entity, TransformGizmoControlledComponent))
+ //just remove all gizmo in the scene
+ }
+ }
+
+ return (
+
+
+ : }
+ className="p-0"
+ onClick={onTogglePlayMode}
+ />
+
+
+ )
+}
+
+export default PlayModeTool
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/RenderTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/RenderTool.tsx
new file mode 100644
index 0000000000..e005c57c22
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/RenderTool.tsx
@@ -0,0 +1,147 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { ShadowMapResolutionOptions } from '@etherealengine/client-core/src/user/components/UserMenu/menus/SettingMenu'
+import { useHookstate, useMutableState } from '@etherealengine/hyperflux'
+import { RendererState } from '@etherealengine/spatial/src/renderer/RendererState'
+import { RenderModes, RenderModesType } from '@etherealengine/spatial/src/renderer/constants/RenderModes'
+import { GiWireframeGlobe } from 'react-icons/gi'
+import { RiArrowDownSLine } from 'react-icons/ri'
+import { TbBallBowling, TbInnerShadowBottom, TbInnerShadowBottomFilled, TbShadow } from 'react-icons/tb'
+import { ViewportPanelTab } from '..'
+import Button from '../../../../../primitives/tailwind/Button'
+import Tooltip from '../../../../../primitives/tailwind/Tooltip'
+import BooleanInput from '../../../input/Boolean'
+import InputGroup from '../../../input/Group'
+import SelectInput from '../../../input/Select'
+import PopOver from '../../../layout/Popover'
+
+const renderModes: { name: RenderModesType; icon: JSX.Element }[] = [
+ {
+ name: 'Unlit',
+ icon:
+ },
+ {
+ name: 'Lit',
+ icon:
+ },
+ { name: 'Normals', icon: },
+ {
+ name: 'Wireframe',
+ icon:
+ },
+ {
+ name: 'Shadows',
+ icon:
+ }
+]
+
+const RenderModeTool = () => {
+ const { t } = useTranslation()
+ const anchorEl = useHookstate(null)
+ const anchorPosition = useHookstate({ left: 0, top: 0 })
+
+ const rendererState = useMutableState(RendererState)
+ const options = [] as { label: string; value: string }[]
+ const isVisible = useHookstate(false)
+
+ for (let key of Object.keys(RenderModes)) {
+ options.push({
+ label: RenderModes[key],
+ value: RenderModes[key]
+ })
+ }
+
+ const onChangeRenderMode = (mode: RenderModesType) => {
+ rendererState.renderMode.set(mode)
+ }
+
+ const handlePostProcessingChange = () => {
+ rendererState.usePostProcessing.set(!rendererState.usePostProcessing.value)
+ rendererState.automatic.set(false)
+ }
+
+ return (
+
+ {renderModes.map((mode) => (
+
+
+ ))}
+
+ )
+}
+
+export default RenderModeTool
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/TransformPivotTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/TransformPivotTool.tsx
new file mode 100644
index 0000000000..1358652dc8
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/TransformPivotTool.tsx
@@ -0,0 +1,88 @@
+/*
+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 React from 'react'
+
+import { TransformPivot } from '@etherealengine/engine/src/scene/constants/transformConstants'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+
+import { setTransformPivot, toggleTransformPivot } from '@etherealengine/editor/src/functions/transformFunctions'
+import { EditorHelperState } from '@etherealengine/editor/src/services/EditorHelperState'
+import { t } from 'i18next'
+import { useTranslation } from 'react-i18next'
+import { FaRegDotCircle } from 'react-icons/fa'
+import Button from '../../../../../primitives/tailwind/Button'
+import Select from '../../../../../primitives/tailwind/Select'
+
+const transformPivotOptions = [
+ {
+ label: t('editor:toolbar.transformPivot.lbl-selection'),
+ description: t('editor:toolbar.transformPivot.info-selection'),
+ value: TransformPivot.Selection
+ },
+ {
+ label: t('editor:toolbar.transformPivot.lbl-center'),
+ description: t('editor:toolbar.transformPivot.info-center'),
+ value: TransformPivot.Center
+ },
+ {
+ label: t('editor:toolbar.transformPivot.lbl-bottom'),
+ description: t('editor:toolbar.transformPivot.info-bottom'),
+ value: TransformPivot.Bottom
+ },
+ {
+ label: t('editor:toolbar.transformPivot.lbl-origin'),
+ description: t('editor:toolbar.transformPivot.info-origin'),
+ value: TransformPivot.Origin
+ }
+]
+
+const TransformPivotTool = () => {
+ const { t } = useTranslation()
+
+ const editorHelperState = useHookstate(getMutableState(EditorHelperState))
+
+ return (
+
+ }
+ onClick={toggleTransformPivot}
+ variant="transparent"
+ title={t('editor:toolbar.transformPivot.toggleTransformPivot')}
+ className="px-0"
+ />
+
+
+ )
+}
+
+export default TransformPivotTool
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/TransformSnapTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/TransformSnapTool.tsx
new file mode 100644
index 0000000000..3600575fa1
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/TransformSnapTool.tsx
@@ -0,0 +1,121 @@
+/*
+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 React from 'react'
+
+import { SnapMode } from '@etherealengine/engine/src/scene/constants/transformConstants'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+
+import { EditorHelperState } from '@etherealengine/editor/src/services/EditorHelperState'
+import { ObjectGridSnapState } from '@etherealengine/editor/src/systems/ObjectGridSnapSystem'
+import { useTranslation } from 'react-i18next'
+import { MdOutlineCenterFocusWeak } from 'react-icons/md'
+import Button from '../../../../../primitives/tailwind/Button'
+import Select from '../../../../../primitives/tailwind/Select'
+
+const translationSnapOptions = [
+ { label: '0.1m', value: 0.1 },
+ { label: '0.125m', value: 0.125 },
+ { label: '0.25m', value: 0.25 },
+ { label: '0.5m', value: 0.5 },
+ { label: '1m', value: 1 },
+ { label: '2m', value: 2 },
+ { label: '4m', value: 4 }
+]
+
+const rotationSnapOptions = [
+ { label: '1°', value: 1 },
+ { label: '5°', value: 5 },
+ { label: '10°', value: 10 },
+ { label: '15°', value: 15 },
+ { label: '30°', value: 30 },
+ { label: '45°', value: 45 },
+ { label: '90°', value: 90 }
+]
+
+const TransformSnapTool = () => {
+ const { t } = useTranslation()
+
+ const editorHelperState = useHookstate(getMutableState(EditorHelperState))
+ const objectSnapState = useHookstate(getMutableState(ObjectGridSnapState))
+ const onChangeTranslationSnap = (snapValue: number) => {
+ getMutableState(EditorHelperState).translationSnap.set(snapValue)
+ if (editorHelperState.gridSnap.value !== SnapMode.Grid) {
+ getMutableState(EditorHelperState).gridSnap.set(SnapMode.Grid)
+ }
+ }
+
+ const onChangeRotationSnap = (snapValue: number) => {
+ getMutableState(EditorHelperState).rotationSnap.set(snapValue)
+ if (editorHelperState.gridSnap.value !== SnapMode.Grid) {
+ getMutableState(EditorHelperState).gridSnap.set(SnapMode.Grid)
+ }
+ }
+
+ const toggleAttachmentPointSnap = () => {
+ objectSnapState.enabled.set(!objectSnapState.enabled.value)
+ }
+
+ // const onChangeScaleSnap = (snapValue: number) => {
+ // getMutableState(EditorHelperState).scaleSnap.set(snapValue)
+ // if (editorHelperState.snapMode.value !== SnapMode.Grid) {
+ // getMutableState(EditorHelperState).snapMode.set(SnapMode.Grid)
+ // }
+ // }
+
+ return (
+
+ }
+ onClick={toggleAttachmentPointSnap}
+ variant="transparent"
+ title={t('editor:toolbar.transformSnapTool.toggleSnapMode')}
+ className="px-0"
+ />
+ {/* */}
+
+ {/*
+ */}
+
+ {/* */}
+
+ )
+}
+
+export default TransformSnapTool
diff --git a/packages/ui/src/components/editor/panels/Viewport/tools/TransformSpaceTool.tsx b/packages/ui/src/components/editor/panels/Viewport/tools/TransformSpaceTool.tsx
new file mode 100644
index 0000000000..8db47683b0
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/Viewport/tools/TransformSpaceTool.tsx
@@ -0,0 +1,80 @@
+/*
+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 React from 'react'
+
+import { TransformSpace } from '@etherealengine/engine/src/scene/constants/transformConstants'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+
+import { setTransformSpace, toggleTransformSpace } from '@etherealengine/editor/src/functions/transformFunctions'
+import { EditorHelperState } from '@etherealengine/editor/src/services/EditorHelperState'
+import { t } from 'i18next'
+import { useTranslation } from 'react-i18next'
+import { PiGlobeSimple } from 'react-icons/pi'
+import Button from '../../../../../primitives/tailwind/Button'
+import Select from '../../../../../primitives/tailwind/Select'
+
+const transformSpaceOptions = [
+ {
+ label: t('editor:toolbar.transformSpace.lbl-selection'),
+ description: t('editor:toolbar.transformSpace.info-selection'),
+ value: TransformSpace.local
+ },
+ {
+ label: t('editor:toolbar.transformSpace.lbl-world'),
+ description: t('editor:toolbar.transformSpace.info-world'),
+ value: TransformSpace.world
+ }
+]
+
+const TransformSpaceTool = () => {
+ const { t } = useTranslation()
+
+ const transformSpace = useHookstate(getMutableState(EditorHelperState).transformSpace)
+
+ return (
+
+ }
+ onClick={toggleTransformSpace}
+ variant="transparent"
+ title={t('editor:toolbar.transformSpace.lbl-toggleTransformSpace')}
+ className="px-0"
+ />
+ {/* */}
+
+ {/* */}
+
+ )
+}
+
+export default TransformSpaceTool
diff --git a/packages/ui/src/components/editor/panels/VisualScript/index.stories.tsx b/packages/ui/src/components/editor/panels/VisualScript/index.stories.tsx
new file mode 100644
index 0000000000..55507d790a
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/VisualScript/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Panel/VisualScript',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'VisualScriptPanelTitle',
+ jest: 'VisualScriptPanelTitle.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/panels/VisualScript/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/index.tsx
new file mode 100644
index 0000000000..13b59064fa
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/VisualScript/index.tsx
@@ -0,0 +1,53 @@
+/*
+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 { TabData } from 'rc-dock'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PanelDragContainer, PanelTitle } from '../../layout/Panel'
+
+export const VisualScriptPanelTitle = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+ {'VisualScript'}
+
+
+
+ )
+}
+
+export default VisualScriptPanelTitle
+
+export const VisualScriptPanelTab: TabData = {
+ id: 'visualScriptPanel',
+ closable: true,
+ title: ,
+ content: <>>
+}
diff --git a/packages/ui/src/components/editor/panels/preview/material/index.tsx b/packages/ui/src/components/editor/panels/preview/material/index.tsx
new file mode 100644
index 0000000000..ff208a28d4
--- /dev/null
+++ b/packages/ui/src/components/editor/panels/preview/material/index.tsx
@@ -0,0 +1,93 @@
+/*
+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 React, { useEffect, useRef } from 'react'
+import { Mesh, SphereGeometry } from 'three'
+
+import { useRender3DPanelSystem } from '@etherealengine/client-core/src/user/components/Panel3D/useRender3DPanelSystem'
+import { generateEntityUUID, getComponent, getMutableComponent, setComponent, UUIDComponent } from '@etherealengine/ecs'
+import { EnvmapComponent } from '@etherealengine/engine/src/scene/components/EnvmapComponent'
+import { MaterialSelectionState } from '@etherealengine/engine/src/scene/materials/MaterialLibraryState'
+import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'
+import { CameraOrbitComponent } from '@etherealengine/spatial/src/camera/components/CameraOrbitComponent'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import { addObjectToGroup } from '@etherealengine/spatial/src/renderer/components/GroupComponent'
+import { VisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent'
+import { getMaterial } from '@etherealengine/spatial/src/renderer/materials/materialFunctions'
+import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem'
+import { MaterialsPanelTab } from '../../Materials'
+
+export const MaterialPreviewCanvas = () => {
+ const panelRef = useRef() as React.MutableRefObject
+ const renderPanel = useRender3DPanelSystem(panelRef)
+ const selectedMaterial = useHookstate(getMutableState(MaterialSelectionState).selectedMaterial)
+ const panel = document.getElementById(MaterialsPanelTab.id!)
+
+ useEffect(() => {
+ if (!selectedMaterial.value) return
+ const { sceneEntity, cameraEntity } = renderPanel
+ setComponent(sceneEntity, NameComponent, 'Material Preview Entity')
+ const uuid = generateEntityUUID()
+ setComponent(sceneEntity, UUIDComponent, uuid)
+ setComponent(sceneEntity, VisibleComponent, true)
+ const material = getMaterial(getState(MaterialSelectionState).selectedMaterial!)
+ if (!material) return
+ addObjectToGroup(sceneEntity, new Mesh(new SphereGeometry(5, 32, 32), material))
+ setComponent(sceneEntity, EnvmapComponent, { type: 'Skybox', envMapIntensity: 2 })
+ const orbitCamera = getMutableComponent(cameraEntity, CameraOrbitComponent)
+ orbitCamera.focusedEntities.set([sceneEntity])
+ orbitCamera.refocus.set(true)
+ }, [selectedMaterial])
+
+ useEffect(() => {
+ if (!panelRef?.current) return
+ if (!panel) return
+ getComponent(renderPanel.cameraEntity, RendererComponent).needsResize = true
+
+ const observer = new ResizeObserver(() => {
+ getComponent(renderPanel.cameraEntity, RendererComponent).needsResize = true
+ })
+
+ observer.observe(panel)
+
+ return () => {
+ observer.disconnect()
+ }
+ }, [panelRef])
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+export const MaterialPreviewPanel = (props) => {
+ const selectedMaterial = useHookstate(getMutableState(MaterialSelectionState).selectedMaterial)
+ if (!selectedMaterial.value) return null
+ return
+}
diff --git a/packages/ui/src/components/editor/properties/animation/index.stories.tsx b/packages/ui/src/components/editor/properties/animation/index.stories.tsx
new file mode 100644
index 0000000000..acb3cd8fb0
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/animation/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Animation',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'AnimationNodeEditor',
+ jest: 'animationNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/animation/index.tsx b/packages/ui/src/components/editor/properties/animation/index.tsx
new file mode 100644
index 0000000000..23af87bcfe
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/animation/index.tsx
@@ -0,0 +1,118 @@
+/*
+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 { getOptionalComponent, useComponent, useOptionalComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { AnimationComponent } from '@etherealengine/engine/src/avatar/components/AnimationComponent'
+import { LoopAnimationComponent } from '@etherealengine/engine/src/avatar/components/LoopAnimationComponent'
+import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent'
+import { useState } from '@etherealengine/hyperflux'
+import { getCallback } from '@etherealengine/spatial/src/common/CallbackComponent'
+import StreetviewIcon from '@mui/icons-material/Streetview'
+import { VRM } from '@pixiv/three-vrm'
+import React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { SelectOptionsType } from '../../../../primitives/tailwind/Select'
+import InputGroup from '../../input/Group'
+import ModelInput from '../../input/Model'
+import NumericInput from '../../input/Numeric'
+import ProgressBar from '../../input/Progress'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+export const LoopAnimationNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const entity = props.entity
+
+ const animationOptions = useState([] as { label: string; value: number }[])
+ const loopAnimationComponent = useComponent(entity, LoopAnimationComponent)
+
+ const modelComponent = useOptionalComponent(entity, ModelComponent)
+ const animationComponent = useOptionalComponent(entity, AnimationComponent)
+
+ const errors = getEntityErrors(props.entity, ModelComponent)
+
+ useEffect(() => {
+ const animationComponent = getOptionalComponent(entity, AnimationComponent)
+ if (!animationComponent || !animationComponent.animations.length) return
+ animationOptions.set([
+ { label: 'None', value: -1 },
+ ...animationComponent.animations.map((clip, index) => ({ label: clip.name, value: index }))
+ ])
+ }, [modelComponent?.asset, modelComponent?.convertToVRM, animationComponent?.animations])
+
+ const onChangePlayingAnimation = (index) => {
+ commitProperties(LoopAnimationComponent, {
+ activeClipIndex: index
+ })
+ getCallback(props.entity, 'xre.play')!()
+ }
+
+ return (
+
+
+
+
+
+ {modelComponent?.asset.value instanceof VRM && (
+
+
+ {errors?.LOADING_ERROR && (
+ {t('editor:properties.model.error-url')}
+ )}
+
+ )}
+
+
+
+
+ )
+}
+
+LoopAnimationNodeEditor.iconComponent = StreetviewIcon
+
+export default LoopAnimationNodeEditor
diff --git a/packages/ui/src/components/editor/properties/camera/index.stories.tsx b/packages/ui/src/components/editor/properties/camera/index.stories.tsx
new file mode 100644
index 0000000000..c6a31fe992
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/camera/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Camera',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'CameraNodeEditor',
+ jest: 'cameraNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/camera/index.tsx b/packages/ui/src/components/editor/properties/camera/index.tsx
new file mode 100644
index 0000000000..33ba3d544c
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/camera/index.tsx
@@ -0,0 +1,240 @@
+/*
+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 { t } from 'i18next'
+import React from 'react'
+
+import { getOptionalComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { CameraSettingsComponent } from '@etherealengine/engine/src/scene/components/CameraSettingsComponent'
+import { CameraMode } from '@etherealengine/spatial/src/camera/types/CameraMode'
+
+import { defineQuery } from '@etherealengine/ecs/src/QueryFunctions'
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent'
+import { MeshComponent } from '@etherealengine/spatial/src/renderer/components/MeshComponent'
+import { iterateEntityNode } from '@etherealengine/spatial/src/transform/components/EntityTree'
+import { Button } from '@mui/material'
+import { HiOutlineCamera } from 'react-icons/hi'
+import { Box3, Vector3 } from 'three'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import PropertyGroup from '../group'
+
+/** Types copied from Camera Modes of engine. */
+const cameraModeSelect = [
+ {
+ label: 'First Person',
+ value: CameraMode.FirstPerson
+ },
+ {
+ label: 'Shoulder Cam',
+ value: CameraMode.ShoulderCam
+ },
+ {
+ label: 'Third Person',
+ value: CameraMode.ThirdPerson
+ },
+ {
+ label: 'Top Down',
+ value: CameraMode.TopDown
+ },
+ {
+ label: 'Strategic',
+ value: CameraMode.Strategic
+ },
+ {
+ label: 'Dynamic',
+ value: CameraMode.Dynamic
+ }
+]
+
+/** Types copied from Camera Modes of engine. */
+const projectionTypeSelect = [
+ {
+ label: 'Orthographic',
+ value: 0
+ },
+ {
+ label: 'Perspective',
+ value: 1
+ }
+]
+
+const modelQuery = defineQuery([ModelComponent])
+const _box3 = new Box3()
+
+export const CameraPropertiesNodeEditor: EditorComponentType = (props) => {
+ const cameraSettings = useComponent(props.entity, CameraSettingsComponent)
+
+ const calculateClippingPlanes = () => {
+ const box = new Box3()
+ const modelEntities = modelQuery()
+ for (const entity of modelEntities) {
+ console.log(entity)
+ iterateEntityNode(entity, (entity) => {
+ const mesh = getOptionalComponent(entity, MeshComponent)
+ if (mesh?.geometry?.boundingBox) {
+ console.log(mesh)
+ _box3.copy(mesh.geometry.boundingBox)
+ _box3.applyMatrix4(mesh.matrixWorld)
+ box.union(_box3)
+ }
+ })
+ }
+ const boxSize = box.getSize(new Vector3()).length()
+ commitProperties(
+ CameraSettingsComponent,
+ {
+ cameraNearClip: 0.1,
+ cameraFarClip: Math.max(boxSize, 100)
+ },
+ [props.entity]
+ )
+ }
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default CameraPropertiesNodeEditor
diff --git a/packages/ui/src/components/editor/properties/collider/index.stories.tsx b/packages/ui/src/components/editor/properties/collider/index.stories.tsx
new file mode 100644
index 0000000000..4e522c21fe
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/collider/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Collider',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ColliderNodeEditor',
+ jest: 'ColliderNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/collider/index.tsx b/packages/ui/src/components/editor/properties/collider/index.tsx
new file mode 100644
index 0000000000..6eea49e37b
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/collider/index.tsx
@@ -0,0 +1,106 @@
+/*
+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 { camelCaseToSpacedString } from '@etherealengine/common/src/utils/camelCaseToSpacedString'
+import { useComponent } from '@etherealengine/ecs'
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import {
+ ColliderComponent,
+ supportedColliderShapes
+} from '@etherealengine/spatial/src/physics/components/ColliderComponent'
+import { Shapes } from '@etherealengine/spatial/src/physics/types/PhysicsTypes'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { FiMinimize2 } from 'react-icons/fi'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import Vector3Input from '../../input/Vector3'
+import NodeEditor from '../nodeEditor'
+
+const shapeTypeOptions = Object.entries(Shapes)
+ .filter(([_, value]) => supportedColliderShapes.includes(value as any))
+ .map(([label, value]) => ({
+ label: camelCaseToSpacedString(label),
+ value
+ }))
+
+export const ColliderComponentEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const colliderComponent = useComponent(props.entity, ColliderComponent)
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+ColliderComponentEditor.iconComponent = FiMinimize2
+
+export default ColliderComponentEditor
diff --git a/packages/ui/src/components/editor/properties/envMapBake/index.stories.tsx b/packages/ui/src/components/editor/properties/envMapBake/index.stories.tsx
new file mode 100644
index 0000000000..ddad51a6c5
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envMapBake/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/EnvMapBake',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'EnvMapBakeNodeEditor',
+ jest: 'envMapBakeNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/envMapBake/index.tsx b/packages/ui/src/components/editor/properties/envMapBake/index.tsx
new file mode 100644
index 0000000000..861d0a52a1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envMapBake/index.tsx
@@ -0,0 +1,210 @@
+/*
+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 React from 'react'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { EnvMapBakeComponent } from '@etherealengine/engine/src/scene/components/EnvMapBakeComponent'
+import { EnvMapBakeTypes } from '@etherealengine/engine/src/scene/types/EnvMapBakeTypes'
+
+import { commitProperty, updateProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { uploadBPCEMBakeToServer } from '@etherealengine/editor/src/functions/uploadEnvMapBake'
+import BooleanInput from '@etherealengine/ui/src/components/editor/input/Boolean'
+import { IoMapOutline } from 'react-icons/io5'
+import Button from '../../../../primitives/tailwind/Button'
+import InputGroup from '../../input/Group'
+import SelectInput from '../../input/Select'
+import Vector3Input from '../../input/Vector3'
+import NodeEditor from '../nodeEditor'
+import EnvMapBakeProperties from './properties'
+
+export const enum BakePropertyTypes {
+ 'Boolean',
+ 'BakeType',
+ 'RefreshMode',
+ 'Resolution',
+ 'Vector'
+}
+
+const DefaultEnvMapBakeSettings = [
+ {
+ label: 'Bake Settings',
+ options: [
+ {
+ label: 'Type',
+ propertyName: 'bakeType',
+ type: BakePropertyTypes.BakeType
+ },
+ {
+ label: 'Scale',
+ propertyName: 'bakeScale',
+ type: BakePropertyTypes.Vector
+ }
+ ]
+ },
+ {
+ label: 'Realtime Settings',
+ options: [
+ {
+ label: 'Refresh Mode',
+ propertyName: 'refreshMode',
+ type: BakePropertyTypes.RefreshMode
+ }
+ ]
+ },
+
+ {
+ label: 'Settings',
+ options: [
+ {
+ label: 'Box Projection',
+ propertyName: 'boxProjection',
+ type: BakePropertyTypes.Boolean
+ }
+ ]
+ },
+ {
+ label: 'Capture Settings',
+ options: [
+ {
+ label: 'Resolution',
+ propertyName: 'resolution',
+ type: BakePropertyTypes.Resolution
+ }
+ ]
+ }
+]
+
+const bakeResolutionTypes = [256, 512, 1024, 2048]
+
+const titleLabelStyle = {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'left',
+ fontWeight: 'bold',
+ color: 'var(--textColor)',
+ padding: '0 8px 8px',
+ ':last-child': {
+ marginLeft: 'auto'
+ }
+}
+
+const envMapBakeNodeEditorStyle = {}
+
+export const EnvMapBakeNodeEditor = (props) => {
+ const bakeComponent = useComponent(props.entity, EnvMapBakeComponent)
+ const renderEnvMapBakeProperties = () => {
+ const renderedProperty = DefaultEnvMapBakeSettings.map((element, id) => {
+ if (element.label == 'Realtime Settings' && bakeComponent.bakeType.value == EnvMapBakeTypes.Realtime) {
+ return
+ }
+
+ const renderProp = element.label
+ ? [
+
+ {element.label}
+
+ ]
+ : []
+
+ element.options?.forEach((property, propertyid) => {
+ renderProp.push(
+
+ )
+ })
+
+ renderProp.push(
)
+ return renderProp
+ })
+
+ return renderedProperty
+ }
+
+ const onChangePosition = (value) => {
+ bakeComponent.bakePositionOffset.value.copy(value)
+ }
+ const onChangeScale = (value) => {
+ bakeComponent.bakeScale.value.copy(value)
+ }
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+ ({ label: resolution.toString(), value: resolution }))}
+ key={props.entity}
+ value={bakeComponent.resolution.value}
+ onChange={commitProperty(EnvMapBakeComponent, 'resolution')}
+ />
+
+
+
+
+
+ )
+}
+
+EnvMapBakeNodeEditor.iconComponent = IoMapOutline
+export default EnvMapBakeNodeEditor
diff --git a/packages/ui/src/components/editor/properties/envMapBake/properties/index.stories.tsx b/packages/ui/src/components/editor/properties/envMapBake/properties/index.stories.tsx
new file mode 100644
index 0000000000..81629dc4e8
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envMapBake/properties/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/EnvMapBake/Properties',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'EnvMapBakePropertiesNodeEditor',
+ jest: 'envMapBakePropertiesNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/envMapBake/properties/index.tsx b/packages/ui/src/components/editor/properties/envMapBake/properties/index.tsx
new file mode 100644
index 0000000000..4165d9b7ab
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envMapBake/properties/index.tsx
@@ -0,0 +1,163 @@
+/*
+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 React from 'react'
+
+import { ComponentType } from '@etherealengine/ecs/src/ComponentFunctions'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import { EnvMapBakeComponent } from '@etherealengine/engine/src/scene/components/EnvMapBakeComponent'
+import { EnvMapBakeRefreshTypes } from '@etherealengine/engine/src/scene/types/EnvMapBakeRefreshTypes'
+import { EnvMapBakeTypes } from '@etherealengine/engine/src/scene/types/EnvMapBakeTypes'
+
+import { commitProperty, updateProperty } from '@etherealengine/editor/src/components/properties/Util'
+import BooleanInput from '@etherealengine/ui/src/components/editor/input/Boolean'
+import { BakePropertyTypes } from '..'
+import InputGroup from '../../../input/Group'
+import SelectInput from '../../../input/Select'
+import Vector3Input from '../../../input/Vector3'
+
+type EnvMapBakePropertyEditorProps = {
+ bakeComponent: ComponentType
+ element: any
+ entity: Entity
+}
+
+const envMapBakeSelectTypes = [
+ {
+ label: 'Runtime',
+ value: EnvMapBakeTypes.Realtime
+ },
+ {
+ label: 'Baked',
+ value: EnvMapBakeTypes.Baked
+ }
+]
+
+const envMapBakeRefreshSelectTypes = [
+ {
+ label: 'On Awake',
+ value: EnvMapBakeRefreshTypes.OnAwake
+ }
+ // {
+ // label:"Every Frame",
+ // value: EnvMapBakeRefreshTypes.EveryFrame,
+ // }
+]
+
+const bakeResolutionTypes = [
+ {
+ label: '128',
+ value: 128
+ },
+ {
+ label: '256',
+ value: 256
+ },
+ {
+ label: '512',
+ value: 512
+ },
+ {
+ label: '1024',
+ value: 1024
+ },
+ {
+ label: '2048',
+ value: 2048
+ }
+]
+
+export const EnvMapBakeProperties = (props: EnvMapBakePropertyEditorProps) => {
+ const getPropertyValue = (option) => props.bakeComponent[option]
+
+ let renderVal = <>>
+ const label = props.element.label
+ const propertyName = props.element.propertyName
+
+ switch (props.element.type) {
+ case BakePropertyTypes.Boolean:
+ renderVal = (
+
+ )
+ break
+ case BakePropertyTypes.BakeType:
+ renderVal = (
+
+ )
+ break
+
+ case BakePropertyTypes.RefreshMode:
+ renderVal = (
+
+ )
+ break
+
+ case BakePropertyTypes.Resolution:
+ renderVal = (
+
+ )
+ break
+
+ case BakePropertyTypes.Vector:
+ renderVal = (
+
+ )
+ break
+
+ default:
+ renderVal = Undefined value Type
+ break
+ }
+
+ return (
+
+ {renderVal}
+
+ )
+}
+
+export default EnvMapBakeProperties
diff --git a/packages/ui/src/components/editor/properties/envmap/index.stories.tsx b/packages/ui/src/components/editor/properties/envmap/index.stories.tsx
new file mode 100644
index 0000000000..269c9d52e7
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envmap/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Envmap',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'EnvmapNodeEditor',
+ jest: 'envmapNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/envmap/index.tsx b/packages/ui/src/components/editor/properties/envmap/index.tsx
new file mode 100644
index 0000000000..f83d628f41
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/envmap/index.tsx
@@ -0,0 +1,163 @@
+/*
+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 React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { UUIDComponent } from '@etherealengine/ecs'
+import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { EnvMapBakeComponent } from '@etherealengine/engine/src/scene/components/EnvMapBakeComponent'
+import { EnvmapComponent } from '@etherealengine/engine/src/scene/components/EnvmapComponent'
+import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { EnvMapSourceType, EnvMapTextureType } from '@etherealengine/engine/src/scene/constants/EnvMapEnum'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+
+import { useQuery } from '@etherealengine/ecs/src/QueryFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperties,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { IoMapOutline } from 'react-icons/io5'
+import ColorInput from '../../../../primitives/tailwind/Color'
+import Slider from '../../../../primitives/tailwind/Slider'
+import FolderInput from '../../input/Folder'
+import InputGroup from '../../input/Group'
+import ImagePreviewInput from '../../input/Image/Preview'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * EnvMapSourceOptions array containing SourceOptions for Envmap
+ */
+const EnvMapSourceOptions = Object.values(EnvMapSourceType).map((value) => ({ label: value, value }))
+
+/**
+ * EnvMapSourceOptions array containing SourceOptions for Envmap
+ */
+const EnvMapTextureOptions = Object.values(EnvMapTextureType).map((value) => ({ label: value, value }))
+
+/**
+ * EnvMapEditor provides the editor view for environment map property customization.
+ */
+export const EnvMapEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const entity = props.entity
+
+ const bakeEntities = useQuery([EnvMapBakeComponent]).map((entity) => {
+ return {
+ label: getComponent(entity, NameComponent),
+ value: getComponent(entity, UUIDComponent)
+ }
+ })
+
+ const onChangeCubemapURLSource = useCallback((value) => {
+ const directory = value[value.length - 1] === '/' ? value.substring(0, value.length - 1) : value
+ if (directory !== directory /*envmapComponent.envMapSourceURL*/) {
+ updateProperties(EnvmapComponent, { envMapSourceURL: directory })
+ }
+ }, [])
+
+ const envmapComponent = useComponent(entity, EnvmapComponent)
+
+ const errors = getEntityErrors(props.entity, EnvmapComponent)
+
+ return (
+ }
+ >
+
+
+
+ {envmapComponent.type.value === EnvMapSourceType.Color && (
+
+
+
+ )}
+ {envmapComponent.type.value === EnvMapSourceType.Bake && (
+
+
+
+ )}
+ {envmapComponent.type.value === EnvMapSourceType.Texture && (
+
+
+
+
+
+ {envmapComponent.envMapTextureType.value === EnvMapTextureType.Cubemap && (
+
+ )}
+ {envmapComponent.envMapTextureType.value === EnvMapTextureType.Equirectangular && (
+
+ )}
+ {errors?.MISSING_FILE && (
+ {t('editor:properties.scene.error-url')}
+ )}
+
+
+ )}
+
+ {envmapComponent.type.value !== EnvMapSourceType.None && (
+
+
+
+ )}
+
+ )
+}
+EnvMapEditor.iconComponent = IoMapOutline
+export default EnvMapEditor
diff --git a/packages/ui/src/components/editor/properties/gallery/index.stories.tsx b/packages/ui/src/components/editor/properties/gallery/index.stories.tsx
new file mode 100644
index 0000000000..b0ca058a7f
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/gallery/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Gallery',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'GalleryNodeEditor',
+ jest: 'galleryNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/gallery/index.tsx b/packages/ui/src/components/editor/properties/gallery/index.tsx
new file mode 100644
index 0000000000..a7bf87ef14
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/gallery/index.tsx
@@ -0,0 +1,109 @@
+/*
+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 { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { BsPlusSquare } from 'react-icons/bs'
+import { LuImage } from 'react-icons/lu'
+import { Quaternion, Vector3 } from 'three'
+import Text from '../../../../primitives/tailwind/Text'
+import InputGroup from '../../input/Group'
+import StringInput from '../../input/String'
+import NodeEditor from '../nodeEditor'
+
+export const GalleryNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ //const spawnComponent = useComponent(props.entity, SpawnPointComponent)
+ const elements = ['hello', 'bye', 'thanks'] // temp use
+
+ return (
+ }
+ >
+
+
+ {t('editor:properties.gallery.lbl-thumbnail')}
+
+
+ {
+ const elem = { position: new Vector3(), quaternion: new Quaternion() }
+ const newElements = [
+ //...elements.get(NO_PROXY),
+ ...elements,
+ elem
+ ]
+ //commitProperty(, 'elements')(newElements)
+ }}
+ />
+
+
+ {elements.map(
+ (
+ elem,
+ index // need styling
+ ) => (
+
+
+ {t('editor:properties.gallery.lbl-asset') + ' ' + (index + 1)}
+
+
+ {
+ //updateProperty(, '')
+ }}
+ onRelease={(value) => {
+ //commitProperty(, '')
+ }}
+ />
+
+
+ {
+ //updateProperty(, '')
+ }}
+ onRelease={(value) => {
+ //commitProperty(, '')
+ }}
+ />
+
+
+ )
+ )}
+
+ )
+}
+
+GalleryNodeEditor.iconComponent = LuImage
+
+export default GalleryNodeEditor
diff --git a/packages/ui/src/components/editor/properties/groundPlane/index.tsx b/packages/ui/src/components/editor/properties/groundPlane/index.tsx
new file mode 100644
index 0000000000..a66f72eab0
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/groundPlane/index.tsx
@@ -0,0 +1,76 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { FaSquareFull } from 'react-icons/fa6'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { GroundPlaneComponent } from '@etherealengine/engine/src/scene/components/GroundPlaneComponent'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import ColorInput from '../../../../primitives/tailwind/Color'
+import InputGroup from '../../input/Group'
+import NodeEditor from '../nodeEditor'
+
+export const GroundPlaneNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const groundPlaneComponent = useComponent(props.entity, GroundPlaneComponent)
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+GroundPlaneNodeEditor.iconComponent = FaSquareFull
+
+export default GroundPlaneNodeEditor
diff --git a/packages/ui/src/components/editor/properties/group/index.stories.tsx b/packages/ui/src/components/editor/properties/group/index.stories.tsx
new file mode 100644
index 0000000000..320593ff22
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/group/index.stories.tsx
@@ -0,0 +1,44 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/group',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PropertyGroup',
+ jest: 'propertyGroup.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/properties/group/index.tsx b/packages/ui/src/components/editor/properties/group/index.tsx
new file mode 100644
index 0000000000..d613cf1d05
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/group/index.tsx
@@ -0,0 +1,85 @@
+/*
+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 React, { Fragment, useState } from 'react'
+import { HiOutlineChevronDown, HiOutlineChevronRight } from 'react-icons/hi'
+import { HiMiniXMark } from 'react-icons/hi2'
+import { PiCursor } from 'react-icons/pi'
+import Button from '../../../../primitives/tailwind/Button'
+import Text from '../../../../primitives/tailwind/Text'
+
+interface Props {
+ name?: string
+ icon?: any
+ description?: string
+ onClose?: () => void
+ children?: React.ReactNode
+ rest?: Record
+}
+
+const PropertyGroup = ({ name, icon, description, children, onClose, ...rest }: Props) => {
+ const [minimized, setMinimized] = useState(false)
+
+ return (
+
+
+
+ {!minimized && description && (
+
+ {description.split('\\n').map((line, idx) => (
+
+ {line}
+
+
+ ))}
+
+ )}
+ {!minimized &&
{children}
}
+
+ )
+}
+
+PropertyGroup.defaultProps = {
+ name: 'Component name',
+ icon:
+}
+
+export default PropertyGroup
diff --git a/packages/ui/src/components/editor/properties/image/index.stories.tsx b/packages/ui/src/components/editor/properties/image/index.stories.tsx
new file mode 100644
index 0000000000..5f108feb09
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/image/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Image',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ImageNodeEditor',
+ jest: 'imageNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/properties/image/index.tsx b/packages/ui/src/components/editor/properties/image/index.tsx
new file mode 100644
index 0000000000..f59b4502fe
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/image/index.tsx
@@ -0,0 +1,73 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { ImageComponent } from '@etherealengine/engine/src/scene/components/ImageComponent'
+
+import { useComponent } from '@etherealengine/ecs'
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { LuImage } from 'react-icons/lu'
+import InputGroup from '../../input/Group'
+import ImageInput from '../../input/Image'
+import NodeEditor from '../nodeEditor'
+import ImageSourceProperties from './sourceProperties'
+
+export const ImageNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const entity = props.entity
+ const imageComponent = useComponent(entity, ImageComponent)
+ const errors = getEntityErrors(props.entity, ImageComponent)
+
+ return (
+ }
+ >
+
+
+
+ {errors ? (
+ Object.entries(errors).map(([err, message]) => (
+
+ {'Error: ' + err + '--' + message}
+
+ ))
+ ) : (
+ <>>
+ )}
+ {}
+ {/**/}
+
+ )
+}
+
+ImageNodeEditor.iconComponent = LuImage
+
+export default ImageNodeEditor
diff --git a/packages/ui/src/components/editor/properties/image/sourceProperties/index.stories.tsx b/packages/ui/src/components/editor/properties/image/sourceProperties/index.stories.tsx
new file mode 100644
index 0000000000..a2ff96588e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/image/sourceProperties/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Image/SourceProperties',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ImageSourcePropertiesEditor',
+ jest: 'imageNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: Component.defaultProps }
diff --git a/packages/ui/src/components/editor/properties/image/sourceProperties/index.tsx b/packages/ui/src/components/editor/properties/image/sourceProperties/index.tsx
new file mode 100644
index 0000000000..02aabbe5b1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/image/sourceProperties/index.tsx
@@ -0,0 +1,107 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { BackSide, DoubleSide, FrontSide } from 'three'
+
+import { ImageAlphaMode, ImageProjection } from '@etherealengine/engine/src/scene/classes/ImageUtils'
+import { ImageComponent } from '@etherealengine/engine/src/scene/components/ImageComponent'
+
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import SelectInput from '../../../input/Select'
+
+const mapValue = (v) => ({ label: v, value: v })
+const imageProjectionOptions = Object.values(ImageProjection).map(mapValue)
+const imageTransparencyOptions = Object.values(ImageAlphaMode).map(mapValue)
+
+const ImageProjectionSideOptions = [
+ { label: 'Front', value: FrontSide },
+ { label: 'Back', value: BackSide },
+ { label: 'Both', value: DoubleSide }
+]
+
+export const ImageSourceProperties: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ //const imageComponent = useComponent(props.entity, ImageComponent)
+
+ return (
+ <>
+
+
+
+ {
+ /*imageComponent.alphaMode.value === ImageAlphaMode.Mask*/ true && (
+
+
+
+ )
+ }
+
+
+
+
+
+
+ >
+ )
+}
+
+export default ImageSourceProperties
diff --git a/packages/ui/src/components/editor/properties/imageGrid/index.stories.tsx b/packages/ui/src/components/editor/properties/imageGrid/index.stories.tsx
new file mode 100644
index 0000000000..3a4e127500
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/imageGrid/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/ImageGrid',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ImageGridNodeEditor',
+ jest: 'imageGridNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/imageGrid/index.tsx b/packages/ui/src/components/editor/properties/imageGrid/index.tsx
new file mode 100644
index 0000000000..a72791c97c
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/imageGrid/index.tsx
@@ -0,0 +1,106 @@
+/*
+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 { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { BsPlusSquare } from 'react-icons/bs'
+import { LuImage } from 'react-icons/lu'
+import { Quaternion, Vector3 } from 'three'
+import Text from '../../../../primitives/tailwind/Text'
+import InputGroup from '../../input/Group'
+import StringInput from '../../input/String'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * SpawnPointNodeEditor component used to provide the editor view to customize Spawn Point properties.
+ */
+export const GalleryNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ //const spawnComponent = useComponent(props.entity, SpawnPointComponent)
+ const elements = ['hello', 'bye', 'thanks'] // temp use
+
+ return (
+ }>
+
+
+ {t('editor:properties.gallery.assets')}
+
+
+ {
+ const elem = { position: new Vector3(), quaternion: new Quaternion() }
+ const newElements = [
+ //...elements.get(NO_PROXY),
+ ...elements,
+ elem
+ ]
+ //commitProperty(, 'elements')(newElements)
+ }}
+ />
+
+
+ {elements.map(
+ (
+ elem,
+ index // need styling
+ ) => (
+
+
+ {t('editor:properties.gallery.lbl-asset') + ' ' + (index + 1)}
+
+
+ {
+ //updateProperty(, '')
+ }}
+ onRelease={(value) => {
+ //commitProperty(, '')
+ }}
+ />
+
+
+ {
+ //updateProperty(, '')
+ }}
+ onRelease={(value) => {
+ //commitProperty(, '')
+ }}
+ />
+
+
+ )
+ )}
+
+ )
+}
+
+GalleryNodeEditor.iconComponent = LuImage
+
+export default GalleryNodeEditor
diff --git a/packages/ui/src/components/editor/properties/light/ambient/index.stories.tsx b/packages/ui/src/components/editor/properties/light/ambient/index.stories.tsx
new file mode 100644
index 0000000000..9877ca2f6e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/ambient/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/Ambient',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'AmbientLightNodeEditor',
+ jest: 'ambientLightNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/ambient/index.tsx b/packages/ui/src/components/editor/properties/light/ambient/index.tsx
new file mode 100644
index 0000000000..73b173d5e4
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/ambient/index.tsx
@@ -0,0 +1,84 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { AmbientLightComponent } from '@etherealengine/spatial/src/renderer/components/AmbientLightComponent'
+import { HiOutlineSun } from 'react-icons/hi2'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import NodeEditor from '../../nodeEditor'
+
+/**
+ * AmbientLightNodeEditor component used to customize the ambient light element on the scene
+ * ambient light is basically used to illuminates all the objects present inside the scene.
+ */
+export const AmbientLightNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const lightComponent = useComponent(props.entity, AmbientLightComponent)
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+ )
+}
+
+AmbientLightNodeEditor.iconComponent = HiOutlineSun
+
+export default AmbientLightNodeEditor
diff --git a/packages/ui/src/components/editor/properties/light/directional/index.stories.tsx b/packages/ui/src/components/editor/properties/light/directional/index.stories.tsx
new file mode 100644
index 0000000000..22bc026031
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/directional/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/Directional',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'DirectionalLightNodeEditor',
+ jest: 'directionalLightNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/directional/index.tsx b/packages/ui/src/components/editor/properties/light/directional/index.tsx
new file mode 100644
index 0000000000..8b1f7a5c35
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/directional/index.tsx
@@ -0,0 +1,94 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { DirectionalLightComponent } from '@etherealengine/spatial/src/renderer/components/DirectionalLightComponent'
+
+import { BsLightning } from 'react-icons/bs'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import NodeEditor from '../../nodeEditor'
+
+/**
+ * DirectionalLightNodeEditor is used provides properties to customize DirectionaLight element.
+ */
+export const DirectionalLightNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const lightComponent = useComponent(props.entity, DirectionalLightComponent).value
+
+ return (
+ }
+ >
+
+
+
+
+
+
+ {/**/}
+
+
+
+
+ )
+}
+
+DirectionalLightNodeEditor.iconComponent = BsLightning
+
+export default DirectionalLightNodeEditor
diff --git a/packages/ui/src/components/editor/properties/light/hemisphere/index.stories.tsx b/packages/ui/src/components/editor/properties/light/hemisphere/index.stories.tsx
new file mode 100644
index 0000000000..580ba9a411
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/hemisphere/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/Hemisphere',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'HemisphereLightNodeEditor',
+ jest: 'hemisphereLightNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/hemisphere/index.tsx b/packages/ui/src/components/editor/properties/light/hemisphere/index.tsx
new file mode 100644
index 0000000000..b37f0da218
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/hemisphere/index.tsx
@@ -0,0 +1,85 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { HemisphereLightComponent } from '@etherealengine/spatial/src/renderer/components/HemisphereLightComponent'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { PiSunHorizon } from 'react-icons/pi'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import NodeEditor from '../../nodeEditor'
+
+/**
+ * HemisphereLightNodeEditor used to provide property customization view for Hemisphere Light.
+ */
+export const HemisphereLightNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const lightComponent = useComponent(props.entity, HemisphereLightComponent).value
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+HemisphereLightNodeEditor.iconComponent = PiSunHorizon
+
+export default HemisphereLightNodeEditor
diff --git a/packages/ui/src/components/editor/properties/light/point/index.stories.tsx b/packages/ui/src/components/editor/properties/light/point/index.stories.tsx
new file mode 100644
index 0000000000..a45c2d8995
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/point/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/Point',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PointLightNodeEditor',
+ jest: 'pointLightNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/point/index.tsx b/packages/ui/src/components/editor/properties/light/point/index.tsx
new file mode 100644
index 0000000000..26dffc550f
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/point/index.tsx
@@ -0,0 +1,95 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { PointLightComponent } from '@etherealengine/spatial/src/renderer/components/PointLightComponent'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { AiOutlineBulb } from 'react-icons/ai'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import NodeEditor from '../../nodeEditor'
+import LightShadowProperties from '../shadowProperties'
+
+export const PointLightNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const lightComponent = useComponent(props.entity, PointLightComponent).value
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+PointLightNodeEditor.iconComponent = AiOutlineBulb
+
+export default PointLightNodeEditor
diff --git a/packages/ui/src/components/editor/properties/light/shadowProperties/index.stories.tsx b/packages/ui/src/components/editor/properties/light/shadowProperties/index.stories.tsx
new file mode 100644
index 0000000000..8320b8545e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/shadowProperties/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/ShadowProperties',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'LightShadowProperties',
+ jest: 'LightShadowProperties.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/shadowProperties/index.tsx b/packages/ui/src/components/editor/properties/light/shadowProperties/index.tsx
new file mode 100644
index 0000000000..b85b2ec626
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/shadowProperties/index.tsx
@@ -0,0 +1,88 @@
+/*
+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 { useTranslation } from 'react-i18next'
+
+import { Component, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import React from 'react'
+import BooleanInput from '../../../input/Boolean'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+
+/**creating properties for LightShadowProperties component */
+type LightShadowPropertiesProps = {
+ entity: Entity
+ comp: Component
+}
+
+/**
+ * OnChangeShadowMapResolution used to customize properties of LightShadowProperties
+ * Used with LightNodeEditors.
+ */
+export const LightShadowProperties: EditorComponentType = (props: LightShadowPropertiesProps) => {
+ const { t } = useTranslation()
+
+ const lightComponent = useComponent(props.entity, props.comp).value as any
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default LightShadowProperties
diff --git a/packages/ui/src/components/editor/properties/light/spot/index.stories.tsx b/packages/ui/src/components/editor/properties/light/spot/index.stories.tsx
new file mode 100644
index 0000000000..cf5546246e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/spot/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Light/Spot',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'VideoNodeEditor',
+ jest: 'videoNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/light/spot/index.tsx b/packages/ui/src/components/editor/properties/light/spot/index.tsx
new file mode 100644
index 0000000000..bb4094ec79
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/light/spot/index.tsx
@@ -0,0 +1,122 @@
+/*
+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 { SpotLightComponent } from '@etherealengine/spatial/src/renderer/components/SpotLightComponent'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { LuCircleDot } from 'react-icons/lu'
+import { MathUtils as _Math } from 'three'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import NodeEditor from '../../nodeEditor'
+import LightShadowProperties from '../shadowProperties'
+
+/**
+ * SpotLightNodeEditor component class used to provide editor view for property customization.
+ */
+export const SpotLightNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const lightComponent = useComponent(props.entity, SpotLightComponent).value
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+
+
+ updateProperty(SpotLightComponent, 'angle')(_Math.degToRad(value))}
+ onRelease={(value) => commitProperty(SpotLightComponent, 'angle')(_Math.degToRad(value))}
+ unit="°"
+ />
+
+
+
+
+
+
+
+
+
+ )
+}
+
+SpotLightNodeEditor.iconComponent = LuCircleDot
+
+export default SpotLightNodeEditor
diff --git a/packages/ui/src/components/editor/properties/link/index.stories.tsx b/packages/ui/src/components/editor/properties/link/index.stories.tsx
new file mode 100644
index 0000000000..4879081cb1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/link/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Link',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'LinkNodeEditor',
+ jest: 'linkNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/link/index.tsx b/packages/ui/src/components/editor/properties/link/index.tsx
new file mode 100644
index 0000000000..05ae4d251a
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/link/index.tsx
@@ -0,0 +1,93 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PiLinkBreak } from 'react-icons/pi'
+
+import { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { LinkComponent } from '@etherealengine/engine/src/scene/components/LinkComponent'
+import BooleanInput from '../../input/Boolean'
+import InputGroup from '../../input/Group'
+import { ControlledStringInput } from '../../input/String'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * LinkNodeEditor component used to provide the editor view to customize link properties.
+ */
+export const LinkNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const linkComponent = useComponent(props.entity, LinkComponent)
+ const errors = getEntityErrors(props.entity, LinkComponent)
+
+ return (
+ }
+ >
+ {errors
+ ? Object.entries(errors).map(([err, message]) => (
+
+ {'Error: ' + err + '--' + message}
+
+ ))
+ : null}
+
+
+
+
+ {linkComponent.sceneNav.value ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ )
+}
+
+LinkNodeEditor.iconComponent = PiLinkBreak
+
+export default LinkNodeEditor
diff --git a/packages/ui/src/components/editor/properties/media/index.stories.tsx b/packages/ui/src/components/editor/properties/media/index.stories.tsx
new file mode 100644
index 0000000000..1ebacc769e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/media/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Media',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'MediaNodeEditor',
+ jest: '',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/media/index.tsx b/packages/ui/src/components/editor/properties/media/index.tsx
new file mode 100644
index 0000000000..1de9d35286
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/media/index.tsx
@@ -0,0 +1,172 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiOutlineVideoCamera } from 'react-icons/hi2'
+
+import { useComponent, useOptionalComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import {
+ MediaComponent,
+ MediaElementComponent,
+ setTime
+} from '@etherealengine/engine/src/scene/components/MediaComponent'
+import { PlayMode } from '@etherealengine/engine/src/scene/constants/PlayMode'
+import Button from '../../../../primitives/tailwind/Button'
+import Slider from '../../../../primitives/tailwind/Slider'
+import ArrayInputGroup from '../../input/Array'
+import BooleanInput from '../../input/Boolean'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+const PlayModeOptions = [
+ {
+ label: 'Single',
+ value: PlayMode.single
+ },
+ {
+ label: 'Random',
+ value: PlayMode.random
+ },
+ {
+ label: 'Loop',
+ value: PlayMode.loop
+ },
+ {
+ label: 'SingleLoop',
+ value: PlayMode.singleloop
+ }
+]
+
+/**
+ * MediaNodeEditor used to render editor view for property customization.
+ */
+export const MediaNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const media = useComponent(props.entity, MediaComponent)
+ const element = useOptionalComponent(props.entity, MediaElementComponent)
+
+ const toggle = () => {
+ media.paused.set(!media.paused.value)
+ }
+
+ const reset = () => {
+ if (element) {
+ setTime(element.element, media.seekTime.value)
+ }
+ }
+
+ return (
+ }
+ >
+
+ commitProperty(MediaComponent, 'volume')}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {media.resources.length > 0 && (
+
+
+ {media.paused.value ? t('editor:properties.media.playtitle') : t('editor:properties.media.pausetitle')}
+
+ {t('editor:properties.media.resettitle')}
+
+ )}
+
+ )
+}
+
+MediaNodeEditor.iconComponent = HiOutlineVideoCamera
+
+export default MediaNodeEditor
diff --git a/packages/ui/src/components/editor/properties/mesh/geometryEditor.tsx b/packages/ui/src/components/editor/properties/mesh/geometryEditor.tsx
new file mode 100644
index 0000000000..8c537dfbcc
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/mesh/geometryEditor.tsx
@@ -0,0 +1,94 @@
+/*
+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 React, { useCallback, useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { BufferAttribute, BufferGeometry, InterleavedBufferAttribute } from 'three'
+
+import { useHookstate } from '@etherealengine/hyperflux'
+import { HiTrash } from 'react-icons/hi2'
+import Button from '../../../../primitives/tailwind/Button'
+import Label from '../../../../primitives/tailwind/Label'
+import Text from '../../../../primitives/tailwind/Text'
+
+const recalculateNormals = (geometry: BufferGeometry) => {
+ geometry.computeVertexNormals()
+}
+
+export default function GeometryEditor({ geometry }: { ['geometry']: BufferGeometry | null }) {
+ if (!geometry) return null
+
+ const { t } = useTranslation()
+
+ const updateGeo = useHookstate(0)
+
+ const updateGeoData = useCallback(
+ () => ({
+ uuid: geometry.uuid,
+ name: geometry.name,
+ attributes: Object.entries(geometry.attributes).map(([attribName, attrib]) => ({
+ name: attribName,
+ count: attrib.count,
+ itemSize: attrib.itemSize,
+ normalized: (attrib as BufferAttribute | InterleavedBufferAttribute).normalized
+ }))
+ }),
+ [updateGeo]
+ )
+
+ const geoData = useHookstate(updateGeoData())
+ useEffect(() => {
+ geoData.set(updateGeoData())
+ }, [updateGeo])
+
+ const deleteBufferAttribute = (attribName: string) => {
+ geometry.deleteAttribute(attribName)
+ updateGeo.set(updateGeo.get() + 1)
+ }
+
+ return (
+
+
recalculateNormals(geometry)}>
+ {t('editor:properties.mesh.geometry.recalculateNormals')}
+
+ {geoData.attributes.map((attribute, idx) => (
+
+
}
+ className="absolute right-0 top-1 text-theme-iconRed"
+ onClick={() => deleteBufferAttribute(attribute.name.value)}
+ />
+ {['name', 'count', 'itemSize'].map((property) => (
+
+
+ {attribute[property].value}
+
+ ))}
+
+ ))}
+
+ )
+}
diff --git a/packages/ui/src/components/editor/properties/mesh/index.tsx b/packages/ui/src/components/editor/properties/mesh/index.tsx
new file mode 100644
index 0000000000..c800aceacd
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/mesh/index.tsx
@@ -0,0 +1,66 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { getComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import { MeshComponent } from '@etherealengine/spatial/src/renderer/components/MeshComponent'
+import { HiMinus, HiPlusSmall } from 'react-icons/hi2'
+import Accordion from '../../../../primitives/tailwind/Accordion'
+import NodeEditor from '../nodeEditor'
+import GeometryEditor from './geometryEditor'
+
+const MeshNodeEditor: EditorComponentType = (props: { entity: Entity }) => {
+ const entity = props.entity
+ const { t } = useTranslation()
+ const meshComponent = getComponent(entity, MeshComponent)
+ return (
+
+ }
+ shrinkIcon={}
+ >
+
+
+ }
+ shrinkIcon={}
+ >
+ {/* */}
+
+
+ )
+}
+
+export default MeshNodeEditor
diff --git a/packages/ui/src/components/editor/properties/mesh/materialEditor.tsx b/packages/ui/src/components/editor/properties/mesh/materialEditor.tsx
new file mode 100644
index 0000000000..1cbd7e6a44
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/mesh/materialEditor.tsx
@@ -0,0 +1,24 @@
+/*
+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.
+*/
diff --git a/packages/ui/src/components/editor/properties/mountPoint/index.stories.tsx b/packages/ui/src/components/editor/properties/mountPoint/index.stories.tsx
new file mode 100644
index 0000000000..1c9c8aa4e9
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/mountPoint/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/MountPoint',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'MountPointNodeEditor',
+ jest: 'mountPointNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/mountPoint/index.tsx b/packages/ui/src/components/editor/properties/mountPoint/index.tsx
new file mode 100644
index 0000000000..e4d6b35130
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/mountPoint/index.tsx
@@ -0,0 +1,80 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useComponent } from '@etherealengine/ecs'
+import InputGroup from '@etherealengine/editor/src/components/inputs/InputGroup'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { MountPoint, MountPointComponent } from '@etherealengine/engine/src/scene/components/MountPointComponent'
+import { LuUsers2 } from 'react-icons/lu'
+import SelectInput from '../../input/Select'
+import Vector3Input from '../../input/Vector3'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * MountPointNodeEditor component used to provide the editor view to customize Mount Point properties.
+ *
+ * @type {Class component}
+ */
+export const MountPointNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const mountComponent = useComponent(props.entity, MountPointComponent)
+
+ return (
+ }
+ >
+
+ ({ label: key, value }))}
+ onChange={commitProperty(MountPointComponent, 'type')}
+ />
+
+
+
+
+
+ )
+}
+
+MountPointNodeEditor.iconComponent = LuUsers2
+
+export default MountPointNodeEditor
diff --git a/packages/ui/src/components/editor/properties/nodeEditor/index.stories.tsx b/packages/ui/src/components/editor/properties/nodeEditor/index.stories.tsx
new file mode 100644
index 0000000000..496930f0b5
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/nodeEditor/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/NodeEditor',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'NodeEditor',
+ jest: 'nodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/nodeEditor/index.tsx b/packages/ui/src/components/editor/properties/nodeEditor/index.tsx
new file mode 100644
index 0000000000..59de26ede8
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/nodeEditor/index.tsx
@@ -0,0 +1,110 @@
+/*
+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 React, { PropsWithChildren, Suspense } from 'react'
+
+import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle'
+import { hasComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { EditorPropType } from '@etherealengine/editor/src/components/properties/Util'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import Text from '../../../../primitives/tailwind/Text'
+import PropertyGroup from '../group'
+
+interface NodeErrorProps {
+ name?: string
+ children?: React.ReactNode
+}
+
+interface NodeErrorState {
+ error: Error | null
+}
+
+class NodeEditorErrorBoundary extends React.Component {
+ public state: NodeErrorState = {
+ error: null
+ }
+
+ public static getDerivedStateFromError(error: Error): NodeErrorState {
+ // Update state so the next render will show the fallback UI.
+ return { error }
+ }
+
+ public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('Uncaught error:', error, errorInfo)
+ }
+
+ public render() {
+ if (this.state.error) {
+ return (
+
+
+ [{this.props.name}] {this.state.error.message}`
+
+
{this.state.error.stack}
+
+ )
+ }
+
+ return this.props.children
+ }
+}
+
+type NodeEditorProps = EditorPropType & {
+ description?: string
+ name?: string
+ icon?: JSX.Element
+}
+
+export const NodeEditor: React.FC> = ({
+ description,
+ children,
+ name,
+ entity,
+ component,
+ icon
+}) => {
+ return (
+ {
+ const entities = SelectionState.getSelectedEntities()
+ EditorControlFunctions.addOrRemoveComponent(entities, component, false)
+ }
+ : undefined
+ }
+ >
+ }>
+ {children}
+
+
+ )
+}
+
+export default NodeEditor
diff --git a/packages/ui/src/components/editor/properties/parameter/index.tsx b/packages/ui/src/components/editor/properties/parameter/index.tsx
new file mode 100644
index 0000000000..4eb0708f65
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/parameter/index.tsx
@@ -0,0 +1,162 @@
+/*
+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 React, { Fragment } from 'react'
+
+import { generateDefaults } from '@etherealengine/spatial/src/renderer/materials/constants/DefaultArgs'
+import ColorInput from '../../../../primitives/tailwind/Color'
+import BooleanInput from '../../input/Boolean'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import StringInput from '../../input/String'
+import TexturePreviewInput from '../../input/Texture'
+
+export default function ParameterInput({
+ entity,
+ values,
+ onChange,
+ defaults,
+ thumbnails
+}: {
+ entity: string
+ values: object
+ defaults?: object
+ thumbnails?: Record
+ onChange: (k: string) => (v) => void
+}) {
+ function setArgsProp(k) {
+ const thisOnChange = onChange(k)
+ return (value) => {
+ //values[k] = value
+ thisOnChange(value)
+ }
+ }
+
+ function setArgsArrayProp(k, idx) {
+ const thisOnChange = onChange(k)
+ return (value) => {
+ const nuVals = values[k].map((oldVal, oldIdx) => (idx === oldIdx ? value : oldVal))
+ thisOnChange(nuVals)
+ }
+ }
+
+ function setArgsObjectProp(k) {
+ const thisOnChange = onChange(k)
+ const oldVal = values[k]
+ return (field) => {
+ return (value) => {
+ const nuVal = Object.fromEntries([
+ ...Object.entries(oldVal).filter(([_field, _value]) => _field !== field),
+ [field, value]
+ ])
+ thisOnChange(nuVal)
+ }
+ }
+ }
+
+ const _defaults = defaults ?? generateDefaults(values)
+ /*
+0: "boolean"
+1: "string"
+2: "integer"
+3: "float"
+4: "vec2"
+5: "vec3"
+6: "vec4"
+7: "color"
+8: "euler"
+9: "quat"
+10: "mat3"
+11: "mat4"
+12: "object"
+13: "list"
+14: "entity"*/
+ return (
+
+ {Object.entries(_defaults).map(([k, parms]: [string, any]) => {
+ const compKey = `${entity}-${k}`
+ return (
+
+ {(() => {
+ switch (parms.type) {
+ case 'boolean':
+ return
+ case 'entity':
+ case 'integer':
+ case 'float':
+ return
+ case 'string':
+ return
+ case 'color':
+ return
+ case 'texture':
+ if (thumbnails?.[k])
+ return
+ else return
+ case 'vec2':
+ case 'vec3':
+ case 'vec4':
+ return (
+
+ {typeof values[k]?.map === 'function' &&
+ (values[k] as number[]).map((arrayVal, idx) => {
+ return (
+
+ )
+ })}
+
+ )
+ case 'select':
+ return (
+
+ )
+ case 'object':
+ return (
+
+ )
+ default:
+ return <>>
+ }
+ })()}
+
+ )
+ })}
+
+ )
+}
diff --git a/packages/ui/src/components/editor/properties/particle/index.stories.tsx b/packages/ui/src/components/editor/properties/particle/index.stories.tsx
new file mode 100644
index 0000000000..ff3dbeb489
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/particle/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Particle',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ParticleNodeEditor',
+ jest: 'particleNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/particle/index.tsx b/packages/ui/src/components/editor/properties/particle/index.tsx
new file mode 100644
index 0000000000..364c503097
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/particle/index.tsx
@@ -0,0 +1,446 @@
+/*
+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 React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+ AdditiveBlending,
+ Blending,
+ CustomBlending,
+ MultiplyBlending,
+ NoBlending,
+ NormalBlending,
+ SubtractiveBlending
+} from 'three'
+import { BurstParameters, RenderMode } from 'three.quarks'
+
+import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import {
+ ApplyForceBehaviorJSON,
+ BehaviorJSON,
+ BurstParametersJSON,
+ CONE_SHAPE_DEFAULT,
+ ColorGeneratorJSON,
+ ConstantColorJSON,
+ DONUT_SHAPE_DEFAULT,
+ ExtraSystemJSON,
+ MESH_SHAPE_DEFAULT,
+ POINT_SHAPE_DEFAULT,
+ ParticleSystemComponent,
+ SPHERE_SHAPE_DEFAULT,
+ ValueGeneratorJSON
+} from '@etherealengine/engine/src/scene/components/ParticleSystemComponent'
+import { State } from '@etherealengine/hyperflux'
+import { HiSparkles } from 'react-icons/hi'
+
+import NumericInputGroup from '@etherealengine/editor/src/components/inputs/NumericInputGroup'
+import ParameterInput from '@etherealengine/editor/src/components/inputs/ParameterInput'
+import PaginatedList from '@etherealengine/editor/src/components/layout/PaginatedList'
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import { Button } from '@mui/material'
+import BehaviorInput from '../../input/Behavior'
+import ColorGenerator from '../../input/Generator/Color'
+import ValueGenerator from '../../input/Generator/Value'
+import InputGroup from '../../input/Group'
+import ModelInput from '../../input/Model'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import TexturePreviewInput from '../../input/Texture'
+import NodeEditor from '../nodeEditor'
+
+const ParticleSystemNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const entity = props.entity
+ const particleSystemState = useComponent(entity, ParticleSystemComponent)
+ const particleSystem = particleSystemState.value
+
+ const onSetSystemParm = useCallback((field: keyof typeof particleSystem.systemParameters) => {
+ const parm = particleSystem.systemParameters[field]
+ return (value: typeof parm) => {
+ particleSystemState._refresh.set(particleSystem._refresh + 1)
+ commitProperty(ParticleSystemComponent, ('systemParameters.' + field) as any)(value)
+ }
+ }, [])
+
+ const shapeDefaults = {
+ point: POINT_SHAPE_DEFAULT,
+ sphere: SPHERE_SHAPE_DEFAULT,
+ cone: CONE_SHAPE_DEFAULT,
+ donut: DONUT_SHAPE_DEFAULT,
+ mesh_surface: MESH_SHAPE_DEFAULT
+ }
+
+ const onChangeShape = useCallback(() => {
+ const onSetShape = onSetSystemParm('shape')
+ return (shape: string) => {
+ onSetShape(shapeDefaults[shape])
+ }
+ }, [])
+
+ const onChangeShapeParm = useCallback((field: keyof typeof particleSystem.systemParameters.shape) => {
+ return (value: any) => {
+ const nuParms = JSON.parse(JSON.stringify(particleSystem.systemParameters.shape))
+ nuParms[field] = value
+ commitProperty(ParticleSystemComponent, 'systemParameters.shape' as any)(nuParms)
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ }
+ }, [])
+
+ const onSetState = useCallback((state: State) => {
+ return (value: any) => {
+ state.set(value)
+ const { systemParameters, behaviorParameters } = JSON.parse(
+ JSON.stringify(getComponent(entity, ParticleSystemComponent))
+ )
+ commitProperties(
+ ParticleSystemComponent,
+ {
+ systemParameters,
+ behaviorParameters
+ },
+ [props.entity]
+ )
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ }
+ }, [])
+
+ const onAddBehavior = useCallback(() => {
+ const nuBehavior: ApplyForceBehaviorJSON = {
+ type: 'ApplyForce',
+ direction: [0, 1, 0],
+ magnitude: {
+ type: 'ConstantValue',
+ value: 1
+ }
+ }
+ particleSystemState.behaviorParameters.set([
+ ...JSON.parse(JSON.stringify(particleSystem.behaviorParameters)),
+ nuBehavior
+ ])
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ }, [])
+
+ const onRemoveBehavior = useCallback(
+ (behavior: BehaviorJSON) => () => {
+ const data = JSON.parse(JSON.stringify(particleSystem.behaviorParameters.filter((b) => b !== behavior)))
+ commitProperty(ParticleSystemComponent, 'behaviorParameters')(data)
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ },
+ []
+ )
+
+ const onAddBurst = useCallback(() => {
+ const nuBurst: BurstParametersJSON = {
+ time: 0,
+ count: 0,
+ cycle: 0,
+ interval: 0,
+ probability: 0
+ }
+ const data = [...JSON.parse(JSON.stringify(particleSystem.systemParameters.emissionBursts)), nuBurst]
+ commitProperty(ParticleSystemComponent, 'systemParameters.emissionBursts' as any)(data)
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ }, [])
+
+ const onRemoveBurst = useCallback((burst: State) => {
+ return () => {
+ const data = JSON.parse(
+ JSON.stringify(
+ particleSystem.systemParameters.emissionBursts.filter((b: any) => b !== (burst.value as BurstParameters))
+ )
+ )
+ commitProperty(ParticleSystemComponent, 'systemParameters.emissionBursts' as any)(data)
+ particleSystemState._refresh.set((particleSystem._refresh + 1) % 1000)
+ }
+ }, [])
+
+ return (
+ }
+ >
+ Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Burst
+
+ )
+ : []
+ }
+ element={(burst: State) => {
+ return (
+
+
+
+
+
+
+ Remove Burst
+
+ )
+ }}
+ />
+ {particleSystem.systemParameters.shape.type === 'mesh_surface' && (
+
+
+
+ )}
+ {particleSystem.systemParameters.shape.type !== 'mesh_surface' && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ value={particleSystem.systemParameters.startColor as ConstantColorJSON}
+ onChange={onSetState}
+ />
+
+
+
+
+
+
+
+ {particleSystem.systemParameters.renderMode === RenderMode.Trail && (
+ <>
+
+
+ }
+ onChange={onSetState}
+ />
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+ {typeof particleSystem.systemParameters.startTileIndex === 'number' && (
+ <>
+
+ {
+ const nuParms = JSON.parse(JSON.stringify(particleSystem.systemParameters))
+ nuParms.startTileIndex = {
+ type: 'ConstantValue',
+ value: particleSystem.systemParameters.startTileIndex
+ }
+ particleSystemState.systemParameters.set(nuParms)
+ commitProperty(ParticleSystemComponent, 'systemParameters')(nuParms)
+ particleSystemState._refresh.set(particleSystem._refresh + 1)
+ }}
+ >
+ Convert to Value Generator
+
+ >
+ )}
+ {typeof particleSystem.systemParameters.startTileIndex === 'object' && (
+ }
+ value={particleSystem.systemParameters.startTileIndex as ValueGeneratorJSON}
+ onChange={onSetState}
+ />
+ )}
+
+
+
+ ).instancingGeometry
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+ Behaviors
+ Add Behavior
+ ) => {
+ return (
+ <>
+
+ Remove
+ >
+ )
+ }}
+ />
+
+ )
+}
+
+ParticleSystemNodeEditor.iconComponent = HiSparkles
+
+export default ParticleSystemNodeEditor
diff --git a/packages/ui/src/components/editor/properties/portal/index.stories.tsx b/packages/ui/src/components/editor/properties/portal/index.stories.tsx
new file mode 100644
index 0000000000..29c81b5241
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/portal/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Portal',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PortalNodeEditor',
+ jest: 'portalNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/portal/index.tsx b/packages/ui/src/components/editor/properties/portal/index.tsx
new file mode 100644
index 0000000000..ff0f85da0b
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/portal/index.tsx
@@ -0,0 +1,226 @@
+/*
+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 React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { CiSpeaker } from 'react-icons/ci'
+import { Euler, Quaternion, Vector3 } from 'three'
+
+import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import {
+ PortalComponent,
+ PortalEffects,
+ PortalPreviewTypes
+} from '@etherealengine/engine/src/scene/components/PortalComponent'
+
+import { UUIDComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { bakeEnvmapTexture, uploadCubemapBakeToServer } from '@etherealengine/editor/src/functions/uploadEnvMapBake'
+import { imageDataToBlob } from '@etherealengine/engine/src/scene/classes/ImageUtils'
+import { useHookstate } from '@etherealengine/hyperflux'
+import { TransformComponent } from '@etherealengine/spatial'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import Button from '../../../../primitives/tailwind/Button'
+import EulerInput from '../../input/Euler'
+import InputGroup from '../../input/Group'
+import ImagePreviewInput from '../../input/Image/Preview'
+import SelectInput from '../../input/Select'
+import StringInput, { ControlledStringInput } from '../../input/String'
+import Vector3Input from '../../input/Vector3'
+import NodeEditor from '../nodeEditor'
+
+type PortalOptions = {
+ label: string
+ value: string
+}
+
+const rotation = new Quaternion()
+
+/**
+ * PortalNodeEditor provides the editor for properties of PortalNode.
+ */
+export const PortalNodeEditor: EditorComponentType = (props) => {
+ const state = useHookstate({
+ portals: [] as PortalOptions[],
+ previewImageData: null as ImageData | null,
+ previewImageURL: ''
+ })
+
+ const { t } = useTranslation()
+ const transformComponent = useComponent(props.entity, TransformComponent)
+ const portalComponent = useComponent(props.entity, PortalComponent)
+
+ useEffect(() => {
+ loadPortals()
+ }, [])
+
+ const updateCubeMapBake = async () => {
+ const imageData = await bakeEnvmapTexture(
+ transformComponent.value.position.clone().add(new Vector3(0, 2, 0).multiply(transformComponent.scale.value))
+ )
+ const blob = await imageDataToBlob(imageData)
+ state.previewImageData.set(imageData)
+ state.previewImageURL.set(URL.createObjectURL(blob!))
+ }
+
+ const loadPortals = async () => {
+ const portalsDetail: any[] = []
+ try {
+ portalsDetail
+ .push
+ //...((await API.instance.client.service(portalPath).find({ query: { paginate: false } })) as PortalType[])
+ ()
+ console.log('portalsDetail', portalsDetail, getComponent(props.entity, UUIDComponent))
+ } catch (error) {
+ throw new Error(error)
+ }
+ state.portals.set(
+ portalsDetail
+ .filter((portal) => portal.portalEntityId !== getComponent(props.entity, UUIDComponent))
+ .map(({ portalEntityId, portalEntityName, sceneName }) => {
+ return { value: portalEntityId, label: sceneName + ': ' + portalEntityName }
+ })
+ )
+ }
+
+ const uploadEnvmap = async () => {
+ if (!state.previewImageData.value) return
+ const url = await uploadCubemapBakeToServer(getComponent(props.entity, NameComponent), state.previewImageData.value)
+ commitProperties(PortalComponent, { previewImageURL: url }, [props.entity])
+ }
+
+ const changeSpawnRotation = (value: Euler) => {
+ rotation.setFromEuler(value)
+
+ commitProperties(PortalComponent, { spawnRotation: rotation })
+ }
+
+ const changePreviewType = (val) => {
+ commitProperties(PortalComponent, { previewType: val })
+ loadPortals()
+ }
+
+ return (
+ } {...props}>
+
+
+
+
+
+
+
+
+
+
+ {
+ return { value: val, label: val }
+ })}
+ value={portalComponent.effectType.value}
+ onChange={commitProperty(PortalComponent, 'effectType')}
+ />
+
+
+ {
+ return { value: val, label: val }
+ })}
+ value={portalComponent.previewType.value}
+ onChange={changePreviewType}
+ />
+
+
+
+
+
+
+
+ {
+ updateCubeMapBake()
+ }}
+ >
+ {t('editor:properties.portal.lbl-generateImage')}
+
+ {
+ uploadEnvmap()
+ }}
+ >
+ {t('editor:properties.portal.lbl-saveImage')}
+
+
+
+
+
+
+
+
+
+
+ commitProperty(PortalComponent, 'spawnRotation')(getComponent(props.entity, PortalComponent).spawnRotation)
+ }
+ />
+
+
+ )
+}
+
+PortalNodeEditor.iconComponent = CiSpeaker
+
+export default PortalNodeEditor
diff --git a/packages/ui/src/components/editor/properties/positionalAudio/index.stories.tsx b/packages/ui/src/components/editor/properties/positionalAudio/index.stories.tsx
new file mode 100644
index 0000000000..03a4d86e08
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/positionalAudio/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/PositionalAudio',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'PositionalAudioNodeEditor',
+ jest: 'positionalAudioNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/positionalAudio/index.tsx b/packages/ui/src/components/editor/properties/positionalAudio/index.tsx
new file mode 100644
index 0000000000..f66e37a19c
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/positionalAudio/index.tsx
@@ -0,0 +1,209 @@
+/*
+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 React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { PiSpeakerLowLight } from 'react-icons/pi'
+
+import { hasComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { PositionalAudioComponent } from '@etherealengine/engine/src/audio/components/PositionalAudioComponent'
+import { DistanceModel, DistanceModelOptions } from '@etherealengine/engine/src/audio/constants/AudioConstants'
+
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { MediaComponent } from '@etherealengine/engine/src/scene/components/MediaComponent'
+import { VolumetricComponent } from '@etherealengine/engine/src/scene/components/VolumetricComponent'
+import Slider from '../../../../primitives/tailwind/Slider'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import ProgressBar from '../../input/Progress'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * AudioNodeEditor used to customize audio element on the scene.
+ */
+export const PositionalAudioNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const audioComponent = useComponent(props.entity, PositionalAudioComponent)
+
+ useEffect(() => {
+ if (!hasComponent(props.entity, MediaComponent) && !hasComponent(props.entity, VolumetricComponent)) {
+ const nodes = SelectionState.getSelectedEntities()
+ EditorControlFunctions.addOrRemoveComponent(nodes, MediaComponent, true)
+ }
+ }, [])
+
+ return (
+ }
+ >
+
+
+
+
+
+ {audioComponent.distanceModel.value === DistanceModel.Linear ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+PositionalAudioNodeEditor.iconComponent = PiSpeakerLowLight
+
+export default PositionalAudioNodeEditor
diff --git a/packages/ui/src/components/editor/properties/rigidBody/index.tsx b/packages/ui/src/components/editor/properties/rigidBody/index.tsx
new file mode 100644
index 0000000000..6340cf4778
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/rigidBody/index.tsx
@@ -0,0 +1,67 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { MdPanTool } from 'react-icons/md'
+
+import { camelCaseToSpacedString } from '@etherealengine/common/src/utils/camelCaseToSpacedString'
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { RigidBodyComponent } from '@etherealengine/spatial/src/physics/components/RigidBodyComponent'
+import { BodyTypes } from '@etherealengine/spatial/src/physics/types/PhysicsTypes'
+import Select from '../../../../primitives/tailwind/Select'
+import InputGroup from '../../input/Group'
+import NodeEditor from '../nodeEditor'
+
+const bodyTypeOptions = Object.entries(BodyTypes).map(([label, value]) => {
+ return { label: camelCaseToSpacedString(label as string), value }
+})
+
+export const RigidBodyComponentEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const rigidbodyComponent = useComponent(props.entity, RigidBodyComponent)
+
+ return (
+
+
+
+
+
+ )
+}
+
+RigidBodyComponentEditor.iconComponent = MdPanTool
+
+export default RigidBodyComponentEditor
diff --git a/packages/ui/src/components/editor/properties/scenePreviewCamera/index.stories.tsx b/packages/ui/src/components/editor/properties/scenePreviewCamera/index.stories.tsx
new file mode 100644
index 0000000000..468da6fdef
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/scenePreviewCamera/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/ScenePreviewCamera',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ScenePreviewCameraNodeEditor',
+ jest: 'scenePreviewCameraNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/scenePreviewCamera/index.tsx b/packages/ui/src/components/editor/properties/scenePreviewCamera/index.tsx
new file mode 100644
index 0000000000..e4f0ab3180
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/scenePreviewCamera/index.tsx
@@ -0,0 +1,118 @@
+/*
+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 { debounce } from 'lodash'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiOutlineCamera } from 'react-icons/hi'
+
+import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { Engine } from '@etherealengine/ecs/src/Engine'
+import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
+
+import { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { previewScreenshot } from '@etherealengine/editor/src/functions/takeScreenshot'
+import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
+import { ScenePreviewCameraComponent } from '@etherealengine/engine/src/scene/components/ScenePreviewCamera'
+import { getState } from '@etherealengine/hyperflux'
+import { getNestedVisibleChildren } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem'
+import { GroupComponent } from '@etherealengine/spatial/src/renderer/components/GroupComponent'
+import { SceneComponent } from '@etherealengine/spatial/src/renderer/components/SceneComponents'
+import { computeTransformMatrix } from '@etherealengine/spatial/src/transform/systems/TransformSystem'
+import { Scene } from 'three'
+import ImagePreviewInput from '../../input/Image/Preview'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * ScenePreviewCameraNodeEditor provides the editor view to customize properties.
+ */
+export const ScenePreviewCameraNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const [bufferUrl, setBufferUrl] = useState('')
+ const transformComponent = useComponent(Engine.instance.cameraEntity, TransformComponent)
+
+ const onSetFromViewport = () => {
+ const { position, rotation } = getComponent(Engine.instance.cameraEntity, TransformComponent)
+ const transform = getComponent(props.entity, TransformComponent)
+ transform.position.copy(position)
+ transform.rotation.copy(rotation)
+ computeTransformMatrix(props.entity)
+
+ EditorControlFunctions.commitTransformSave([props.entity])
+ }
+
+ const updateScenePreview = async () => {
+ const rootEntity = getState(EditorState).rootEntity
+ const scene = new Scene()
+ scene.children = getComponent(rootEntity, SceneComponent)
+ .children.map(getNestedVisibleChildren)
+ .flat()
+ .map((entity) => getComponent(entity, GroupComponent))
+ .flat()
+ const imageBlob = (await previewScreenshot(
+ 512 / 2,
+ 320 / 2,
+ 0.9,
+ 'jpeg',
+ scene,
+ getComponent(props.entity, ScenePreviewCameraComponent).camera
+ ))!
+ const url = URL.createObjectURL(imageBlob)
+ setBufferUrl(url)
+ }
+
+ const updateCubeMapBakeDebounced = useCallback(debounce(updateScenePreview, 500), []) //ms
+
+ useEffect(() => {
+ updateCubeMapBakeDebounced()
+ return () => {
+ updateCubeMapBakeDebounced.cancel()
+ }
+ }, [transformComponent.position])
+
+ return (
+ }
+ >
+ {/* {
+ onSetFromViewport()
+ updateScenePreview()
+ }}
+ >
+ {t('editor:properties.sceneCamera.lbl-setFromViewPort')}
+ */}
+
+
+ )
+}
+
+ScenePreviewCameraNodeEditor.iconComponent = HiOutlineCamera
+
+export default ScenePreviewCameraNodeEditor
diff --git a/packages/ui/src/components/editor/properties/shadow/index.stories.tsx b/packages/ui/src/components/editor/properties/shadow/index.stories.tsx
new file mode 100644
index 0000000000..6f50e6b898
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/shadow/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Shadow',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'ShadowNodeEditor',
+ jest: 'shadowNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/shadow/index.tsx b/packages/ui/src/components/editor/properties/shadow/index.tsx
new file mode 100644
index 0000000000..888d10f918
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/shadow/index.tsx
@@ -0,0 +1,62 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { FaClone } from 'react-icons/fa6'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { ShadowComponent } from '@etherealengine/engine/src/scene/components/ShadowComponent'
+
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import InputGroup from '../../input/Group'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * ShadowProperties used to create editor view for the properties of ModelNode.
+ */
+export const ShadowNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const shadowComponent = useComponent(props.entity, ShadowComponent)
+ return (
+ }
+ {...props}
+ >
+
+
+
+
+
+
+
+ )
+}
+
+export default ShadowNodeEditor
diff --git a/packages/ui/src/components/editor/properties/skybox/index.stories.tsx b/packages/ui/src/components/editor/properties/skybox/index.stories.tsx
new file mode 100644
index 0000000000..52263f8413
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/skybox/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Skybox',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'SkyboxNodeEditor',
+ jest: 'skyboxNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/skybox/index.tsx b/packages/ui/src/components/editor/properties/skybox/index.tsx
new file mode 100644
index 0000000000..20e3ecd199
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/skybox/index.tsx
@@ -0,0 +1,236 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { FiCloud } from 'react-icons/fi'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent'
+import { SkyboxComponent } from '@etherealengine/engine/src/scene/components/SkyboxComponent'
+import { SkyTypeEnum } from '@etherealengine/engine/src/scene/constants/SkyTypeEnum'
+
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import ColorInput from '../../../../primitives/tailwind/Color'
+import Slider from '../../../../primitives/tailwind/Slider'
+import FolderInput from '../../input/Folder'
+import InputGroup from '../../input/Group'
+import ImageInput from '../../input/Image'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+const hoursToRadians = (hours: number) => hours / 24
+const radiansToHours = (rads: number) => rads * 24
+
+const SkyOptions = [
+ {
+ label: 'Color',
+ value: SkyTypeEnum.color.toString()
+ },
+ {
+ label: 'Skybox',
+ value: SkyTypeEnum.skybox.toString()
+ },
+ {
+ label: 'Cubemap',
+ value: SkyTypeEnum.cubemap.toString()
+ },
+ {
+ label: 'Equirectangular',
+ value: SkyTypeEnum.equirectangular.toString()
+ }
+]
+
+/**
+ * SkyboxNodeEditor component class used to render editor view to customize component property.
+ */
+export const SkyboxNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const entity = props.entity
+ const hasError = getEntityErrors(entity, SkyboxComponent)
+ const skyboxComponent = useComponent(entity, SkyboxComponent)
+
+ const onChangeEquirectangularPathOption = (equirectangularPath) => {
+ if (equirectangularPath !== skyboxComponent.equirectangularPath.value) {
+ commitProperties(SkyboxComponent, { equirectangularPath })
+ }
+ }
+
+ const onChangeCubemapPathOption = (path: string) => {
+ const directory = path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path
+ if (directory !== skyboxComponent.cubemapPath.value) {
+ commitProperties(SkyboxComponent, { cubemapPath: directory })
+ }
+ }
+
+ const renderSkyboxSettings = () => (
+ <>
+
+ updateProperty(SkyboxComponent, 'skyboxProps.azimuth' as any)(hoursToRadians(value))}
+ onRelease={(value) => commitProperty(SkyboxComponent, 'skyboxProps.azimuth' as any)(hoursToRadians(value))}
+ unit="h"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+
+ // creating editor view for equirectangular Settings
+ const renderEquirectangularSettings = () => (
+
+
+ {hasError && {t('editor:properties.skybox.error-url')}
}
+
+ )
+
+ // creating editor view for cubemap Settings
+ const renderCubemapSettings = () => (
+
+
+ {hasError && {t('editor:properties.skybox.error-url')}
}
+
+ )
+
+ // creating editor view for color Settings
+ const renderColorSettings = () => (
+
+
+
+ )
+
+ // creating editor view for skybox Properties
+ const renderSkyBoxProps = () => {
+ switch (skyboxComponent.backgroundType.value) {
+ case SkyTypeEnum.equirectangular:
+ return renderEquirectangularSettings()
+ case SkyTypeEnum.cubemap:
+ return renderCubemapSettings()
+ case SkyTypeEnum.color:
+ return renderColorSettings()
+ default:
+ return renderSkyboxSettings()
+ }
+ }
+
+ return (
+ }
+ {...props}
+ >
+
+ commitProperty(SkyboxComponent, 'backgroundType')(parseInt(value as string, 10))}
+ inputClassName="text-xs p-2"
+ />
+
+ {renderSkyBoxProps()}
+
+ )
+}
+
+SkyboxNodeEditor.iconComponent = FiCloud
+
+export default SkyboxNodeEditor
diff --git a/packages/ui/src/components/editor/properties/spawnPoint/index.stories.tsx b/packages/ui/src/components/editor/properties/spawnPoint/index.stories.tsx
new file mode 100644
index 0000000000..864d400903
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spawnPoint/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/SpawnPoint',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'SpawnPointNodeEditor',
+ jest: 'spawnPointNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/spawnPoint/index.tsx b/packages/ui/src/components/editor/properties/spawnPoint/index.tsx
new file mode 100644
index 0000000000..761c5ab1e1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spawnPoint/index.tsx
@@ -0,0 +1,54 @@
+/*
+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 { useComponent } from '@etherealengine/ecs'
+import { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import { SpawnPointComponent } from '@etherealengine/engine/src/scene/components/SpawnPointComponent'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { RiCameraLensFill } from 'react-icons/ri'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * SpawnPointNodeEditor component used to provide the editor view to customize Spawn Point properties.
+ */
+export const SpawnPointNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const spawnComponent = useComponent(props.entity, SpawnPointComponent)
+
+ return (
+ }
+ >
+ )
+}
+
+SpawnPointNodeEditor.iconComponent = RiCameraLensFill
+
+export default SpawnPointNodeEditor
diff --git a/packages/ui/src/components/editor/properties/spline/index.stories.tsx b/packages/ui/src/components/editor/properties/spline/index.stories.tsx
new file mode 100644
index 0000000000..b5f05b7a66
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spline/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Spline',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'SplineNodeEditor',
+ jest: 'splineNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/spline/index.tsx b/packages/ui/src/components/editor/properties/spline/index.tsx
new file mode 100644
index 0000000000..7e2435d9e1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spline/index.tsx
@@ -0,0 +1,131 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { SplineComponent } from '@etherealengine/engine/src/scene/components/SplineComponent'
+
+import TimelineIcon from '@mui/icons-material/Timeline'
+
+import { useComponent } from '@etherealengine/ecs'
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { NO_PROXY } from '@etherealengine/hyperflux'
+import { BsPlusSquare } from 'react-icons/bs'
+import { MdClear } from 'react-icons/md'
+import { Quaternion, Vector3 } from 'three'
+import EulerInput from '../../input/Euler'
+import InputGroup from '../../input/Group'
+import Vector3Input from '../../input/Vector3'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * SplineNodeEditor used to create and customize splines in the scene.
+ *
+ * @param {Object} props
+ * @constructor
+ */
+
+export const SplineNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const component = useComponent(props.entity, SplineComponent)
+ const elements = component.elements
+
+ return (
+
+
+
+ {t('editor:properties.spline.lbl-addNode')}
+
+
+ {
+ const elem = { position: new Vector3(), quaternion: new Quaternion() }
+ const newElements = [...elements.get(NO_PROXY), elem]
+ commitProperty(SplineComponent, 'elements')(newElements)
+ }}
+ >
+
+
+ {elements.map(
+ (
+ elem,
+ index // need styling
+ ) => (
+
+
+
+
+ {`Node ${index + 1}`}
+
+
+ {
+ const newElements = [...elements.get(NO_PROXY)].filter((_, i) => i !== index)
+ commitProperty(SplineComponent, 'elements')(newElements)
+ }}
+ />
+
+
+
+ {
+ commitProperty(
+ SplineComponent,
+ `elements.${index}.position` as any
+ )(new Vector3(position.x, position.y, position.z))
+ }}
+ />
+
+
+ {
+ commitProperty(
+ SplineComponent,
+ `elements.${index}.quaternion` as any
+ )(new Quaternion().setFromEuler(euler))
+ }}
+ />
+
+
+
+ )
+ )}
+
+ )
+}
+
+SplineNodeEditor.iconComponent = TimelineIcon
+
+export default SplineNodeEditor
diff --git a/packages/ui/src/components/editor/properties/spline/track/index.stories.tsx b/packages/ui/src/components/editor/properties/spline/track/index.stories.tsx
new file mode 100644
index 0000000000..6e34bc7e6d
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spline/track/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Spline/Track',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'SplineTrackNodeEditor',
+ jest: 'splineTrackNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/spline/track/index.tsx b/packages/ui/src/components/editor/properties/spline/track/index.tsx
new file mode 100644
index 0000000000..1578b518df
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/spline/track/index.tsx
@@ -0,0 +1,123 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { SplineTrackComponent } from '@etherealengine/engine/src/scene/components/SplineTrackComponent'
+
+import CameraswitchIcon from '@mui/icons-material/Cameraswitch'
+
+import { UUIDComponent } from '@etherealengine/ecs'
+import { useQuery } from '@etherealengine/ecs/src/QueryFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { SplineComponent } from '@etherealengine/engine/src/scene/components/SplineComponent'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import SelectInput from '../../../input/Select'
+import { Vector3Scrubber } from '../../../input/Vector3'
+import NodeEditor from '../../nodeEditor'
+
+/**
+ * SplineTrackNodeEditor adds rotation editing to splines.
+ *
+ * @param {Object} props
+ * @constructor
+ */
+
+export const SplineTrackNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const component = useComponent(props.entity, SplineTrackComponent)
+ const velocity = component.velocity
+ const alpha = component.velocity
+
+ const availableSplines = useQuery([SplineComponent]).map((entity) => {
+ const name = getComponent(entity, NameComponent)
+ const uuid = getComponent(entity, UUIDComponent)
+ return {
+ label: name,
+ value: uuid
+ }
+ })
+
+ // @todo allow these to be passed in or remove this capability
+
+ const setAlpha = (value) => {
+ component.alpha.set(value)
+ }
+
+ return (
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+SplineTrackNodeEditor.iconComponent = CameraswitchIcon
+
+export default SplineTrackNodeEditor
diff --git a/packages/ui/src/components/editor/properties/text/box/index.stories.tsx b/packages/ui/src/components/editor/properties/text/box/index.stories.tsx
new file mode 100644
index 0000000000..49f82ac2a4
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/text/box/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Text/Box',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'TextBoxNodeEditor',
+ jest: 'textBoxNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/text/box/index.tsx b/packages/ui/src/components/editor/properties/text/box/index.tsx
new file mode 100644
index 0000000000..e9cda48efb
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/text/box/index.tsx
@@ -0,0 +1,142 @@
+/*
+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 { useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { TextComponent } from '@etherealengine/engine/src/scene/components/TextComponent'
+import StreetviewIcon from '@mui/icons-material/Streetview'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import ColorInput from '../../../../../primitives/tailwind/Color'
+import Text from '../../../../../primitives/tailwind/Text'
+import InputGroup from '../../../input/Group'
+import NumericInput from '../../../input/Numeric'
+import StringInput, { ControlledStringInput } from '../../../input/String'
+import NodeEditor from '../../nodeEditor'
+
+const PaddingNumericInput = ({
+ value,
+ onChange,
+ borderStyles
+}: {
+ value: number
+ onChange: (value: number) => void
+ borderStyles: Record
+}) => {
+ return (
+
+
+
onChange(parseFloat(event.target.value))}
+ />
+
+ {'rem'}
+
+
+ )
+}
+
+export const TextBoxEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const text = useComponent(props.entity, TextComponent)
+
+ return (
+
+
+
+
+
+
+
+
+ {}} unit="em" />
+
+
+ {}} unit="px" />
+
+
+
+
+
+ {}} unit="rem" />
+
+
+
+
+
+ {}} unit="pc" />
+
+
+
+
+
+
{}} borderStyles={{ borderTop: '1px solid white' }} />
+
+
+
{}} borderStyles={{ borderBottom: '1px solid white' }} />
+
+
+
+
+
{}} borderStyles={{ borderLeft: '1px solid white' }} />
+
+
+
{}} borderStyles={{ borderRight: '1px solid white' }} />
+
+
+
+
+
+ )
+}
+
+TextBoxEditor.iconComponent = StreetviewIcon
+
+export default TextBoxEditor
diff --git a/packages/ui/src/components/editor/properties/text/index.stories.tsx b/packages/ui/src/components/editor/properties/text/index.stories.tsx
new file mode 100644
index 0000000000..2b2fbe5f73
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/text/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Text',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'TextNodeEditor',
+ jest: 'textNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/text/index.tsx b/packages/ui/src/components/editor/properties/text/index.tsx
new file mode 100644
index 0000000000..bffaa4c96c
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/text/index.tsx
@@ -0,0 +1,511 @@
+/*
+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.
+*/
+
+/**
+ * @fileoverview
+ * Defines the {@link NodeEditor} UI for managing {@link TextComponent}s in the Studio.
+ */
+
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { PiTextT } from 'react-icons/pi'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties//Util'
+import {
+ FontMaterialKind,
+ TextComponent,
+ TroikaTextLineHeight
+} from '@etherealengine/engine/src/scene/components/TextComponent'
+import { useHookstate } from '@etherealengine/hyperflux'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import { ColorInput } from '../../../../primitives/tailwind/Color'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import SelectInput from '../../input/Select'
+import { ControlledStringInput } from '../../input/String'
+import Vector2Input from '../../input/Vector2'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * @description SelectInput option groups for the TextNodeEditor UI tsx code.
+ * @private Stored `@local` scope of the file, so it only exists once and its not GC'ed when the component is used.
+ */
+const SelectOptions = {
+ TextDirection: [
+ { label: 'Auto', value: 'auto' },
+ { label: 'Left to Right', value: 'ltr' },
+ { label: 'Right to Left', value: 'rtl' }
+ ],
+ TextAlignment: [
+ { label: 'Justify', value: 'justify' },
+ { label: 'Center', value: 'center' },
+ { label: 'Left', value: 'left' },
+ { label: 'Right', value: 'right' }
+ ],
+ TextWrapping: [
+ { label: 'Whitespace', value: 'normal' },
+ { label: 'Break Word', value: 'break-word' }
+ ],
+ FontMaterial: [
+ { label: 'Basic', value: FontMaterialKind.Basic },
+ { label: 'Standard', value: FontMaterialKind.Standard }
+ ]
+}
+
+/**
+ * @description Default fallback value for when when text.lineheight is not set to 'normal'
+ */
+const LineHeightNumericDefault = 1.2 as TroikaTextLineHeight
+
+const HoverInfo = {
+ FontFamily: `
+URL of a custom font to be used. Font files can be in .ttf, .otf, or .woff (not .woff2) formats. Defaults to Noto Sans when empty.
+Example: https://fonts.gstatic.com/s/orbitron/v9/yMJRMIlzdpvBhQQL_Qq7dys.woff
+`,
+ AdvancedGroup: `
+Toggle Advanced options. Only modify these if you know what you are doing.
+`,
+ TextOrientation: `
+Defines the axis plane on which the text should be laid out when the mesh has no extra rotation transform.
+It is specified as a string with two axes:
+ 1. the horizontal axis with positive pointing right,
+ 2. the vertical axis with positive pointing up.
+Defaults to '+x+y', meaning the text sits on the xy plane with the text's top toward positive y and facing positive z.
+A value of '+x-z' would place it on the xz plane with the text's top toward negative z and facing positive y.
+`,
+ Clipping: `
+Limit the range of pixels to draw to the given clip Rectangle.
+Directly tied to the axis selection @textOrientation.
+Useful for when text wrapping is disabled, but text should still be contained within a certain range.
+`,
+ GlyphResolution: `
+Level of quality at which font glyphs are rasterized.
+Compare values 2 and 3 to understand how this value behaves.
+A value of 4 is already acceptable quality depending on context. A value of 6 is great quality, very difficult to distinguish from 7.
+Anything above 9-10 could literally halt your computer while the text is being rendered.
+`,
+ GlyphDetail: `
+Number of subdivisions of the plane Mesh where the text is being rendered. Useful for material effects.
+`,
+ GPUAccelerated: `
+Forces the text rendering to happen on the CPU when disabled.
+Text rendering performance is significantly slower when disabled.
+Only useful for hardware that has no GPU acceleration support.
+`
+}
+
+/**
+ * TextNodeEditor component used to provide the editor a view to customize text properties.
+ */
+export const TextNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const text = useComponent(props.entity, TextComponent)
+ const advancedActive = useHookstate(false) // State tracking whether the Advanced Options Section is active or not
+
+ // LineHeight state management
+ const lineHeightIsNormal = useHookstate(true) // true when `text.lineHeight` is set to its union 'normal'
+ const lineHeight_setNormal = (checkboxValue: boolean) => {
+ // Used as a BooleanInput callback for setting the value of lineheight.
+ // Sets the value to either its 'normal' type-union option, or to a default lineHeight value when the checkbox is off.
+ lineHeightIsNormal.set(checkboxValue)
+ if (checkboxValue) text.lineHeight.set('normal' as TroikaTextLineHeight)
+ else text.lineHeight.set(LineHeightNumericDefault)
+ }
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {advancedActive.value ? (
+ /*Show Advanced Options only when Active*/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+ <>{/*advanced.inactive*/}>
+ )}
+
+ )
+}
+
+TextNodeEditor.iconComponent = PiTextT
+
+export default TextNodeEditor
diff --git a/packages/ui/src/components/editor/properties/transform/index.stories.tsx b/packages/ui/src/components/editor/properties/transform/index.stories.tsx
new file mode 100644
index 0000000000..331f0b8ff8
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/transform/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Transform',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'TransformNodeEditor',
+ jest: 'transformNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/transform/index.tsx b/packages/ui/src/components/editor/properties/transform/index.tsx
new file mode 100644
index 0000000000..7a0c69e1e1
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/transform/index.tsx
@@ -0,0 +1,155 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+import { Euler, Quaternion, Vector3 } from 'three'
+
+import {
+ getComponent,
+ hasComponent,
+ useComponent,
+ useOptionalComponent
+} from '@etherealengine/ecs/src/ComponentFunctions'
+import { SceneDynamicLoadTagComponent } from '@etherealengine/engine/src/scene/components/SceneDynamicLoadTagComponent'
+import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import ThreeDRotationIcon from '@mui/icons-material/ThreeDRotation'
+
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { ObjectGridSnapState } from '@etherealengine/editor/src/systems/ObjectGridSnapSystem'
+
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { EditorHelperState } from '@etherealengine/editor/src/services/EditorHelperState'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import { TransformSpace } from '@etherealengine/engine/src/scene/constants/transformConstants'
+import { TransformComponent } from '@etherealengine/spatial'
+import EulerInput from '../../input/Euler'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import Vector3Input from '../../input/Vector3'
+import PropertyGroup from '../group'
+
+const position = new Vector3()
+const rotation = new Quaternion()
+const scale = new Vector3()
+
+/**
+ * TransformPropertyGroup component is used to render editor view to customize properties.
+ */
+export const TransformPropertyGroup: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ useOptionalComponent(props.entity, SceneDynamicLoadTagComponent)
+ const transformComponent = useComponent(props.entity, TransformComponent)
+ const transformSpace = useHookstate(getMutableState(EditorHelperState).transformSpace)
+
+ transformSpace.value === TransformSpace.world
+ ? transformComponent.matrixWorld.value.decompose(position, rotation, scale)
+ : transformComponent.matrix.value.decompose(position, rotation, scale)
+
+ scale.copy(transformComponent.scale.value)
+
+ const onRelease = () => {
+ const bboxSnapState = getMutableState(ObjectGridSnapState)
+ if (bboxSnapState.enabled.value) {
+ bboxSnapState.apply.set(true)
+ } else {
+ EditorControlFunctions.commitTransformSave([props.entity])
+ }
+ }
+
+ const onChangeDynamicLoad = (value) => {
+ const selectedEntities = SelectionState.getSelectedEntities()
+ EditorControlFunctions.addOrRemoveComponent(selectedEntities, SceneDynamicLoadTagComponent, value)
+ }
+
+ const onChangePosition = (value: Vector3) => {
+ const selectedEntities = SelectionState.getSelectedEntities()
+ EditorControlFunctions.positionObject(selectedEntities, [value])
+ }
+
+ const onChangeRotation = (value: Euler) => {
+ const selectedEntities = SelectionState.getSelectedEntities()
+ EditorControlFunctions.rotateObject(selectedEntities, [value])
+ }
+
+ const onChangeScale = (value: Vector3) => {
+ const selectedEntities = SelectionState.getSelectedEntities()
+ EditorControlFunctions.scaleObject(selectedEntities, [value], true)
+ }
+
+ return (
+
+
+
+ {hasComponent(props.entity, SceneDynamicLoadTagComponent) && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+TransformPropertyGroup.iconComponent = ThreeDRotationIcon
+
+export default TransformPropertyGroup
diff --git a/packages/ui/src/components/editor/properties/trigger/index.stories.tsx b/packages/ui/src/components/editor/properties/trigger/index.stories.tsx
new file mode 100644
index 0000000000..149933d17a
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/trigger/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/TriggerProperties',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'TriggerPropertiesNodeEditor',
+ jest: 'TriggerPropertiesNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/trigger/index.tsx b/packages/ui/src/components/editor/properties/trigger/index.tsx
new file mode 100644
index 0000000000..e33a992a59
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/trigger/index.tsx
@@ -0,0 +1,171 @@
+/*
+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 { UUIDComponent, defineQuery, getComponent, hasComponent, useComponent } from '@etherealengine/ecs'
+import {
+ EditorComponentType,
+ commitProperties,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { useHookstate } from '@etherealengine/hyperflux'
+import { CallbackComponent } from '@etherealengine/spatial/src/common/CallbackComponent'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+import { TriggerComponent } from '@etherealengine/spatial/src/physics/components/TriggerComponent'
+import { EntityTreeComponent } from '@etherealengine/spatial/src/transform/components/EntityTree'
+import React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiPlus, HiTrash } from 'react-icons/hi2'
+import Button from '../../../../primitives/tailwind/Button'
+import { SelectOptionsType } from '../../../../primitives/tailwind/Select'
+import InputGroup from '../../input/Group'
+import SelectInput from '../../input/Select'
+import StringInput from '../../input/String'
+import NodeEditor from '../nodeEditor'
+
+const callbackQuery = defineQuery([CallbackComponent])
+
+type TargetOptionType = { label: string; value: string; callbacks: SelectOptionsType[] }
+
+const TriggerProperties: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+ const targets = useHookstate([{ label: 'Self', value: 'Self', callbacks: [] }])
+
+ const triggerComponent = useComponent(props.entity, TriggerComponent)
+
+ useEffect(() => {
+ const options = [] as TargetOptionType[]
+ options.push({
+ label: 'Self',
+ value: 'Self',
+ callbacks: []
+ })
+ for (const entity of callbackQuery()) {
+ if (entity === props.entity || !hasComponent(entity, EntityTreeComponent)) continue
+ const callbacks = getComponent(entity, CallbackComponent)
+ options.push({
+ label: getComponent(entity, NameComponent),
+ value: getComponent(entity, UUIDComponent),
+ callbacks: Object.keys(callbacks).map((cb) => ({ label: cb, value: cb }))
+ })
+ }
+ targets.set(options)
+ }, [])
+
+ return (
+
+
+ }
+ className="text-sm text-[#8B8B8D]"
+ onClick={() => {
+ const triggers = [
+ ...triggerComponent.triggers.value,
+ {
+ target: 'Self',
+ onEnter: '',
+ onExit: ''
+ }
+ ]
+ commitProperties(TriggerComponent, { triggers: JSON.parse(JSON.stringify(triggers)) }, [props.entity])
+ }}
+ />
+
+ {triggerComponent.triggers.map((trigger, index) => {
+ const targetOption = targets.value.find((o) => o.value === trigger.target.value)
+ const target = targetOption ? targetOption.value : 'Self'
+ console.log('debug1 ', targetOption, 'and', targetOption?.callbacks.length)
+ return (
+
+ }
+ className="ml-auto text-sm text-[#8B8B8D]"
+ onClick={() => {
+ const triggers = [...triggerComponent.triggers.value]
+ triggers.splice(index, 1)
+ commitProperties(TriggerComponent, { triggers: JSON.parse(JSON.stringify(triggers)) }, [props.entity])
+ }}
+ />
+
+ ({ label, value }))}
+ disabled={props.multiEdit}
+ />
+
+
+ {targetOption?.callbacks.length ? (
+
+ ) : (
+
+ )}
+
+
+
+ {targetOption?.callbacks.length ? (
+
+ ) : (
+
+ )}
+
+
+ )
+ })}
+
+ )
+}
+
+export default TriggerProperties
diff --git a/packages/ui/src/components/editor/properties/video/index.stories.tsx b/packages/ui/src/components/editor/properties/video/index.stories.tsx
new file mode 100644
index 0000000000..62f0d94329
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/video/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Video',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'VideoNodeEditor',
+ jest: 'videoNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/video/index.tsx b/packages/ui/src/components/editor/properties/video/index.tsx
new file mode 100644
index 0000000000..1695ac1fb6
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/video/index.tsx
@@ -0,0 +1,139 @@
+/*
+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 React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import { HiOutlineVideoCamera } from 'react-icons/hi2'
+
+import { EntityUUID, UUIDComponent } from '@etherealengine/ecs'
+import { getComponent, hasComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+import { MediaComponent } from '@etherealengine/engine/src/scene/components/MediaComponent'
+import { VideoComponent } from '@etherealengine/engine/src/scene/components/VideoComponent'
+import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
+
+import { useQuery } from '@etherealengine/ecs/src/QueryFunctions'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
+import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices'
+import InputGroup from '../../input/Group'
+import ProgressBar from '../../input/Progress'
+import SelectInput from '../../input/Select'
+import Vector2Input from '../../input/Vector2'
+import NodeEditor from '../nodeEditor'
+
+const fitOptions = [
+ { label: 'Cover', value: 'cover' },
+ { label: 'Contain', value: 'contain' },
+ { label: 'Vertical', value: 'vertical' },
+ { label: 'Horizontal', value: 'horizontal' }
+]
+
+const projectionOptions = [
+ { label: 'Flat', value: 'Flat' },
+ { label: 'Equirectangular360', value: 'Equirectangular360' }
+]
+
+/**
+ * VideoNodeEditor used to render editor view for property customization.
+ */
+export const VideoNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const video = useComponent(props.entity, VideoComponent)
+
+ const mediaEntities = useQuery([MediaComponent])
+
+ const mediaOptions = mediaEntities
+ .filter((entity) => entity !== props.entity)
+ .map((entity) => {
+ return { label: getComponent(entity, NameComponent), value: getComponent(entity, UUIDComponent) }
+ })
+ mediaOptions.unshift({ label: 'Self', value: '' as EntityUUID })
+
+ useEffect(() => {
+ if (!hasComponent(props.entity, MediaComponent)) {
+ const nodes = SelectionState.getSelectedEntities()
+ EditorControlFunctions.addOrRemoveComponent(nodes, MediaComponent, true)
+ }
+ }, [])
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+VideoNodeEditor.iconComponent = HiOutlineVideoCamera
+
+export default VideoNodeEditor
diff --git a/packages/ui/src/components/editor/properties/visualScript/index.stories.tsx b/packages/ui/src/components/editor/properties/visualScript/index.stories.tsx
new file mode 100644
index 0000000000..353d93b086
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/visualScript/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/VisualScript',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'VisualScriptNodeEditor',
+ jest: 'visualScriptNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/visualScript/index.tsx b/packages/ui/src/components/editor/properties/visualScript/index.tsx
new file mode 100644
index 0000000000..714753be0e
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/visualScript/index.tsx
@@ -0,0 +1,68 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions'
+
+import IntegrationInstructionsIcon from '@mui/icons-material/IntegrationInstructions'
+
+import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util'
+import { VisualScriptComponent } from '@etherealengine/engine'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import InputGroup from '../../input/Group'
+import { NodeEditor } from '../nodeEditor'
+
+/**
+ *
+ * AmbientLightNodeEditor component used to customize the ambient light element on the scene
+ * ambient light is basically used to illuminates all the objects present inside the scene.
+ *
+ * @type {[component class]}
+ */
+export const VisualScriptNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const visualScriptComponent = useComponent(props.entity, VisualScriptComponent)
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+VisualScriptNodeEditor.iconComponent = IntegrationInstructionsIcon
+
+export default VisualScriptNodeEditor
diff --git a/packages/ui/src/components/editor/properties/volumetric/index.stories.tsx b/packages/ui/src/components/editor/properties/volumetric/index.stories.tsx
new file mode 100644
index 0000000000..ac94545e61
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/volumetric/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/Volumetric',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'VolumetricNodeEditor',
+ jest: '',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/volumetric/index.tsx b/packages/ui/src/components/editor/properties/volumetric/index.tsx
new file mode 100644
index 0000000000..8e2d6986de
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/volumetric/index.tsx
@@ -0,0 +1,385 @@
+/*
+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 React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import {
+ getOptionalMutableComponent,
+ hasComponent,
+ useComponent,
+ useOptionalComponent
+} from '@etherealengine/ecs/src/ComponentFunctions'
+import { VolumetricComponent } from '@etherealengine/engine/src/scene/components/VolumetricComponent'
+import { PlayMode } from '@etherealengine/engine/src/scene/constants/PlayMode'
+
+import { ECSState } from '@etherealengine/ecs/src/ECSState'
+import { Entity } from '@etherealengine/ecs/src/Entity'
+import CompoundNumericInput from '@etherealengine/editor/src/components/inputs/CompoundNumericInput'
+import {
+ EditorComponentType,
+ commitProperty,
+ updateProperty
+} from '@etherealengine/editor/src/components/properties/Util'
+import { UVOL1Component } from '@etherealengine/engine/src/scene/components/UVOL1Component'
+import { UVOL2Component } from '@etherealengine/engine/src/scene/components/UVOL2Component'
+import { TextureType } from '@etherealengine/engine/src/scene/constants/UVOLTypes'
+import { getState } from '@etherealengine/hyperflux/functions/StateFunctions'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import VideocamIcon from '@mui/icons-material/Videocam'
+import { Button } from '@mui/material'
+import { Scrubber } from 'react-scrubber'
+import 'react-scrubber/lib/scrubber.css'
+import ArrayInputGroup from '../../input/Array'
+import InputGroup from '../../input/Group'
+import SelectInput from '../../input/Select'
+import NodeEditor from '../nodeEditor'
+
+const PlayModeOptions = [
+ {
+ label: 'Single',
+ value: PlayMode.single
+ },
+ {
+ label: 'Random',
+ value: PlayMode.random
+ },
+ {
+ label: 'Loop',
+ value: PlayMode.loop
+ },
+ {
+ label: 'SingleLoop',
+ value: PlayMode.singleloop
+ }
+]
+
+type TextureTargetLabelsType = {
+ [key in TextureType]: {
+ label: string
+ value: number
+ }[]
+}
+
+/**
+ * VolumetricNodeEditor provides the editor view to customize properties.
+ *
+ * @param {any} props
+ * @constructor
+ */
+export const VolumetricNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ const volumetricComponent = useComponent(props.entity, VolumetricComponent)
+
+ const toggle = () => {
+ volumetricComponent.paused.set(!volumetricComponent.paused.value)
+ }
+
+ const [trackLabels, setTrackLabels] = React.useState(
+ [] as {
+ label: string
+ value: number
+ }[]
+ )
+
+ useEffect(() => {
+ const tracks = volumetricComponent.paths.value
+ if (tracks.length === 0) {
+ return
+ }
+ if (tracks.length === 1) {
+ const segments = tracks[0].split('/')
+ setTrackLabels([
+ {
+ label: segments[segments.length - 1],
+ value: 0
+ }
+ ])
+ console.log('Setting labels: ', [
+ {
+ label: segments[segments.length - 1],
+ value: 0
+ }
+ ])
+ return
+ }
+
+ let prefix = tracks[0]
+
+ // Don't show the longest common prefix
+ for (let j = 1; j < tracks.length; j++) {
+ while (tracks[j].indexOf(prefix) !== 0) {
+ prefix = prefix.substring(0, prefix.length - 1)
+ }
+ }
+ const _trackLabels = [] as {
+ label: string
+ value: number
+ }[]
+
+ for (let i = 0; i < tracks.length; i++) {
+ _trackLabels.push({
+ label: tracks[i].slice(prefix.length),
+ value: i
+ })
+ }
+ setTrackLabels(_trackLabels)
+ console.log('Setting labels: ', _trackLabels)
+ }, [volumetricComponent.paths])
+
+ const uvol2 = useOptionalComponent(props.entity, UVOL2Component)
+ const [geometryTargets, setGeometryTargets] = React.useState(
+ [] as {
+ label: string
+ value: number
+ }[]
+ )
+
+ useEffect(() => {
+ if (uvol2) {
+ const _geometryTargets = [] as {
+ label: string
+ value: number
+ }[]
+ _geometryTargets.push({
+ label: 'auto',
+ value: -1
+ })
+ uvol2.geometryInfo.targets.value.forEach((target, index) => {
+ _geometryTargets.push({
+ label: target,
+ value: index
+ })
+ })
+ setGeometryTargets(_geometryTargets)
+ }
+ }, [uvol2?.geometryInfo.targets])
+
+ const [textureTargets, setTextureTargets] = React.useState({} as TextureTargetLabelsType)
+ useEffect(() => {
+ if (!uvol2) {
+ return
+ }
+ const textureTypes = uvol2.textureInfo.textureTypes.value
+ const _textureTargets = {} as TextureTargetLabelsType
+ textureTypes.forEach((textureType) => {
+ _textureTargets[textureType] = [] as {
+ label: string
+ value: number
+ }[]
+ _textureTargets[textureType].push({
+ label: 'auto',
+ value: -1
+ })
+ uvol2.textureInfo[textureType].targets.value.forEach((target, index) => {
+ _textureTargets[textureType].push({
+ label: target,
+ value: index
+ })
+ })
+ })
+ setTextureTargets(_textureTargets)
+ }, [
+ uvol2?.textureInfo.textureTypes,
+ uvol2?.textureInfo.baseColor.targets,
+ uvol2?.textureInfo.normal.targets,
+ uvol2?.textureInfo.emissive.targets,
+ uvol2?.textureInfo.metallicRoughness.targets,
+ uvol2?.textureInfo.occlusion.targets
+ ])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {(hasComponent(props.entity, UVOL2Component) || hasComponent(props.entity, UVOL1Component)) && (
+
+ )}
+
+
+ {
+ volumetricComponent.currentTrackInfo.playbackRate.set(value)
+ }}
+ />
+
+
+
+
+ {volumetricComponent.paths && volumetricComponent.paths.length > 0 && volumetricComponent.paths[0] && (
+
+ {volumetricComponent.paused.value
+ ? t('editor:properties.media.playtitle')
+ : t('editor:properties.media.pausetitle')}
+
+ )}
+
+
+ {hasComponent(props.entity, UVOL2Component) && (
+ <>
+
+ {
+ if (uvol2) {
+ uvol2.geometryInfo.userTarget.set(value)
+ }
+ }}
+ />
+
+ {Object.keys(textureTargets).map((textureType, index) => {
+ return (
+
+ {
+ if (uvol2) {
+ uvol2?.textureInfo[textureType].userTarget.set(value)
+ }
+ }}
+ />
+
+ )
+ })}
+ >
+ )}
+
+
+ {
+ volumetricComponent.track.set(value)
+ }}
+ />
+
+
+ )
+}
+
+function VolumetricCurrentTimeScrubber(props: { entity: Entity }) {
+ const { t } = useTranslation()
+ const volumetricComponent = useComponent(props.entity, VolumetricComponent)
+ const uvol2Component = useOptionalComponent(props.entity, UVOL2Component)
+
+ const [isChanging, setIsChanging] = React.useState(false)
+
+ return (
+
+ {
+ setIsChanging(true)
+ }}
+ onScrubEnd={(value) => {
+ if (!isChanging) return
+ const uvol2Component = getOptionalMutableComponent(props.entity, UVOL2Component)
+ if (
+ uvol2Component &&
+ volumetricComponent.currentTrackInfo.currentTime.value < value &&
+ value < uvol2Component.bufferedUntil.value
+ ) {
+ const engineState = getState(ECSState)
+ UVOL2Component.setStartAndPlaybackTime(props.entity, value, engineState.elapsedSeconds)
+ }
+ setIsChanging(false)
+ }}
+ onScrubChange={() => {}}
+ tooltip={{
+ enabledOnHover: true
+ }}
+ {...(hasComponent(props.entity, UVOL2Component)
+ ? {
+ bufferPosition: uvol2Component?.bufferedUntil.value
+ }
+ : {})}
+ />
+
+ )
+}
+
+//setting iconComponent with icon name
+VolumetricNodeEditor.iconComponent = VideocamIcon
+
+export default VolumetricNodeEditor
diff --git a/packages/ui/src/components/editor/properties/xruiPlayback/index.stories.tsx b/packages/ui/src/components/editor/properties/xruiPlayback/index.stories.tsx
new file mode 100644
index 0000000000..a1ae1489b2
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/xruiPlayback/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Properties/XRUIPlayback',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'XRUIPlaybackNodeEditor',
+ jest: 'xruiPlaybackNodeEditor.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+export const Default = { args: {} }
diff --git a/packages/ui/src/components/editor/properties/xruiPlayback/index.tsx b/packages/ui/src/components/editor/properties/xruiPlayback/index.tsx
new file mode 100644
index 0000000000..b414e64480
--- /dev/null
+++ b/packages/ui/src/components/editor/properties/xruiPlayback/index.tsx
@@ -0,0 +1,75 @@
+/*
+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 { EditorComponentType } from '@etherealengine/editor/src/components/properties/Util'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { LuPlayCircle } from 'react-icons/lu'
+import InputGroup from '../../input/Group'
+import NumericInput from '../../input/Numeric'
+import NodeEditor from '../nodeEditor'
+
+/**
+ * SpawnPointNodeEditor component used to provide the editor view to customize Spawn Point properties.
+ *
+ * @type {Class component}
+ */
+export const XRUIPlaybackNodeEditor: EditorComponentType = (props) => {
+ const { t } = useTranslation()
+
+ //const spawnComponent = useComponent(props.entity, SpawnPointComponent)
+
+ return (
+ }
+ {...props}
+ >
+
+ {}}
+ //onChange={updateProperty(, '')}
+ //onRelease={commitProperty(, '')}
+ unit="px"
+ />
+
+
+ )
+}
+
+XRUIPlaybackNodeEditor.iconComponent = LuPlayCircle
+
+export default XRUIPlaybackNodeEditor
diff --git a/packages/ui/src/components/editor/settings/import/index.tsx b/packages/ui/src/components/editor/settings/import/index.tsx
new file mode 100644
index 0000000000..ff0028d7f4
--- /dev/null
+++ b/packages/ui/src/components/editor/settings/import/index.tsx
@@ -0,0 +1,336 @@
+/*
+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 { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
+import { ImportSettingsState } from '@etherealengine/editor/src/components/assets/ImportSettingsPanel'
+import { LODList, LODVariantDescriptor } from '@etherealengine/editor/src/constants/GLTFPresets'
+import { NO_PROXY, getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { BooleanInput } from '@etherealengine/ui/src/components/editor/input/Boolean'
+import { t } from 'i18next'
+import React, { useEffect, useState } from 'react'
+import Modal from '../../../../primitives/tailwind/Modal'
+import Slider from '../../../../primitives/tailwind/Slider'
+import InputGroup from '../../input/Group'
+import SelectInput from '../../input/Select'
+import StringInput from '../../input/String'
+
+const UASTCFlagOptions = [
+ { label: 'Fastest', value: 0 },
+ { label: 'Faster', value: 1 },
+ { label: 'Default', value: 2 },
+ { label: 'Slower', value: 3 },
+ { label: 'Very Slow', value: 4 },
+ { label: 'Mask', value: 0xf },
+ { label: 'UASTC Error', value: 8 },
+ { label: 'BC7 Error', value: 16 },
+ { label: 'Faster Hints', value: 64 },
+ { label: 'Fastest Hints', value: 128 },
+ { label: 'Disable Flip and Individual', value: 256 }
+]
+
+const ImageCompressionBox = ({ compressProperties }) => {
+ return (
+ <>
+ {/*
+ {t('editor:properties.model.transform.compress') as string}
+ */}
+
+ compressProperties.mode.set(val)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {compressProperties.mode.value === 'ETC1S' && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ {compressProperties.mode.value === 'UASTC' && (
+ <>
+
+ compressProperties.uastcFlags.set(val)}
+ />
+
+
+
+
+ >
+ )}
+ >
+ )
+}
+
+export function ImportSettingsPanel() {
+ const importSettingsState = useHookstate(getMutableState(ImportSettingsState))
+ const compressProperties = useHookstate(getMutableState(ImportSettingsState).imageSettings.get(NO_PROXY))
+
+ const [defaultImportFolder, setDefaultImportFolder] = useState(importSettingsState.importFolder.value)
+ const [LODImportFolder, setLODImportFolder] = useState(importSettingsState.LODFolder.value)
+ const [LODGenEnabled, setLODGenEnabled] = useState(importSettingsState.LODsEnabled.value)
+ const [selectedLODS, setSelectedLods] = useState(
+ importSettingsState.selectedLODS.get(NO_PROXY) as LODVariantDescriptor[]
+ )
+ const [currentLOD, setCurrentLOD] = useState(
+ importSettingsState.selectedLODS[0].get(NO_PROXY) as LODVariantDescriptor
+ )
+ const [currentIndex, setCurrentIndex] = useState(0)
+ const [KTXEnabled, setKTXEnabled] = useState(importSettingsState.imageCompression.value)
+
+ const presetLabels = ['LOD0', 'LOD1', 'LOD2']
+
+ useEffect(() => {
+ handleLODChange()
+ }, [currentLOD, currentIndex])
+
+ const handleLODChange = () => {
+ const newLODS = [...selectedLODS]
+ newLODS.splice(currentIndex, 1, currentLOD)
+ setSelectedLods(newLODS)
+ }
+
+ const handleSaveChanges = () => {
+ importSettingsState.importFolder.set(defaultImportFolder)
+ importSettingsState.LODFolder.set(LODImportFolder)
+ importSettingsState.LODsEnabled.set(LODGenEnabled)
+ importSettingsState.imageCompression.set(KTXEnabled)
+ importSettingsState.imageSettings.set(compressProperties.get(NO_PROXY))
+ importSettingsState.selectedLODS.set(selectedLODS)
+ handleCancel()
+ }
+
+ const handleCancel = () => {
+ PopoverState.hidePopupover()
+ }
+
+ return (
+ handleSaveChanges()}
+ onClose={() => handleCancel()}
+ className="w-[50vw] max-w-2xl"
+ >
+
+
+
Default Import Folder
+
setDefaultImportFolder(val)} />
+
+
+
glTF / glB
+
+ {LODGenEnabled && (
+
+
LODs Folder
+
setLODImportFolder(val)} />
+ LODs to Generate
+ {selectedLODS.slice(0, 3).map((LOD, idx) => (
+
+ ({
+ label: sLOD.params.dst,
+ value: sLOD.params.dst
+ }))}
+ value={LOD.params.dst}
+ onChange={(val) => {
+ setCurrentLOD(LODList.find((sLOD) => sLOD.params.dst === val) ?? LODList[0])
+ setCurrentIndex(idx)
+ }}
+ />
+
+ ))}
+
+ )}
+
+
+
Images
+
Compression Settings
+
+
+ {
+ setKTXEnabled(val)
+ }}
+ />
+
+ {KTXEnabled && }
+
+
+
+
+ )
+}
+
+export default ImportSettingsPanel
+
+/**/
diff --git a/packages/ui/src/components/editor/toolbar/index.stories.tsx b/packages/ui/src/components/editor/toolbar/index.stories.tsx
new file mode 100644
index 0000000000..e2a7cfa9c5
--- /dev/null
+++ b/packages/ui/src/components/editor/toolbar/index.stories.tsx
@@ -0,0 +1,51 @@
+/*
+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 Component from './index'
+
+const argTypes = {}
+
+export default {
+ title: 'Editor/Toolbar',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'Toolbar',
+ jest: 'Toolbar.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ },
+ argTypes
+}
+
+export const Default = {
+ args: {
+ label: 'Source Path',
+ containerClassName: 'w-96',
+ values: ['test name 1', 'test value 2', 'test 3', 'test 4'],
+ inputLabel: 'Path'
+ }
+}
diff --git a/packages/ui/src/components/editor/toolbar/index.tsx b/packages/ui/src/components/editor/toolbar/index.tsx
new file mode 100755
index 0000000000..7c7f8089c3
--- /dev/null
+++ b/packages/ui/src/components/editor/toolbar/index.tsx
@@ -0,0 +1,51 @@
+/*
+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 React from 'react'
+
+import MenuIcon from '@mui/icons-material/Menu'
+import WindowIcon from '@mui/icons-material/Window'
+
+import DropDownMenu from '@etherealengine/editor/src/components/dropDownMenu'
+import { EditorNavbarProfile } from '@etherealengine/editor/src/components/projects/EditorNavbarProfile'
+import PublishLocation from '@etherealengine/editor/src/components/toolbar/tools//PublishLocation'
+
+type ToolBarProps = {
+ menu?: any
+ panels?: any
+}
+
+export const ToolBar = (props: ToolBarProps) => {
+ return (
+
+ )
+}
+
+export default ToolBar
diff --git a/packages/ui/src/components/editor/toolbar/mainMenu/index.tsx b/packages/ui/src/components/editor/toolbar/mainMenu/index.tsx
new file mode 100644
index 0000000000..c8a82a3ac7
--- /dev/null
+++ b/packages/ui/src/components/editor/toolbar/mainMenu/index.tsx
@@ -0,0 +1,97 @@
+/*
+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 React from 'react'
+
+import MenuItem from '@mui/material/MenuItem'
+import Button from '../../../../primitives/tailwind/Button'
+import ContextMenu from '../../layout/ContextMenu'
+
+interface Command {
+ name: string
+ action: () => void
+ hotkey?: string
+}
+
+interface MainMenuProp {
+ commands: Command[]
+ icon: any
+}
+
+const MainMenu = ({ commands, icon }: MainMenuProp) => {
+ const [anchorPosition, setAnchorPosition] = React.useState({ left: 0, top: 0 })
+ const [anchorEl, setAnchorEl] = React.useState(null)
+ const open = Boolean(anchorEl)
+
+ const onOpen = (event: React.MouseEvent) => {
+ event.preventDefault()
+ event.stopPropagation()
+
+ setAnchorEl(event.currentTarget)
+ setAnchorPosition({
+ left: 0,
+ top: event.currentTarget.offsetHeight + 6
+ })
+ }
+
+ const handleClose = () => {
+ setAnchorEl(null)
+ setAnchorPosition({ left: 0, top: 0 })
+ }
+
+ const renderMenu = (command: Command) => {
+ const menuItem = (
+
+ )
+ return menuItem
+ }
+
+ return (
+ <>
+
+
+ {commands.map((command: Command) => renderMenu(command))}
+
+ >
+ )
+}
+
+export default MainMenu
diff --git a/packages/ui/src/components/editor/toolbar/mainMenu/saveAsScene/index.tsx b/packages/ui/src/components/editor/toolbar/mainMenu/saveAsScene/index.tsx
new file mode 100644
index 0000000000..ab1eefcd97
--- /dev/null
+++ b/packages/ui/src/components/editor/toolbar/mainMenu/saveAsScene/index.tsx
@@ -0,0 +1,63 @@
+/*
+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 { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
+import { useHookstate } from '@etherealengine/hyperflux'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import Input from '../../../../../primitives/tailwind/Input'
+import Modal from '../../../../../primitives/tailwind/Modal'
+
+/**
+ * SaveNewSceneDialog used to show dialog when to save new scene.
+ */
+export function SaveNewSceneDialog({
+ initialName,
+ onConfirm,
+ onCancel
+}: {
+ initialName: string
+ onConfirm: (value: { name: string }) => void
+ onCancel: () => void
+}) {
+ const name = useHookstate(initialName)
+ const { t } = useTranslation()
+
+ return (
+ onConfirm({ name: name.value })}
+ onClose={() => {
+ onCancel()
+ PopoverState.hidePopupover()
+ }}
+ >
+ name.set(event.target.value)} />
+
+ )
+}
+
+export default SaveNewSceneDialog
diff --git a/packages/ui/src/components/editor/toolbar/mainMenu/saveScene/index.tsx b/packages/ui/src/components/editor/toolbar/mainMenu/saveScene/index.tsx
new file mode 100644
index 0000000000..53cf5ddb2b
--- /dev/null
+++ b/packages/ui/src/components/editor/toolbar/mainMenu/saveScene/index.tsx
@@ -0,0 +1,52 @@
+/*
+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 React from 'react'
+import { useTranslation } from 'react-i18next'
+
+import ConfirmDialog from '../../../../tailwind/ConfirmDialog'
+
+/**
+ * SaveSceneDialog used to show dialog when to save scene.
+ */
+export function SaveSceneDialog({
+ onConfirm,
+ onCancel
+}: {
+ onConfirm: (val: boolean) => void
+ onCancel: (val?: boolean) => void
+}) {
+ const { t } = useTranslation()
+
+ return (
+ onConfirm(true)}
+ onClose={onCancel}
+ />
+ )
+}
+
+export default SaveSceneDialog
diff --git a/packages/ui/src/components/editor/util/PopoverContext.tsx b/packages/ui/src/components/editor/util/PopoverContext.tsx
new file mode 100644
index 0000000000..1a5d1c55fa
--- /dev/null
+++ b/packages/ui/src/components/editor/util/PopoverContext.tsx
@@ -0,0 +1,30 @@
+/*
+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 { createContext, useContext } from 'react'
+
+export const PopoverContext = createContext({ handlePopoverClose: () => {} })
+
+export const usePopoverContextClose = () => useContext(PopoverContext).handlePopoverClose
diff --git a/packages/ui/src/components/tailwind/ConfirmDialog/index.tsx b/packages/ui/src/components/tailwind/ConfirmDialog/index.tsx
index c2857d057e..1ba5a292a1 100644
--- a/packages/ui/src/components/tailwind/ConfirmDialog/index.tsx
+++ b/packages/ui/src/components/tailwind/ConfirmDialog/index.tsx
@@ -33,11 +33,12 @@ import Text from '../../../primitives/tailwind/Text'
interface ConfirmDialogProps {
text: string
- onSubmit: () => Promise | void
+ onSubmit: () => Promise
+ onClose?: () => void
modalProps?: Partial
}
-export const ConfirmDialog = ({ text, onSubmit, modalProps }: ConfirmDialogProps) => {
+export const ConfirmDialog = ({ text, onSubmit, onClose, modalProps }: ConfirmDialogProps) => {
const errorText = useHookstate('')
const modalProcessing = useHookstate(false)
@@ -56,7 +57,10 @@ export const ConfirmDialog = ({ text, onSubmit, modalProps }: ConfirmDialogProps
{
+ PopoverState.hidePopupover()
+ onClose?.()
+ }}
className="w-[50vw] max-w-2xl"
submitLoading={modalProcessing.value}
{...modalProps}
diff --git a/packages/ui/src/components/tailwind/ErrorDialog/index.tsx b/packages/ui/src/components/tailwind/ErrorDialog/index.tsx
new file mode 100644
index 0000000000..c6c2a9b145
--- /dev/null
+++ b/packages/ui/src/components/tailwind/ErrorDialog/index.tsx
@@ -0,0 +1,52 @@
+/*
+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 { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
+import { t } from 'i18next'
+import React from 'react'
+import ErrorView from '../../../primitives/tailwind/ErrorView'
+import Modal, { ModalProps } from '../../../primitives/tailwind/Modal'
+
+interface ErrorDialogProps {
+ title: string
+ description?: string
+ modalProps?: ModalProps
+}
+
+const ErrorDialog = ({ title, description, modalProps }: ErrorDialogProps) => {
+ return (
+ PopoverState.hidePopupover()}
+ className="w-[50vw] max-w-2xl"
+ {...modalProps}
+ >
+
+
+ )
+}
+
+export default ErrorDialog
diff --git a/packages/ui/src/fonts/font.css b/packages/ui/src/fonts/font.css
new file mode 100644
index 0000000000..bdc9442616
--- /dev/null
+++ b/packages/ui/src/fonts/font.css
@@ -0,0 +1 @@
+@import url('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap')
\ No newline at end of file
diff --git a/packages/ui/src/primitives/tailwind/Checkbox/index.tsx b/packages/ui/src/primitives/tailwind/Checkbox/index.tsx
index f74e0ba9a7..5e03ea1a6d 100644
--- a/packages/ui/src/primitives/tailwind/Checkbox/index.tsx
+++ b/packages/ui/src/primitives/tailwind/Checkbox/index.tsx
@@ -28,35 +28,45 @@ import { twMerge } from 'tailwind-merge'
import Label from '../Label'
-export interface CheckboxProps {
+export interface CheckboxProps extends Omit, 'value' | 'onChange'> {
value: boolean
label?: string
className?: string
+ containerClassName?: string
onChange: (value: boolean) => void
disabled?: boolean
}
-const Checkbox = ({ className, label, value, onChange, disabled }: CheckboxProps) => {
+const Checkbox = ({ className, containerClassName, label, value, onChange, disabled, ...rest }: CheckboxProps) => {
const twClassName = twMerge(
- 'h-4 w-4 rounded',
- 'border-gray-300 bg-gray-100 text-blue-400 focus:ring-2 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600',
+ 'disabled:border-steel-400 disabled:bg-steel-400 peer',
+ 'relative h-4 w-4 shrink-0 appearance-none rounded-sm',
+ 'border-2 border-blue-500 bg-white',
+ 'checked:border-0 checked:bg-blue-800 focus:outline-none focus:ring-2',
+ 'focus:ring-blue-500 focus:ring-offset-0 dark:focus:ring-blue-600',
className
)
return (
-
-
onChange(!value)}
- disabled={disabled}
- />
+
+
onChange(e.target.value as any)} className={twClassName} {...rest} />
{label && (
)}
+
)
}
diff --git a/packages/ui/src/primitives/tailwind/Color/index.stories.tsx b/packages/ui/src/primitives/tailwind/Color/index.stories.tsx
new file mode 100644
index 0000000000..1628ef52f9
--- /dev/null
+++ b/packages/ui/src/primitives/tailwind/Color/index.stories.tsx
@@ -0,0 +1,42 @@
+/*
+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 Color from './index'
+
+export default {
+ title: 'Primitives/Tailwind/Color',
+ component: Color,
+ parameters: {
+ componentSubtitle: 'Color',
+ jest: 'Color.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ }
+}
+
+export const Default = {
+ args: {}
+}
diff --git a/packages/ui/src/primitives/tailwind/Color/index.tsx b/packages/ui/src/primitives/tailwind/Color/index.tsx
new file mode 100644
index 0000000000..798ef955c8
--- /dev/null
+++ b/packages/ui/src/primitives/tailwind/Color/index.tsx
@@ -0,0 +1,93 @@
+/*
+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 { ColorResult } from '@uiw/color-convert'
+import SketchPicker from '@uiw/react-color-sketch'
+import React from 'react'
+import { Color } from 'three'
+
+import { twMerge } from 'tailwind-merge'
+import Text from '../Text'
+
+interface ColorInputProp {
+ value: Color
+ onChange: (color: Color) => void
+ disabled?: boolean
+ isValueAsInteger?: boolean
+ className?: string
+ textClassName?: string
+ sketchPickerClassName?: string
+}
+
+export function ColorInput({
+ value,
+ onChange,
+ disabled,
+ className,
+ textClassName,
+ sketchPickerClassName
+}: ColorInputProp) {
+ const hexColor = typeof value.getHexString === 'function' ? '#' + value.getHexString() : '#000'
+
+ const handleChange = (result: ColorResult) => {
+ const color = new Color(result.hex)
+ onChange(color)
+ }
+
+ return (
+
+
+
+
+
+ {hexColor.toUpperCase()}
+
+
+ )
+}
+
+ColorInput.defaultProps = {
+ value: new Color(),
+ onChange: () => {}
+}
+
+export default ColorInput
diff --git a/packages/ui/src/primitives/tailwind/Slider/index.stories.tsx b/packages/ui/src/primitives/tailwind/Slider/index.stories.tsx
new file mode 100644
index 0000000000..451a128d88
--- /dev/null
+++ b/packages/ui/src/primitives/tailwind/Slider/index.stories.tsx
@@ -0,0 +1,43 @@
+/*
+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 Component from './index'
+
+export default {
+ title: 'Primitives/Tailwind/Slider',
+ component: Component,
+ parameters: {
+ componentSubtitle: 'Slider',
+ jest: 'Slider.test.tsx',
+ design: {
+ type: 'figma',
+ url: ''
+ }
+ }
+}
+
+export const Default = {
+ args: {}
+}
diff --git a/packages/ui/src/primitives/tailwind/Slider/index.tsx b/packages/ui/src/primitives/tailwind/Slider/index.tsx
new file mode 100644
index 0000000000..4c3f151c71
--- /dev/null
+++ b/packages/ui/src/primitives/tailwind/Slider/index.tsx
@@ -0,0 +1,129 @@
+/*
+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 React from 'react'
+import { twMerge } from 'tailwind-merge'
+
+export interface SliderProps {
+ className?: string
+ value: number
+ min?: number
+ max?: number
+ step?: number
+ width?: number
+ onChange: (value: number) => void
+ onRelease: (value: number) => void
+}
+
+/**
+ * @param props.width width of the slider in pixels
+ */
+const Slider = ({ value, min = 0, max = 100, step = 1, width = 200, onChange, onRelease, className }: SliderProps) => {
+ const handleInputChange = (event: React.ChangeEvent
) => {
+ let newValue = parseFloat(event.target.value)
+ if (isNaN(newValue)) {
+ newValue = min
+ } else {
+ newValue = Math.min(Math.max(newValue, min), max)
+ }
+ onChange(newValue)
+ }
+
+ const handleChange = (event: React.ChangeEvent) => {
+ const newValue = parseFloat(event.target.value)
+ onChange(newValue)
+ }
+
+ return (
+
+ onRelease(value)}
+ className="h-8 w-14 rounded bg-neutral-900 text-center font-['Figtree'] text-sm font-normal leading-[21px] text-neutral-400"
+ />
+ onRelease(value)}
+ step={step}
+ type="range"
+ className={twMerge(
+ `w-[${width}px] h-8 cursor-pointer appearance-none overflow-hidden rounded bg-[#111113] from-[#214AA6] via-[#214AA6] focus:outline-none
+
+ disabled:pointer-events-none
+ disabled:opacity-50
+ [&::-moz-range-progress]:bg-[#214AA6]
+ [&::-moz-range-track]:h-full
+ [&::-moz-range-track]:w-full
+
+ [&::-moz-range-track]:rounded
+ [&::-moz-range-track]:bg-[#111113]
+ [&::-webkit-slider-runnable-track]:h-full
+ [&::-webkit-slider-runnable-track]:w-full [&::-webkit-slider-runnable-track]:rounded [&::-webkit-slider-runnable-track]:bg-gradient-to-r via-[${Math.round(
+ ((value - min) / (max - min)) * 95
+ )}%] to-[${Math.round(((value - min) / (max - min)) * 100)}%]
+ [&::-webkit-slider-thumb]:h-full
+ [&::-webkit-slider-thumb]:shadow-[-${width}px_0_0_${width}px_#214AA6]
+
+ [&::-moz-range-thumb]:h-full
+ [&::-moz-range-thumb]:w-4
+ [&::-moz-range-thumb]:appearance-none
+ [&::-moz-range-thumb]:rounded
+ [&::-moz-range-thumb]:bg-[#849ED6]
+
+ [&::-moz-range-thumb]:transition-all
+ [&::-moz-range-thumb]:duration-150
+ [&::-moz-range-thumb]:ease-in-out
+
+ [&::-webkit-slider-thumb]:w-4
+ [&::-webkit-slider-thumb]:appearance-none
+ [&::-webkit-slider-thumb]:rounded
+ [&::-webkit-slider-thumb]:bg-[#849ED6]
+
+ [&::-webkit-slider-thumb]:transition-all
+ [&::-webkit-slider-thumb]:duration-150
+ [&::-webkit-slider-thumb]:ease-in-out
+ `,
+ className
+ )}
+ />
+
+ )
+}
+
+Slider.defaultProps = {
+ min: 0,
+ max: 100,
+ step: 1,
+ value: 60
+}
+
+export default Slider
diff --git a/tailwind.config.js b/tailwind.config.js
index 3fba8fcaf1..0845945e4e 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -31,6 +31,12 @@ module.exports = {
important: true, // important in prod is must be
theme: {
extend: {
+ gradientColorStops: {
+ ...Array.from({ length: 101 }, (_, i) => i).reduce((acc, curr) => {
+ acc[curr] = `${curr}%`;
+ return acc;
+ }, {})
+ },
backgroundImage: {
'gradient-onboarding': 'linear-gradient(180deg, #0A0A0A 0%, #262626 100%)',
'text-gradient-onboarding': 'linear-gradient(275deg, #4195FB 4.98%, #4E9CFB 61.64%, #A5CDFD 97.96%)',
@@ -70,5 +76,11 @@ module.exports = {
'blue-primary': '#375DAF'
}
}
+ },
+ purge: {
+ safelist: [
+ ...Array.from({ length: 101 }, (_, i) => `via-[${i}%]`),
+ ...Array.from({ length: 101 }, (_, i) => `to-[${i}%]`)
+ ]
}
}