diff --git a/packages/editor/src/components/Editor2Container.tsx b/packages/editor/src/components/Editor2Container.tsx index c26be4677f..0436eb982a 100644 --- a/packages/editor/src/components/Editor2Container.tsx +++ b/packages/editor/src/components/Editor2Container.tsx @@ -37,6 +37,8 @@ import { MaterialsPanelTab } from '@etherealengine/ui/src/components/editor/pane import { PropertiesPanelTab } from '@etherealengine/ui/src/components/editor/panels/Properties' import { ScenePanelTab } from '@etherealengine/ui/src/components/editor/panels/Scenes' import { ViewportPanelTab } from '@etherealengine/ui/src/components/editor/panels/Viewport' +import { VisualScriptPanelTab } from '@etherealengine/ui/src/components/editor/panels/VisualScript' + import { EditorProgressBar } from '@etherealengine/ui/src/components/editor/util/EditorProgressBar' import ErrorDialog from '@etherealengine/ui/src/components/tailwind/ErrorDialog' import PopupMenu from '@etherealengine/ui/src/primitives/tailwind/PopupMenu' @@ -93,7 +95,7 @@ const defaultLayout: LayoutData = { tabs: [ViewportPanelTab] }, { - tabs: [ScenePanelTab, FilesPanelTab, AssetsPanelTab] + tabs: [ScenePanelTab, FilesPanelTab, AssetsPanelTab, VisualScriptPanelTab] } ] }, diff --git a/packages/editor/src/components/visualScript/VisualFlow.tsx b/packages/editor/src/components/visualScript/VisualFlow.tsx index 17762f6919..ffceaf5d65 100644 --- a/packages/editor/src/components/visualScript/VisualFlow.tsx +++ b/packages/editor/src/components/visualScript/VisualFlow.tsx @@ -37,9 +37,9 @@ import { VisualScriptState } from '@etherealengine/visual-script' import 'reactflow/dist/style.css' +import Button from '@etherealengine/ui/src/primitives/tailwind/Button' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' import { SelectionState } from '../../services/SelectionServices' -import { PropertiesPanelButton } from '../inputs/Button' import { commitProperty } from '../properties/Util' import './ReactFlowStyle.css' @@ -85,21 +85,16 @@ const VisualFlow = () => { return ( {({ width, height }) => ( -
+
{entities.length && !validEntity ? ( - { addVisualScript() }} > {t('editor:visualScript.panel.addVisualScript')} - + ) : ( <> )} diff --git a/packages/editor/src/components/visualScript/VisualScriptUIModule.ts b/packages/editor/src/components/visualScript/VisualScriptUIModule.ts index 29773dabbc..6fb3afc612 100644 --- a/packages/editor/src/components/visualScript/VisualScriptUIModule.ts +++ b/packages/editor/src/components/visualScript/VisualScriptUIModule.ts @@ -47,7 +47,6 @@ export * from './transformers/VisualToFlow' export * from './transformers/flowToVisual' export * from './util/autoLayout' export * from './util/calculateNewEdge' -export * from './util/colors' export * from './util/getPickerFilters' export * from './util/getSocketsByNodeTypeAndHandleType' export * from './util/hasPositionMetaData' diff --git a/packages/editor/src/components/visualScript/components/InputSocket.tsx b/packages/editor/src/components/visualScript/components/InputSocket.tsx index 3ebc25552f..3b5c530448 100644 --- a/packages/editor/src/components/visualScript/components/InputSocket.tsx +++ b/packages/editor/src/components/visualScript/components/InputSocket.tsx @@ -29,8 +29,8 @@ import { Connection, Handle, Position, useReactFlow } from 'reactflow' import { InputSocketSpecJSON } from '@etherealengine/visual-script' +import { valueTypeColorMap } from '@etherealengine/ui/src/components/editor/panels/VisualScript/util/colors' import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator' -import { colors, valueTypeColorMap } from '../util/colors' import { isValidConnection } from '../util/isValidConnection' import { AutoSizeInput } from './AutoSizeInput' diff --git a/packages/editor/src/components/visualScript/components/NodeContainer.tsx b/packages/editor/src/components/visualScript/components/NodeContainer.tsx index 127f617d38..8527b6f103 100644 --- a/packages/editor/src/components/visualScript/components/NodeContainer.tsx +++ b/packages/editor/src/components/visualScript/components/NodeContainer.tsx @@ -25,9 +25,9 @@ Ethereal Engine. All Rights Reserved. import React, { PropsWithChildren } from 'react' +import { categoryColorMap } from '@etherealengine/ui/src/components/editor/panels/VisualScript/util/colors' import { NodeCategory, NodeSpecJSON } from '@etherealengine/visual-script' - -import { categoryColorMap, colors } from '../util/colors' +import { colors } from '@mui/material' const nodeContainerStyle = { borderRadius: '0.25rem', diff --git a/packages/editor/src/components/visualScript/components/OutputSocket.tsx b/packages/editor/src/components/visualScript/components/OutputSocket.tsx index 8822362daf..0238587808 100644 --- a/packages/editor/src/components/visualScript/components/OutputSocket.tsx +++ b/packages/editor/src/components/visualScript/components/OutputSocket.tsx @@ -29,8 +29,8 @@ import { Connection, Handle, Position, useReactFlow } from 'reactflow' import { OutputSocketSpecJSON } from '@etherealengine/visual-script' +import { colors, valueTypeColorMap } from '@etherealengine/ui/src/components/editor/panels/VisualScript/util/colors' import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator' -import { colors, valueTypeColorMap } from '../util/colors' import { isValidConnection } from '../util/isValidConnection' export type OutputSocketProps = { diff --git a/packages/editor/src/components/visualScript/hooks/useCustomNodeTypes.tsx b/packages/editor/src/components/visualScript/hooks/useCustomNodeTypes.tsx index a991785db7..7d2f695a4d 100644 --- a/packages/editor/src/components/visualScript/hooks/useCustomNodeTypes.tsx +++ b/packages/editor/src/components/visualScript/hooks/useCustomNodeTypes.tsx @@ -26,7 +26,7 @@ Ethereal Engine. All Rights Reserved. import React, { useEffect, useState } from 'react' import { NodeTypes } from 'reactflow' -import { Node } from '../components/Node' +import { Node } from '@etherealengine/ui/src/components/editor/panels/VisualScript/node' import { NodeSpecGenerator } from './useNodeSpecGenerator' const getCustomNodeTypes = (specGenerator: NodeSpecGenerator) => { diff --git a/packages/engine/src/scene/components/VolumetricNodes.ts b/packages/engine/src/scene/components/VolumetricNodes.ts index df815eb2d6..c9fe6b2b63 100644 --- a/packages/engine/src/scene/components/VolumetricNodes.ts +++ b/packages/engine/src/scene/components/VolumetricNodes.ts @@ -37,7 +37,7 @@ import { NodeCategory, makeFlowNodeDefinition } from '@etherealengine/visual-scr */ export const playVolumetric = makeFlowNodeDefinition({ typeName: 'engine/media/volumetric/playVolumetric', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Play Volumetric', in: { flow: 'flow', @@ -60,7 +60,7 @@ export const playVolumetric = makeFlowNodeDefinition({ */ export const setVolumetricTime = makeFlowNodeDefinition({ typeName: 'engine/media/volumetric/setVolumetricTime', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Set Volumetric Time', in: { flow: 'flow', @@ -83,7 +83,7 @@ export const setVolumetricTime = makeFlowNodeDefinition({ */ export const fadeVolumetricAudioVolume = makeFlowNodeDefinition({ typeName: 'engine/media/volumetric/fadeVolumetricVolume', - category: NodeCategory.Effect, + category: NodeCategory.Engine, label: 'Fade Volumetric Volume', in: { flow: 'flow', diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/events/onAxis.ts b/packages/engine/src/visualscript/nodes/profiles/engine/events/onAxis.ts index 12d8257b2b..31e004e66b 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/events/onAxis.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/events/onAxis.ts @@ -51,7 +51,7 @@ const initialState = (): State => ({ // very 3D specific. export const OnAxis = makeEventNodeDefinition({ typeName: 'engine/axis/use', - category: NodeCategory.Event, + category: NodeCategory.Engine, label: 'Use Axis', in: { axis: (_, graphApi) => { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/events/onButton.ts b/packages/engine/src/visualscript/nodes/profiles/engine/events/onButton.ts index e78c3dcc80..7634869203 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/events/onButton.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/events/onButton.ts @@ -52,7 +52,7 @@ const initialState = (): State => ({ const buttonStates = ['down', 'pressed', 'touched', 'up'] as Array export const OnButton = makeEventNodeDefinition({ typeName: 'engine/onButton', - category: NodeCategory.Event, + category: NodeCategory.Engine, label: 'On Button', in: { button: (_) => { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/events/onCollision.ts b/packages/engine/src/visualscript/nodes/profiles/engine/events/onCollision.ts index 4693d73baa..5d360cd1eb 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/events/onCollision.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/events/onCollision.ts @@ -49,7 +49,7 @@ const initialState = (): State => ({ // a visual script node export const OnCollision = makeEventNodeDefinition({ typeName: 'engine/onCollision', - category: NodeCategory.Event, + category: NodeCategory.Engine, label: 'Collision Events', // socket configuration support diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/events/onExecute.ts b/packages/engine/src/visualscript/nodes/profiles/engine/events/onExecute.ts index ee0f993c76..1c638889b3 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/events/onExecute.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/events/onExecute.ts @@ -47,7 +47,7 @@ const initialState = (): State => ({ // very 3D specific. export const OnExecute = makeEventNodeDefinition({ typeName: 'flow/lifecycle/onExecute', - category: NodeCategory.Event, + category: NodeCategory.Flow, label: 'On Execute', in: { system: (_, graphApi) => { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/events/onQuery.ts b/packages/engine/src/visualscript/nodes/profiles/engine/events/onQuery.ts index 87e24caf70..f8c1cc695a 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/events/onQuery.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/events/onQuery.ts @@ -43,7 +43,7 @@ const initialState = (): State => ({ export const OnQuery = makeEventNodeDefinition({ typeName: 'engine/query/use', - category: NodeCategory.Event, + category: NodeCategory.Engine, label: 'On Query', configuration: { numInputs: { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/helper/actionHelper.ts b/packages/engine/src/visualscript/nodes/profiles/engine/helper/actionHelper.ts index d4a4dc6eae..198419971f 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/helper/actionHelper.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/helper/actionHelper.ts @@ -154,7 +154,7 @@ export function registerActionConsumers() { const node = makeEventNodeDefinition({ typeName: `action/${namePath}/${dispatchName}/consume`, - category: NodeCategory.Event, + category: NodeCategory.Action, label: `on ${namePath} ${dispatchName}`, in: { system: (_, graph) => { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/helper/componentHelper.ts b/packages/engine/src/visualscript/nodes/profiles/engine/helper/componentHelper.ts index 3c2d7a049a..3834c12dd3 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/helper/componentHelper.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/helper/componentHelper.ts @@ -99,7 +99,7 @@ export function registerComponentSetters() { } const node = makeFlowNodeDefinition({ typeName: `engine/component/${componentName}/set`, - category: NodeCategory.Action, + category: NodeCategory.Engine, label: `set ${componentName}`, in: { flow: 'flow', @@ -144,7 +144,7 @@ export function registerComponentGetters() { } const node = makeFunctionNodeDefinition({ typeName: `engine/component/${componentName}/get`, - category: NodeCategory.Query, + category: NodeCategory.Engine, label: `get ${componentName}`, in: { entity: 'entity' @@ -197,7 +197,7 @@ export function registerComponentListeners() { const node = makeEventNodeDefinition({ typeName: `engine/component/${componentName}/use`, - category: NodeCategory.Event, + category: NodeCategory.Engine, label: `use ${componentName}`, in: { entity: 'entity' diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/helper/stateHelper.ts b/packages/engine/src/visualscript/nodes/profiles/engine/helper/stateHelper.ts index a8ef125ec3..b5e02219f8 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/helper/stateHelper.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/helper/stateHelper.ts @@ -72,7 +72,7 @@ export function registerStateSetters() { } const node = makeFlowNodeDefinition({ typeName: `engine/state/${stateName}/set`, - category: NodeCategory.Action, + category: NodeCategory.Engine, label: `set ${stateName}`, in: { flow: 'flow', @@ -109,7 +109,7 @@ export function registerStateGetters() { } const node = makeFlowNodeDefinition({ typeName: `engine/state/${stateName}/get`, - category: NodeCategory.Query, + category: NodeCategory.Engine, label: `get ${stateName}`, in: { flow: 'flow' @@ -165,7 +165,7 @@ export function registerStateListeners() { } const node = makeEventNodeDefinition({ typeName: `engine/state/${stateName}/use`, - category: NodeCategory.Event, + category: NodeCategory.Engine, label: `Use ${stateName}`, in: {}, out: { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/registerEngineProfile.ts b/packages/engine/src/visualscript/nodes/profiles/engine/registerEngineProfile.ts index 7263145d63..1711c35150 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/registerEngineProfile.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/registerEngineProfile.ts @@ -28,12 +28,10 @@ Ethereal Engine. All Rights Reserved. import * as VolumetricNodes from '@etherealengine/engine/src/scene/components/VolumetricNodes' import { getNodeDescriptions, - GetSceneProperty, getStringConversionsForValueType, IRegistry, memo, NodeDefinition, - SetSceneProperty, ValueTypeMap } from '@etherealengine/visual-script' @@ -98,8 +96,8 @@ export const getEngineNodesMap = memo>(() => { OnAxis, // async //switchScene.Description, - ...SetSceneProperty(engineValueTypeNames), - ...GetSceneProperty(engineValueTypeNames), + //...SetSceneProperty(engineValueTypeNames), + //...GetSceneProperty(engineValueTypeNames), // flow control ...getEngineStringConversions(getEngineValuesMap()), diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/AxisNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/AxisNodes.ts index fab4752102..59a2bfa06c 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/AxisNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/AxisNodes.ts @@ -31,7 +31,7 @@ import { Assert, Choices, NodeCategory, makeFunctionNodeDefinition } from '@ethe // very 3D specific. export const getAxis = makeFunctionNodeDefinition({ typeName: 'engine/axis/get', - category: NodeCategory.Query, + category: NodeCategory.Engine, label: 'get Axis', in: { axis: (_, graphApi) => { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/ComponentNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/ComponentNodes.ts index 73dc25e846..ed6ebdb6a1 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/ComponentNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/ComponentNodes.ts @@ -34,7 +34,7 @@ import { Assert, NodeCategory, makeFlowNodeDefinition } from '@etherealengine/vi export const addComponent = makeFlowNodeDefinition({ typeName: 'engine/component/addComponent', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Add Component', in: { flow: 'flow', @@ -62,7 +62,7 @@ export const addComponent = makeFlowNodeDefinition({ export const deleteComponent = makeFlowNodeDefinition({ typeName: 'engine/component/deleteComponent', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Delete Component', in: { flow: 'flow', @@ -90,7 +90,7 @@ export const deleteComponent = makeFlowNodeDefinition({ export const setTag = makeFlowNodeDefinition({ typeName: 'engine/component/tag/set', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'set Tag', in: { flow: 'flow', @@ -114,7 +114,7 @@ export const setTag = makeFlowNodeDefinition({ export const removeTag = makeFlowNodeDefinition({ typeName: 'engine/component/tag/remove', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'remove Tag', in: { flow: 'flow', diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/CustomNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/CustomNodes.ts index 11ce1e9a68..08cb4eedac 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/CustomNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/CustomNodes.ts @@ -73,7 +73,7 @@ import { addMediaComponent } from '../helper/assetHelper' export const playVideo = makeFlowNodeDefinition({ typeName: 'engine/media/playVideo', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Play video', in: { flow: 'flow', @@ -133,7 +133,7 @@ export const playVideo = makeFlowNodeDefinition({ export const playAudio = makeFlowNodeDefinition({ typeName: 'engine/media/playAudio', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Play audio', in: { flow: 'flow', @@ -190,7 +190,7 @@ export const playAudio = makeFlowNodeDefinition({ /* export const makeRaycast = makeFlowNodeDefinition({ typeName: 'engine/playAudio', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Play audio', in: { flow: 'flow', @@ -234,7 +234,7 @@ export const makeRaycast = makeFlowNodeDefinition({ export const getAnimationPack = makeFunctionNodeDefinition({ typeName: 'engine/media/getAnimationPack', - category: NodeCategory.Query, + category: NodeCategory.Engine, label: 'Get Avatar Animations', in: { animationName: (_) => { @@ -256,7 +256,7 @@ export const getAnimationPack = makeFunctionNodeDefinition({ export const playAnimation = makeFlowNodeDefinition({ typeName: 'engine/media/playAnimation', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Play animation', in: { flow: 'flow', @@ -289,7 +289,7 @@ export const playAnimation = makeFlowNodeDefinition({ export const setAnimationAction = makeFlowNodeDefinition({ typeName: 'engine/media/setAnimationAction', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Set animation action', in: { flow: 'flow', @@ -352,7 +352,7 @@ export const setAnimationAction = makeFlowNodeDefinition({ const initialState = () => {} export const loadAsset = makeAsyncNodeDefinition({ typeName: 'engine/asset/loadAsset', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Load asset', in: { flow: 'flow', @@ -385,7 +385,7 @@ export const loadAsset = makeAsyncNodeDefinition({ export const fadeCamera = makeFlowNodeDefinition({ typeName: 'engine/camera/cameraFade', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Camera fade', in: { flow: 'flow', @@ -405,7 +405,7 @@ export const fadeCamera = makeFlowNodeDefinition({ export const setCameraZoom = makeFlowNodeDefinition({ typeName: 'engine/camera/setCameraZoom', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Set camera zoom', in: { flow: 'flow', @@ -423,7 +423,7 @@ export const setCameraZoom = makeFlowNodeDefinition({ export const startXRSession = makeFlowNodeDefinition({ typeName: 'engine/xr/startSession', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Start XR Session', in: { flow: 'flow', @@ -447,7 +447,7 @@ export const startXRSession = makeFlowNodeDefinition({ export const finishXRSession = makeFlowNodeDefinition({ typeName: 'engine/xr/endSession', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'End XR Session', in: { flow: 'flow' @@ -462,7 +462,7 @@ export const finishXRSession = makeFlowNodeDefinition({ export const switchScene = makeFlowNodeDefinition({ typeName: 'engine/switchScene', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Switch Scene', in: { flow: 'flow', @@ -487,7 +487,7 @@ export const group = makeFunctionNodeDefinition({ export const redirectToURL = makeFlowNodeDefinition({ typeName: 'engine/redirectToURL', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Redirect to URL', in: { flow: 'flow', @@ -506,7 +506,7 @@ export const redirectToURL = makeFlowNodeDefinition({ */ export const fadeMesh = makeFlowNodeDefinition({ typeName: 'engine/fadeMesh', - category: NodeCategory.Effect, + category: NodeCategory.Engine, label: 'Fade Mesh', in: { flow: 'flow', diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/EntityNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/EntityNodes.ts index 865ed8bc1d..c5105b6e21 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/EntityNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/EntityNodes.ts @@ -63,7 +63,7 @@ const sceneQuery = defineQuery([SourceComponent]) export const getEntity = makeFunctionNodeDefinition({ typeName: 'logic/entity/get/entityInScene', - category: NodeCategory.Query, + category: NodeCategory.Logic, label: 'Get entity in scene', in: { entity: (_) => { @@ -88,7 +88,7 @@ export const getEntity = makeFunctionNodeDefinition({ export const getLocalClientEntity = makeFunctionNodeDefinition({ typeName: 'logic/entity/get/localClientEntity', - category: NodeCategory.Query, + category: NodeCategory.Logic, label: 'Get local client entity', in: {}, out: { entity: 'entity' }, @@ -100,7 +100,7 @@ export const getLocalClientEntity = makeFunctionNodeDefinition({ export const getCameraEntity = makeFunctionNodeDefinition({ typeName: 'logic/entity/get/cameraEntity', - category: NodeCategory.Query, + category: NodeCategory.Logic, label: 'Get camera entity', in: {}, out: { entity: 'entity' }, @@ -112,7 +112,7 @@ export const getCameraEntity = makeFunctionNodeDefinition({ export const entityExists = makeFlowNodeDefinition({ typeName: 'logic/entity/exists', - category: NodeCategory.Action, + category: NodeCategory.Logic, label: 'Entity exists', in: { flow: 'flow', @@ -149,7 +149,7 @@ export const entityExists = makeFlowNodeDefinition({ export const addEntity = makeFlowNodeDefinition({ typeName: 'logic/entity/addEntity', - category: NodeCategory.Action, + category: NodeCategory.Logic, label: 'Add entity', in: { flow: 'flow', @@ -194,7 +194,7 @@ export const addEntity = makeFlowNodeDefinition({ export const deleteEntity = makeFlowNodeDefinition({ typeName: 'logic/entity/deleteEntity', - category: NodeCategory.Action, + category: NodeCategory.Logic, label: 'Delete entity', in: { flow: 'flow', @@ -222,7 +222,7 @@ export const deleteEntity = makeFlowNodeDefinition({ export const getEntityTransform = makeFunctionNodeDefinition({ typeName: 'engine/entity/TransformComponent/get', - category: NodeCategory.Query, + category: NodeCategory.Engine, label: 'Get entity transform', in: { entity: 'entity' @@ -241,7 +241,7 @@ export const getEntityTransform = makeFunctionNodeDefinition({ export const setEntityTransform = makeFlowNodeDefinition({ typeName: 'engine/entity/TransformComponent/set', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'set transformComponent', in: { flow: 'flow', @@ -270,7 +270,7 @@ export const setEntityTransform = makeFlowNodeDefinition({ export const useEntityTransform = makeEventNodeDefinition({ typeName: 'engine/entity/TransformComponent/use', - category: NodeCategory.Event, + category: NodeCategory.Engine, label: 'Use entity transform', in: { entity: 'entity' diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/QueryNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/QueryNodes.ts index dd86491bbc..42b4e74d56 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/QueryNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/QueryNodes.ts @@ -29,7 +29,7 @@ import { NodeCategory, SocketsList, makeFunctionNodeDefinition, sequence } from export const getQuery = makeFunctionNodeDefinition({ typeName: 'engine/query/get', - category: NodeCategory.Query, + category: NodeCategory.Engine, label: 'get Query', configuration: { numInputs: { diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/SplineNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/SplineNodes.ts index 23668d995a..db1408f15c 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/SplineNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/SplineNodes.ts @@ -43,7 +43,7 @@ const splineQuery = defineQuery([SplineComponent]) export const getSpline = makeFunctionNodeDefinition({ typeName: 'engine/spline/getSpline', - category: NodeCategory.Query, + category: NodeCategory.Engine, label: 'Get Spline Entity', in: { spline: (_) => { @@ -76,7 +76,7 @@ const initialState = (): State => ({ }) export const addSplineTrack = makeAsyncNodeDefinition({ typeName: 'engine/spline/addSplineTrack', - category: NodeCategory.Action, + category: NodeCategory.Engine, label: 'Add spline track', in: { flow: 'flow', diff --git a/packages/engine/src/visualscript/nodes/profiles/engine/values/VariableNodes.ts b/packages/engine/src/visualscript/nodes/profiles/engine/values/VariableNodes.ts index 854151969c..3748bc4b1a 100644 --- a/packages/engine/src/visualscript/nodes/profiles/engine/values/VariableNodes.ts +++ b/packages/engine/src/visualscript/nodes/profiles/engine/values/VariableNodes.ts @@ -39,8 +39,8 @@ import { } from '@etherealengine/visual-script' export const EngineVariableGet = makeFunctionNodeDefinition({ - typeName: 'engine/variable/get', - category: NodeCategory.Query, + typeName: 'variable/get', + category: NodeCategory.Variable, label: 'Get', configuration: { variableName: { @@ -87,8 +87,8 @@ export const EngineVariableGet = makeFunctionNodeDefinition({ }) export const EngineVariableSet = makeFlowNodeDefinition({ - typeName: 'engine/variable/set', - category: NodeCategory.Action, + typeName: 'variable/set', + category: NodeCategory.Variable, label: 'Set', configuration: { variableName: { @@ -152,8 +152,8 @@ export const getUseVariableSystemUUID = (variableName) => (useVariableSystemUUID + `${variableName}-` + useVariableSystemCounter) as SystemUUID export const EngineVariableUse = makeEventNodeDefinition({ - typeName: 'engine/variable/use', - category: NodeCategory.Event, + typeName: 'variable/use', + category: NodeCategory.Variable, label: 'Use', configuration: { variableName: { diff --git a/packages/spatial/src/common/constants/MathConstants.ts b/packages/spatial/src/common/constants/MathConstants.ts index 237f985b4b..f06d2516f9 100644 --- a/packages/spatial/src/common/constants/MathConstants.ts +++ b/packages/spatial/src/common/constants/MathConstants.ts @@ -59,9 +59,15 @@ export const Vector2_Zero = Object.freeze(new Vector2(0, 0)) /** const Vector2(1,0) */ export const Vector2_Right = Object.freeze(new Vector2(1, 0)) +/** const Vector2(-1,0) */ +export const Vector2_Left = Object.freeze(new Vector2(-1, 0)) + /** const Vector2(0,1) */ export const Vector2_Up = Object.freeze(new Vector2(0, 1)) +/** const Vector2(0,-1) */ +export const Vector2_Down = Object.freeze(new Vector2(0, -1)) + /** const Vector2(1,1) */ export const Vector2_One = Object.freeze(new Vector2(1, 1)) diff --git a/packages/ui/src/components/editor/layout/PaginatedList.tsx b/packages/ui/src/components/editor/layout/PaginatedList.tsx new file mode 100644 index 0000000000..5fdf3a8071 --- /dev/null +++ b/packages/ui/src/components/editor/layout/PaginatedList.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 ArrowLeftIcon from '@mui/icons-material/ArrowLeft' +import ArrowRightIcon from '@mui/icons-material/ArrowRight' +import React, { useEffect } from 'react' + +import { State, useHookstate } from '@etherealengine/hyperflux' + +import Button from '../../../primitives/tailwind/Button' + +const buttonStyle = { + width: '90%', + overflow: 'hidden', + textOverflow: 'ellipsis', + padding: '5%' +} + +export default function PaginatedList({ + list, + element, + options +}: { + ['list']: T[] | State + ['element']: (val: T | State, _index: number) => JSX.Element + ['options']?: { + ['countPerPage']?: number + } +}) { + const countPerPage = options?.countPerPage ?? 7 + const currentPage = useHookstate(0) + + function getPageIndices() { + const start = countPerPage * currentPage.value + return [start, Math.min(start + countPerPage, list.length /*- 1*/)] + } + + const pageView = useHookstate(getPageIndices()) + useEffect(() => { + pageView.set(getPageIndices()) + }, [currentPage]) + return ( + <> +
+ {list?.length > 0 && ( +
+
+ +
+ {[-2, -1, 0, 1, 2].map((idx) => { + const btnKey = `paginatedButton-${idx}` + if (!(currentPage.value + idx < 0 || currentPage.value + idx >= list.length / countPerPage)) + return ( +
+ +
+ ) + else + return ( +
+
+
+ ) + })} +
+ +
+
+ )} +
+ {(pageView.value[0] === pageView.value[1] ? list : list.slice(...pageView.value)).map((val, index) => { + return
{element(val, index)}
+ })} + + ) +} diff --git a/packages/ui/src/components/editor/layout/Panel.tsx b/packages/ui/src/components/editor/layout/Panel.tsx index d36f46a517..7cbd414be4 100755 --- a/packages/ui/src/components/editor/layout/Panel.tsx +++ b/packages/ui/src/components/editor/layout/Panel.tsx @@ -51,10 +51,7 @@ interface PanelProps { const Panel: React.FC = ({ icon, title, children, toolbarContent, ...rest }) => { return ( -
+
{icon && } {title} diff --git a/packages/ui/src/components/editor/panels/VisualScript/autoSizeInput/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/autoSizeInput/index.tsx new file mode 100644 index 0000000000..f3f2d43ebe --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/autoSizeInput/index.tsx @@ -0,0 +1,79 @@ +/* +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, { CSSProperties, HTMLProps, useCallback, useEffect, useRef, useState } from 'react' + +export type AutoSizeInputProps = HTMLProps & { + minWidth?: number +} + +const baseStyles: CSSProperties = { + position: 'absolute', + top: 0, + left: 0, + visibility: 'hidden', + height: 0, + width: 'auto', + whiteSpace: 'pre' +} + +export const AutoSizeInput: React.FC = ({ minWidth = 100, ...props }) => { + const inputRef = useRef(null) + const measureRef = useRef(null) + const [styles, setStyles] = useState({}) + + // grab the font size of the input on ref mount + const setRef = useCallback((input: HTMLInputElement | null) => { + if (input) { + const styles = window.getComputedStyle(input) + setStyles({ + fontSize: styles.getPropertyValue('font-size'), + paddingLeft: styles.getPropertyValue('padding-left'), + paddingRight: styles.getPropertyValue('padding-right') + }) + } + inputRef.current = input + }, []) + + // measure the text on change and update input + useEffect(() => { + if (measureRef.current === null) return + if (inputRef.current === null) return + + const padding = props.type === 'number' || props.type === 'float' ? 20 : 1 + + const width = measureRef.current.clientWidth + padding + inputRef.current.style.width = Math.max(minWidth, width) + 'px' + }, [props.value, minWidth, styles, props.type]) + + return ( + <> + + + {props.value} + + + ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/container/ReactFlowStyle.css b/packages/ui/src/components/editor/panels/VisualScript/container/ReactFlowStyle.css new file mode 100644 index 0000000000..6b2ae8557c --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/container/ReactFlowStyle.css @@ -0,0 +1,87 @@ +.react-flow__handle { + width: 10px; + height: 10px; +} + +.react-flow__handle-right { + right: -6px; +} + +.react-flow__handle-left { + left: -6px; +} + +/* Styling for the edges in react flow */ +.react-flow__edge-path { + stroke: #b1b1b7; + stroke-width: 2; + cursor: pointer; +} + +/* Styling for number input */ +input[type='number'] { + -moz-appearance: textfield; + appearance: textfield; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +/* Default stroke color for react flow edges */ +.react-flow__edge-path { + stroke: #555; +} + +/* Styling for selected react flow edges */ +.react-flow__edge.selected .react-flow__edge-path { + stroke: #fff; +} + +/* Styling for selected react flow nodes */ +.react-flow__nodes .react-flow__node::selection { + outline: 1px white solid; +} + +/* Styling for the title of react flow nodes */ +.react-flow__nodes .react-flow__node .title { + background-color: #aac; + color: #fff; + padding: 0.5rem 0.25rem; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +/* Styling for the content of react flow nodes */ +.react-flow__nodes .react-flow__node .content { + display: flex; + flex-grow: 1; + flex-direction: column; + gap: 0.5rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +/* Styling for the react flow controls */ +.react-flow .react-flow__controls { + display: flex; + width: 100%; + background-color: #111113; +} + +.react-flow__panel { + margin-left: 0 !important; + margin-bottom: 0 !important; +} + +.react-flow__controls-button{ + background-color: #111113 !important; + color: #6B7280 !important; + border-bottom: none !important; + + svg { + fill: #6B7280 !important; + stroke: #6B7280 !important; + } +} \ No newline at end of file diff --git a/packages/ui/src/components/editor/panels/VisualScript/container/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/container/index.tsx new file mode 100644 index 0000000000..b9776e20b5 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/container/index.tsx @@ -0,0 +1,114 @@ +/* +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, hasComponent, useQuery } from '@etherealengine/ecs' +import { commitProperty } from '@etherealengine/editor/src/components/properties/Util' +import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions' +import { SelectionState } from '@etherealengine/editor/src/services/SelectionServices' +import { VisualScriptComponent } from '@etherealengine/engine' +import { getState } from '@etherealengine/hyperflux' +import { VisualScriptState } from '@etherealengine/visual-script' +import { isEqual } from 'lodash' +import React from 'react' +import { useTranslation } from 'react-i18next' +import AutoSizer from 'react-virtualized-auto-sizer' +import { ReactFlowProvider } from 'reactflow' +import 'reactflow/dist/style.css' +import Button from '../../../../../primitives/tailwind/Button' +import { Flow } from '../flow' +import './ReactFlowStyle.css' + +export const ActiveVisualScript = (props: { entity }) => { + const { entity } = props + + // reactivity + const visualScriptState = getState(VisualScriptState) + + // get underlying data, avoid hookstate error 202 + const visualScriptComponent = getComponent(entity, VisualScriptComponent) + + return ( + + { + if (!newVisualScript) return + if (isEqual(visualScriptComponent.visualScript, newVisualScript)) return + commitProperty(VisualScriptComponent, 'visualScript')(newVisualScript) + }} + /> + + ) +} + +const VisualFlow = () => { + const entities = SelectionState.useSelectedEntities() + const entity = entities[entities.length - 1] + const validEntity = typeof entity === 'number' && hasComponent(entity, VisualScriptComponent) + const { t } = useTranslation() + + const addVisualScript = () => EditorControlFunctions.addOrRemoveComponent([entity], VisualScriptComponent, true) + + // ensure reactivity of adding new visualScript + useQuery([VisualScriptComponent]) + + return ( + + {({ width, height }) => ( +
+ {entities.length && !validEntity ? ( + + ) : ( + <> + )} + {validEntity && } +
+ )} +
+ ) +} + +export const VisualScriptPanel = () => { + return ( + <> +
+
+ +
+
+ + ) +} + +export default VisualScriptPanel diff --git a/packages/ui/src/components/editor/panels/VisualScript/controls/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/controls/index.tsx new file mode 100644 index 0000000000..5cbbc9c285 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/controls/index.tsx @@ -0,0 +1,105 @@ +/* +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, { useState } from 'react' +import { PiDownloadSimpleBold, PiPauseBold, PiPlayBold, PiTrashSimpleBold, PiUploadSimpleBold } from 'react-icons/pi' +import { ControlButton, Controls } from 'reactflow' + +import { NodeSpecGenerator } from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { GraphJSON, VariableJSON } from '@etherealengine/visual-script' +import { ClearModal } from '../modals/clear' +import { HelpModal } from '../modals/help' +import { Examples, LoadModal } from '../modals/load' +import { SaveModal } from '../modals/save' + +export type CustomControlsProps = { + playing: boolean + togglePlay: () => void + onSaveVisualScript: (value: GraphJSON) => void + setVisualScript: (value: GraphJSON) => void + variables: VariableJSON[] + examples: Examples + specGenerator: NodeSpecGenerator | undefined +} + +export const CustomControls: React.FC = ({ + playing, + togglePlay, + setVisualScript, + examples, + variables, + specGenerator +}: CustomControlsProps) => { + const [loadModalOpen, setLoadModalOpen] = useState(false) + const [saveModalOpen, setSaveModalOpen] = useState(false) + const [helpModalOpen, setHelpModalOpen] = useState(false) + const [clearModalOpen, setClearModalOpen] = useState(false) + // load modal should have a drop area for json files + // save modal should provide a path or file browser to save, or just save automatically + return ( + <> + + {/* setHelpModalOpen(true)}> + + */} + setLoadModalOpen(true)}> + + + { + setSaveModalOpen(true) + }} + > + + + setClearModalOpen(true)}> + + + + {playing ? : } + + + setLoadModalOpen(false)} + setVisualScript={setVisualScript} + examples={examples} + /> + {specGenerator && ( + setSaveModalOpen(false)} + /> + )} + setHelpModalOpen(false)} /> + setClearModalOpen(false)} /> + + ) +} + +export default CustomControls diff --git a/packages/ui/src/components/editor/panels/VisualScript/flow/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/flow/index.tsx new file mode 100644 index 0000000000..86b8a6b37c --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/flow/index.tsx @@ -0,0 +1,215 @@ +/* +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 { useTranslation } from 'react-i18next' +import { Background, BackgroundVariant, NodeToolbar, Panel, Position, ReactFlow } from 'reactflow' + +import { + useFlowHandlers, + useNodeSpecGenerator, + useVisualScriptFlow +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { useSelectionHandler } from '@etherealengine/editor/src/components/visualScript/hooks/useSelectionHandler' +import { useTemplateHandler } from '@etherealengine/editor/src/components/visualScript/hooks/useTemplateHandler' +import { useVariableHandler } from '@etherealengine/editor/src/components/visualScript/hooks/useVariableHandler' +import { useVisualScriptRunner } from '@etherealengine/engine/src/visualscript/systems/useVisualScriptRunner' +import { useHookstate } from '@etherealengine/hyperflux' +import { GraphJSON, IRegistry } from '@etherealengine/visual-script' +import Button from '../../../../../primitives/tailwind/Button' +import CustomControls from '../controls' +import { Examples } from '../modals/load' +import { NodePicker } from '../node/picker' +import SidePanel from '../sidePanel' + +type FlowProps = { + initialVisualScript: GraphJSON + examples: Examples + registry: IRegistry + onChangeVisualScript: (newVisualScript: GraphJSON) => void +} + +export const Flow: React.FC = ({ + initialVisualScript: visualScript, + examples, + registry, + onChangeVisualScript +}) => { + const specGenerator = useNodeSpecGenerator(registry) + const flowRef = useRef(null) + const dragging = useHookstate(false) + const mouseOver = useHookstate(false) + const { t } = useTranslation() + + const { + nodes, + edges, + variables, + setVariables, + onNodesChange, + onEdgesChange, + visualScriptJson, + setVisualScriptJson, + deleteNodes, + nodeTypes + } = useVisualScriptFlow({ + initialVisualScriptJson: visualScript, + specGenerator + }) + + const { + onConnect, + handleStartConnect, + handleStopConnect, + handlePaneClick, + handlePaneContextMenu, + nodePickerVisibility, + handleAddNode, + lastConnectStart, + closeNodePicker, + nodePickFilters + } = useFlowHandlers({ + nodes, + onEdgesChange, + onNodesChange, + specGenerator + }) + + const { handleAddVariable, handleEditVariable, handleDeleteVariable } = useVariableHandler({ + variables, + setVariables + }) + + const { togglePlay, playing } = useVisualScriptRunner({ + visualScriptJson, + registry + }) + + const { selectedNodes, selectedEdges, onSelectionChange, copyNodes, pasteNodes } = useSelectionHandler({ + nodes, + onNodesChange, + onEdgesChange + }) + + const { handleAddTemplate, handleEditTemplate, handleDeleteTemplate, handleApplyTemplate } = useTemplateHandler({ + selectedNodes, + selectedEdges, + pasteNodes, + onNodesChange + }) + + useEffect(() => { + if (dragging.value || !mouseOver.value) return + onChangeVisualScript(visualScriptJson ?? visualScript) + }, [visualScriptJson]) // change in node position triggers reactor + + return ( + dragging.set(true)} + onNodeDragStop={() => dragging.set(false)} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} + onConnect={onConnect} + onNodesDelete={deleteNodes} + onConnectStart={handleStartConnect} + onConnectEnd={handleStopConnect} + onPaneMouseEnter={() => mouseOver.set(true)} + onPaneMouseLeave={() => mouseOver.set(false)} + fitView + fitViewOptions={{ maxZoom: 1 }} + onPaneClick={handlePaneClick} + onPaneContextMenu={handlePaneContextMenu} + onSelectionChange={onSelectionChange} + multiSelectionKeyCode={'Shift'} + deleteKeyCode={'Backspace'} + > + + + + + + + + + + {nodePickerVisibility && ( + + )} + + node.id)} + isVisible={selectedNodes.length > 1} + position={Position.Top} + > + + + + ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/index.tsx index 13b59064fa..e28dded131 100644 --- a/packages/ui/src/components/editor/panels/VisualScript/index.tsx +++ b/packages/ui/src/components/editor/panels/VisualScript/index.tsx @@ -28,6 +28,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import { PanelDragContainer, PanelTitle } from '../../layout/Panel' +import VisualScriptPanel from './container' export const VisualScriptPanelTitle = () => { const { t } = useTranslation() @@ -49,5 +50,5 @@ export const VisualScriptPanelTab: TabData = { id: 'visualScriptPanel', closable: true, title: , - content: <> + content: } diff --git a/packages/ui/src/components/editor/panels/VisualScript/modals/clear/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/modals/clear/index.tsx new file mode 100644 index 0000000000..bfce9131bb --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/modals/clear/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 React from 'react' +import { useTranslation } from 'react-i18next' +import { useReactFlow } from 'reactflow' +import { Modal } from '..' + +export type ClearModalProps = { + open?: boolean + onClose: () => void +} + +export const ClearModal: React.FC = ({ open = false, onClose }) => { + const instance = useReactFlow() + const { t } = useTranslation() + + const handleClear = () => { + instance.setNodes([]) + instance.setEdges([]) + // TODO better way to call fit vew after edges render + setTimeout(() => { + instance.fitView() + }, 100) + onClose() + } + + return ( + +

{t('editor:visualScript.modal.clear.confirm')}

+
+ ) +} diff --git a/packages/ui/src/components/editor/properties/group/index.stories.tsx b/packages/ui/src/components/editor/panels/VisualScript/modals/help/index.tsx similarity index 57% rename from packages/ui/src/components/editor/properties/group/index.stories.tsx rename to packages/ui/src/components/editor/panels/VisualScript/modals/help/index.tsx index 320593ff22..693bdec282 100644 --- a/packages/ui/src/components/editor/properties/group/index.stories.tsx +++ b/packages/ui/src/components/editor/panels/VisualScript/modals/help/index.tsx @@ -23,22 +23,28 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 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 +import React from 'react' +import { useTranslation } from 'react-i18next' +import { Modal } from '..' + +export type HelpModalProps = { + open?: boolean + onClose: () => void } -export const Default = { args: Component.defaultProps } +export const HelpModal: React.FC = ({ open = false, onClose }) => { + const { t } = useTranslation() + + return ( + +

{t('editor:visualScript.modal.help.addNodeHelp')}

+

{t('editor:visualScript.modal.help.addConnectionHelp')}

+

{t('editor:visualScript.modal.help.deleteNodeHelp')}

+
+ ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/modals/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/modals/index.tsx new file mode 100644 index 0000000000..2c339d0a4c --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/modals/index.tsx @@ -0,0 +1,72 @@ +/* +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 { useOnPressKey } from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import React, { PropsWithChildren } from 'react' +import { twMerge } from 'tailwind-merge' + +export type ModalAction = { + label: string + onClick: () => void +} + +export type ModalProps = { + open?: boolean + onClose: () => void + title: string + actions: ModalAction[] +} + +export const Modal: React.FC> = ({ open = false, onClose, title, children, actions }) => { + useOnPressKey('Escape', onClose) + + if (open === false) return null + + return ( + <> +
+
+
+

{title}

+
+
{children}
+
+ {actions.map((action, ix) => ( + + ))} +
+
+ + ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/modals/load/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/modals/load/index.tsx new file mode 100644 index 0000000000..6a2f0d6452 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/modals/load/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 React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useReactFlow } from 'reactflow' + +import { GraphJSON } from '@etherealengine/visual-script' +import { Modal } from '..' +import SelectInput from '../../../../input/Select' + +export type Examples = { + [key: string]: GraphJSON +} + +export type LoadModalProps = { + open?: boolean + onClose: () => void + setVisualScript: (value: GraphJSON) => void + examples: Examples +} + +export const LoadModal: React.FC = ({ open = false, onClose, setVisualScript, examples }) => { + const [value, setValue] = useState() + const [selected, setSelected] = useState('') + + const instance = useReactFlow() + const { t } = useTranslation() + + useEffect(() => { + if (selected) { + setValue(JSON.stringify(examples[selected], null, 2)) + } + }, [selected, examples]) + + const handleLoad = useCallback(() => { + let visualScript + if (value !== undefined) { + visualScript = JSON.parse(value) as GraphJSON + } else if (selected !== '') { + visualScript = examples[selected] + } + + if (visualScript === undefined) return + + setVisualScript(visualScript) + + // TODO better way to call fit vew after edges render + setTimeout(() => { + instance.fitView() + }, 100) + + handleClose() + }, [setVisualScript, value, instance]) + + const handleClose = () => { + setValue(undefined) + setSelected('') + onClose() + } + + return ( + + +
or
+
+ { + return { + label: key, + value: key + } + })} + value={selected} + onChange={(val) => setSelected(val as string)} + placeholder={t('editor:visualScript.modal.load.examples')} + /> +
+
+ ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/modals/save/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/modals/save/index.tsx new file mode 100644 index 0000000000..3025722415 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/modals/save/index.tsx @@ -0,0 +1,89 @@ +/* +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, { useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useEdges, useNodes } from 'reactflow' + +import { VariableJSON } from '@etherealengine/visual-script' + +import { + NodeSpecGenerator, + flowToVisual +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { Modal } from '..' + +export type SaveModalProps = { + open?: boolean + variables: VariableJSON[] + onClose: () => void + specGenerator: NodeSpecGenerator +} + +export const SaveModal: React.FC = ({ open = false, variables, onClose, specGenerator }) => { + const ref = useRef(null) + const [copied, setCopied] = useState(false) + const { t } = useTranslation() + + const edges = useEdges() + const nodes = useNodes() + + const flow = useMemo(() => flowToVisual(nodes, edges, variables, specGenerator), [nodes, edges, specGenerator]) + + const jsonString = JSON.stringify(flow, null, 2) + + const handleCopy = () => { + ref.current?.select() + document.execCommand('copy') + ref.current?.blur() + setCopied(true) + setInterval(() => { + setCopied(false) + }, 1000) + } + + return ( + + + + ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/node/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/node/index.tsx new file mode 100644 index 0000000000..e4c9863331 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/node/index.tsx @@ -0,0 +1,253 @@ +/* +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 { FaCircleMinus, FaCirclePlus } from 'react-icons/fa6' +import { NodeProps as FlowNodeProps, useEdges } from 'reactflow' + +import { + NodeSpecGenerator, + isHandleConnected, + useChangeNode +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { useModifyNodeSocket } from '@etherealengine/editor/src/components/visualScript/hooks/useModifyNodeSocket' +import { NodeSpecJSON } from '@etherealengine/visual-script' +import { MdKeyboardArrowDown, MdKeyboardArrowRight } from 'react-icons/md' +import { twMerge } from 'tailwind-merge' +import { categoryColorMap, colors } from '../util/colors' +import InputSocket from './socket/input' +import OutputSocket from './socket/output' + +type NodeUIProps = FlowNodeProps & { + spec: NodeSpecJSON + specGenerator: NodeSpecGenerator +} + +const getPairs = (arr1: T[], arr2: U[]) => { + const max = Math.max(arr1.length, arr2.length) + const pairs: any[] = [] + for (let i = 0; i < max; i++) { + const pair: [T | undefined, U | undefined] = [arr1[i], arr2[i]] + pairs.push(pair) + } + return pairs +} + +const calculatePosition = (index, total) => { + if (total === 1) return { x: 0 } + let x = 0 + const y = (index / total) * 100 + if (total < 2) { + return { x: x, y: (index + 1 / total) * 100 } + } + if (total < 7) { + x = + (total % 2 + ? index > total / 2 + ? index - Math.floor(total / 2) + : Math.floor(total / 2) - index + : index > (total - 1) / 2 + ? index + 1 - total / 2 + : total / 2 - index) * + 2 * + Math.PI + return { x: x, y: y } + } + // whem we have more than 7 inputs or outputs the rest of the handle fall on a flat plane so we only need to calculate for the top and bottom 3 handles + if (index <= 3) x = Math.max((3 - index) * 6, 1) + if (index === 0) x = 24 + if (total - 1 - index <= 3) x = Math.max((3 - (total - 1 - index)) * 2 * Math.PI, 1) + if (index === total - 1) x = 24 + return { x: x, y: y } +} + +const calculateRelativePosition = (index, type, outputTotal, inputTotal) => { + return type === 'output' + ? outputTotal > inputTotal + ? index + : Math.floor(((index + 1) * inputTotal) / (outputTotal + 1)) + : inputTotal > outputTotal + ? index + : Math.floor(((index + 1) * outputTotal) / (inputTotal + 1)) +} + +export const Node: React.FC = ({ id, data, spec, selected, specGenerator }: NodeUIProps) => { + const [collapsed, setCollapsed] = React.useState(false) + const edges = useEdges() + const isVariableNode = spec.configuration.find( + (config) => config.name === 'variableName' && config.valueType === 'string' + ) + const handleChange = useChangeNode(id, isVariableNode !== undefined) + const canAddInputs = spec.configuration.find((config) => config.name === 'numInputs' && config.valueType === 'number') + const canAddOutputs = spec.configuration.find( + (config) => config.name === 'numOutputs' && config.valueType === 'number' + ) + const canAddBoth = spec.configuration.find((config) => config.name === 'numCases' && config.valueType === 'number') + let handleAddNodeSocket + let handleDecreaseNodeSocket + if (canAddInputs) { + handleAddNodeSocket = useModifyNodeSocket(id, 'inputs', 'increase', (canAddInputs.defaultValue as number) ?? 1) + handleDecreaseNodeSocket = useModifyNodeSocket(id, 'inputs', 'decrease', (canAddInputs.defaultValue as number) ?? 1) + } else if (canAddOutputs) { + handleAddNodeSocket = useModifyNodeSocket(id, 'outputs', 'increase', (canAddOutputs.defaultValue as number) ?? 1) + handleDecreaseNodeSocket = useModifyNodeSocket( + id, + 'outputs', + 'decrease', + (canAddOutputs.defaultValue as number) ?? 1 + ) + } else if (canAddBoth) { + handleAddNodeSocket = useModifyNodeSocket(id, 'both', 'increase', (canAddBoth.defaultValue as number) ?? 1) + handleDecreaseNodeSocket = useModifyNodeSocket(id, 'both', 'decrease', (canAddBoth.defaultValue as number) ?? 1) + } + + const pairs = getPairs(spec.inputs, spec.outputs) + const label = spec.label === '' ? data.label : spec.label + + let colorName = categoryColorMap[spec.category] + if (colorName === undefined) { + colorName = 'red' + } + const [backgroundColor, borderColor, textColor] = colors[colorName] + const collapsedHandlePositions = [] as any[] + for (let i = 0; i < pairs.length; i++) { + collapsedHandlePositions.push(calculatePosition(i, pairs.length)) + } + + const collapsedStyle = collapsed + ? { height: `${1.25 * pairs.length}rem`, borderRadius: `${0.625 * pairs.length}rem` } + : {} + return ( +
+
+ {collapsed && + pairs.map( + ([input, output], ix) => + input && ( + + ) + )} + {collapsed ? ( + setCollapsed(false)} /> + ) : ( + setCollapsed(true)} /> + )} + {label} + + {collapsed && + pairs.map( + ([input, output], ix) => + output && ( + + ) + )} +
+ {!collapsed && ( +
+ {pairs.map(([input, output], ix) => ( +
+ {input && ( + + )} + {output && ( + + )} +
+ ))} +
+ {handleAddNodeSocket && ( + + )} + + {handleDecreaseNodeSocket && ( + + )} +
+
+ )} +
+ ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/node/picker/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/node/picker/index.tsx new file mode 100644 index 0000000000..dcd32e01dd --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/node/picker/index.tsx @@ -0,0 +1,189 @@ +/* +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 { useOnPressKey } from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { NodeSpecJSON } from '@etherealengine/visual-script' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { GoDotFill } from 'react-icons/go' +import { HiMagnifyingGlass, HiOutlineChevronDown, HiOutlineChevronRight } from 'react-icons/hi2' +import { XYPosition, useReactFlow } from 'reactflow' +import { twMerge } from 'tailwind-merge' +import Input from '../../../../../../primitives/tailwind/Input' +import { categoryColorMap, colors } from '../../util/colors' + +const createPickerNodes = (tree, onPickNode, position, instance) => { + if (tree?.type !== undefined && typeof tree?.type === 'string') return null + return Object.entries(tree) + .filter(([key, value]) => key !== 'group') + .map(([key, value]) => { + return ( + + ) + }) +} + +const PickerItem = ({ label, node, onPickNode, position, instance, color }) => { + const [isExpanded, setIsExpanded] = useState(false) + const [childNodes, setChildNodes] = useState(null) + const [isLeafNode, setIsLeafNode] = useState(false) + useEffect(() => { + if (!isExpanded && !childNodes) { + const children = createPickerNodes(node, onPickNode, position, instance) as any + setChildNodes(children) + if (children === null) { + setIsLeafNode(true) + } + } + }, [isExpanded]) + + const handleNodeClick = () => { + if (isLeafNode) { + onPickNode(node.type, instance.screenToFlowPosition(position)) + } + setIsExpanded(!isExpanded) + } + const finalColor = (colors[color][0] as string).slice(2) + + return ( + <> +
+ + + {label} + + {node && !isLeafNode && ( + + )} +
+ {isExpanded && childNodes &&
{childNodes}
} + + ) +} + +const NodePickerNode = ({ nodes, onPickNode, position, instance }) => { + const nodeTree = {} + nodes.forEach((node) => { + const parts = node.type.split('/') + let current = nodeTree + parts.forEach((part) => { + if (!current[part]) { + current[part] = {} + } + current = current[part] + }) + current['type'] = node.type + }) + + console.log('node tree', nodeTree) + + const initialTreeNodes = createPickerNodes(nodeTree, onPickNode, position, instance) + + return
{initialTreeNodes}
+} + +export type NodePickerFilters = { + handleType: 'source' | 'target' + valueType: string +} + +type NodePickerProps = { + flowRef: React.MutableRefObject + position: XYPosition + filters?: NodePickerFilters + onPickNode: (type: string, position: XYPosition) => void + onClose: () => void + specJSON: NodeSpecJSON[] | undefined +} + +const pickerStyle = { + width: '240px', + height: '280px' +} + +export const NodePicker: React.FC = ({ + flowRef, + position, + onPickNode, + onClose, + filters, + specJSON +}: NodePickerProps) => { + const [search, setSearch] = useState('') + + const instance = useReactFlow() + useOnPressKey('Escape', onClose) + const { t } = useTranslation() + + if (!specJSON) return null + let filtered = specJSON + if (filters !== undefined) { + filtered = filtered?.filter((node) => { + const sockets = filters?.handleType === 'source' ? node.outputs : node.inputs + return sockets.some((socket) => socket.valueType === filters?.valueType) + }) + } + + filtered = + filtered?.filter((node) => { + const term = search.toLowerCase() + return node.type.toLowerCase().includes(term) + }) || [] + + const paneBounds = flowRef.current!.getBoundingClientRect() + const width = parseInt(pickerStyle.width) + const height = parseInt(pickerStyle.height) + position.x = position.x + width > paneBounds.width ? (position.x -= width) : position.x + position.y = position.y + height > paneBounds.height ? (position.y -= height) : position.y + + return ( +
+
{t('editor:visualScript.picker.title')}
+ { + setSearch(event.target.value) + }} + className="rounded bg-theme-primary px-1.5 text-[#A3A3A3]" + startComponent={} + /> + +
+ ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/node/socket/input/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/node/socket/input/index.tsx new file mode 100644 index 0000000000..9e39d5aec0 --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/node/socket/input/index.tsx @@ -0,0 +1,179 @@ +/* +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 { FaCaretRight } from 'react-icons/fa6' +import { Connection, Handle, Position, useReactFlow } from 'reactflow' + +import { + NodeSpecGenerator, + isValidConnection +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { InputSocketSpecJSON } from '@etherealengine/visual-script' +import { twMerge } from 'tailwind-merge' +import { AutoSizeInput } from '../../../autoSizeInput' +import { colors, valueTypeColorMap } from '../../../util/colors' + +export type InputSocketProps = { + connected: boolean + value: any | undefined + onChange: (key: string, value: any) => void + specGenerator: NodeSpecGenerator + collapsed: boolean + offset: any +} & InputSocketSpecJSON + +const InputFieldForValue = ({ + choices, + value, + defaultValue, + onChange, + name, + valueType +}: Pick) => { + const showChoices = choices?.length + const inputVal = String(value) ?? defaultValue ?? '' + const inputSocketStyle = { + userDrag: 'none', + WebkitUserDrag: 'none', + MozUserDrag: 'none' + } + const inputSocketClassName = 'cursor-pointer p-.5 mr-2 m-0 bg-neutral-800' + if (showChoices) + return ( + + ) + + return ( + <> + {valueType === 'string' && ( + onChange(name, e.currentTarget.value)} + /> + )} + {valueType === 'number' && ( + onChange(name, e.currentTarget.value)} + /> + )} + {valueType === 'float' && ( + onChange(name, e.currentTarget.value)} + /> + )} + {valueType === 'integer' && ( + onChange(name, e.currentTarget.value)} + /> + )} + {valueType === 'boolean' && ( + onChange(name, e.currentTarget.checked)} + /> + )} + + ) +} + +const InputSocket: React.FC = ({ connected, specGenerator, ...rest }) => { + const { name, valueType, collapsed, offset } = rest + const instance = useReactFlow() + const isFlowSocket = valueType === 'flow' + + let colorName = valueTypeColorMap[valueType] + if (colorName === undefined) { + colorName = 'red' + } + + // @ts-ignore + const [backgroundColor, borderColor] = colors[colorName] + const showName = isFlowSocket === false || name !== 'flow' + + const position = {} as any + if (offset?.x !== undefined) position['left'] = `${offset.x}%` + if (offset?.y !== undefined) position['top'] = `${offset.y}%` + + return ( +
+ {isFlowSocket && } + {showName && !collapsed &&
{name}
} + {!isFlowSocket && !connected && !collapsed && } + + isValidConnection(connection, instance, specGenerator)} + /> +
+ ) +} + +export default InputSocket diff --git a/packages/ui/src/components/editor/panels/VisualScript/node/socket/output/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/node/socket/output/index.tsx new file mode 100644 index 0000000000..7b0fc2f8cc --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/node/socket/output/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 { FaCaretRight } from 'react-icons/fa6' +import { Connection, Handle, Position, useReactFlow } from 'reactflow' + +import { + NodeSpecGenerator, + isValidConnection +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { OutputSocketSpecJSON } from '@etherealengine/visual-script' +import { twMerge } from 'tailwind-merge' +import { colors, valueTypeColorMap } from '../../../util/colors' + +export type OutputSocketProps = { + connected: boolean + specGenerator: NodeSpecGenerator + collapsed: boolean + offset: any +} & OutputSocketSpecJSON + +export default function OutputSocket({ specGenerator, connected, ...rest }: OutputSocketProps) { + const { name, valueType, collapsed, offset } = rest + + const instance = useReactFlow() + const isFlowSocket = valueType === 'flow' + let colorName = valueTypeColorMap[valueType] + if (colorName === undefined) { + colorName = 'red' + } + // @ts-ignore + const [backgroundColor, borderColor] = colors[colorName] + const showName = isFlowSocket === false || name !== 'flow' + const position = {} as any + if (offset?.x !== undefined) position['right'] = `${offset.x}%` + if (offset?.y !== undefined) position['top'] = `${offset.y}%` + + console.log('output', position, offset) + return ( +
+ {showName && !collapsed &&
{name}
} + {isFlowSocket && ( + + )} + + isValidConnection(connection, instance, specGenerator)} + /> +
+ ) +} diff --git a/packages/ui/src/components/editor/panels/VisualScript/sidePanel/index.tsx b/packages/ui/src/components/editor/panels/VisualScript/sidePanel/index.tsx new file mode 100644 index 0000000000..1ac83ce37c --- /dev/null +++ b/packages/ui/src/components/editor/panels/VisualScript/sidePanel/index.tsx @@ -0,0 +1,246 @@ +/* +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 { AddOutlined, CancelOutlined } from '@mui/icons-material' +import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { XYPosition, useReactFlow } from 'reactflow' +import { v4 as uuidv4 } from 'uuid' + +import { UndefinedEntity } from '@etherealengine/ecs' +import { + useVisualScriptFlow, + visualToFlow +} from '@etherealengine/editor/src/components/visualScript/VisualScriptUIModule' +import { useTemplateHandler } from '@etherealengine/editor/src/components/visualScript/hooks/useTemplateHandler' +import { useVariableHandler } from '@etherealengine/editor/src/components/visualScript/hooks/useVariableHandler' +import { NodetoEnginetype } from '@etherealengine/engine' +import { NO_PROXY, useMutableState } from '@etherealengine/hyperflux' +import { GraphTemplate, VariableJSON, VisualScriptDomain, VisualScriptState } from '@etherealengine/visual-script' +import Button from '../../../../../primitives/tailwind/Button' +import SelectInput from '../../../input/Select' +import StringInput from '../../../input/String' +import PaginatedList from '../../../layout/PaginatedList' +import Panel from '../../../layout/Panel' +import NodeEditor from '../../../properties/nodeEditor' +import ParameterInput from '../../../properties/parameter' +import { Examples } from '../modals/load' + +type templateHandler = ReturnType +type variableHandler = ReturnType +type visualScriptFlow = ReturnType + +export type SidePanelProps = { + flowref: React.MutableRefObject + examples: Examples + variables: VariableJSON[] +} + +export const SidePanel = ({ + flowref, + examples, + variables, + onNodesChange, + handleAddTemplate, + handleApplyTemplate, + handleDeleteTemplate, + handleEditTemplate, + handleAddVariable, + handleEditVariable, + handleDeleteVariable +}: SidePanelProps & + Pick & + Pick & + Pick) => { + const reactFlow = useReactFlow() + const visualScriptState = useMutableState(VisualScriptState) + const { t } = useTranslation() + const graphTypes = visualScriptState.registries[VisualScriptDomain.ECS].values.get(NO_PROXY) + + useEffect(() => { + for (const graph of Object.values(examples)) { + const [nodes, edges] = visualToFlow(graph) + handleAddTemplate(nodes, edges) + } + }, [examples, handleAddTemplate]) + + return ( + + + { + return ( + + ) + }} + > + + + { + return ( +
+ + { + template.name = e + handleEditTemplate(template) + }} + > + + +
+ ) + }} + >
+
+ + { + return ( + +
+
+ { + handleEditVariable({ ...variable, name: e }) + }} + > + +
+ { + return { label: valueType, value: valueType } + })} + value={variable.valueTypeName} + onChange={(value) => { + handleEditVariable({ + ...variable, + valueTypeName: value as string, + initialValue: graphTypes[value].creator() + }) + }} + /> + (e) => { + let value = e + if (variable.valueTypeName !== 'object' && typeof e === 'object') value = e.target.value + handleEditVariable({ ...variable, initialValue: value }) + }} + /> +
+
+ ) + }} + >
+
+ +
+
+
+ ) +} + +export default SidePanel diff --git a/packages/editor/src/components/visualScript/util/colors.ts b/packages/ui/src/components/editor/panels/VisualScript/util/colors.ts similarity index 66% rename from packages/editor/src/components/visualScript/util/colors.ts rename to packages/ui/src/components/editor/panels/VisualScript/util/colors.ts index ddafc2a923..351efb2dc9 100644 --- a/packages/editor/src/components/visualScript/util/colors.ts +++ b/packages/ui/src/components/editor/panels/VisualScript/util/colors.ts @@ -25,16 +25,19 @@ Ethereal Engine. All Rights Reserved. import { NodeSpecJSON } from '@etherealengine/visual-script' -export type color = 'red' | 'green' | 'lime' | 'purple' | 'blue' | 'gray' | 'white' +export type color = 'red' | 'green' | 'lime' | 'purple' | 'blue' | 'gray' | 'white' | 'orange' | 'cyan' | 'indigo' export const colors: Record = { - red: ['#dd6b20', '#dd6b20', '#fff'], - green: ['#38a169', '#38a169', '#fff'], - lime: ['#84cc16', '#84cc16', '#2d3748'], - purple: ['#9f7aea', '#9f7aea', '#fff'], - blue: ['#22d3ee', '#22d3ee', '#fff'], - gray: ['#718096', '#718096', '#fff'], - white: ['#fff', '#fff', '#4a5568'] + red: ['bg-rose-900', 'border-[#dd6b20]', 'text-[#fff]'], + green: ['bg-green-900', 'border-[#38a169]', 'text-[#fff]'], + lime: ['bg-[#84cc16]', 'border-[#84cc16]', 'text-[#2d3748]'], + purple: ['bg-purple-900', 'border-[#9f7aea]', 'text-[#fff]'], + blue: ['bg-blue-900', 'border-[#22d3ee]', 'text-[#fff]'], + gray: ['bg-[#718096]', 'border-[#718096]', 'text-[#fff]'], + white: ['bg-[#fff]', 'border-[#fff]', 'text-[#4a5568]'], + orange: ['bg-orange-900', 'border-[#f59e0b]', 'text-[#fff]'], + cyan: ['bg-cyan-800', 'border-[#2b6cb0]', 'text-[#fff]'], + indigo: ['bg-indigo-900', 'border-[#4a5568]', 'text-[#fff]'] } export const valueTypeColorMap: Record = { @@ -47,13 +50,12 @@ export const valueTypeColorMap: Record = { } export const categoryColorMap: Record = { - Event: 'red', - Logic: 'green', - Variable: 'purple', - Query: 'purple', - Action: 'blue', - Flow: 'gray', - Effect: 'lime', - Time: 'gray', + Logic: 'red', + Math: 'purple', + Engine: 'blue', + Action: 'orange', + Flow: 'green', + Variable: 'cyan', + Debug: 'indigo', None: 'gray' } diff --git a/packages/ui/src/components/editor/properties/group/index.tsx b/packages/ui/src/components/editor/properties/group/index.tsx index 35f383ca27..d004915f85 100644 --- a/packages/ui/src/components/editor/properties/group/index.tsx +++ b/packages/ui/src/components/editor/properties/group/index.tsx @@ -26,7 +26,6 @@ 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' @@ -40,7 +39,7 @@ interface Props { } const PropertyGroup = ({ name, icon, description, children, onClose, ...rest }: Props) => { - const [minimized, setMinimized] = useState(false) + const [minimized, setMinimized] = useState(true) return (
@@ -52,7 +51,7 @@ const PropertyGroup = ({ name, icon, description, children, onClose, ...rest }: className="ml-0 h-4 border-0 p-0 text-[#444444]" /> {icon} - {name} + {name && {name}}
{onClose && (