diff --git a/.gitignore b/.gitignore index efd1034..f0313e6 100755 --- a/.gitignore +++ b/.gitignore @@ -115,4 +115,5 @@ public/projects/* public/data/* -storage/ \ No newline at end of file +storage/ +yarn.lock \ No newline at end of file diff --git a/flux/.gitignore b/flux/.gitignore new file mode 100644 index 0000000..286b096 --- /dev/null +++ b/flux/.gitignore @@ -0,0 +1,21 @@ +node_modules +dist +package-lock.json + +# Log files +*.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.turbo +dev-dist +vite.config.ts.timestamp* + +cachedb +yarn.lock \ No newline at end of file diff --git a/flux/README b/flux/README new file mode 100644 index 0000000..6ec7cc8 --- /dev/null +++ b/flux/README @@ -0,0 +1,17 @@ +# Create Flux Plugin + +Bootstrap a new [Flux](https://fluxsocial.io) plugin for your community. + +[Documentation](https://docs.fluxsocial.io/create-flux-plugin/getting-started/introduction.html) + +Install deps and start building: + +```bash [npm] +cd [plugin-name] +npm install +npm run dev +``` + +## Prerequisites + +Flux runs on top of [AD4M](https://ad4m.dev), a p2p framework where all data is stored and shared. In order to build a new Flux app you need to [download](https://ad4m.dev/download) and install AD4M. diff --git a/flux/globals.d.ts b/flux/globals.d.ts new file mode 100644 index 0000000..1eabbb4 --- /dev/null +++ b/flux/globals.d.ts @@ -0,0 +1 @@ +declare module "*.module.css"; diff --git a/flux/index.html b/flux/index.html new file mode 100644 index 0000000..51d5739 --- /dev/null +++ b/flux/index.html @@ -0,0 +1,41 @@ + + + + + + + Flux App + + + + + + + + + + diff --git a/flux/package.json b/flux/package.json new file mode 100644 index 0000000..1e32688 --- /dev/null +++ b/flux/package.json @@ -0,0 +1,53 @@ +{ + "name": "@conjureworld/flux-plugin", + "version": "0.0.1", + "fluxapp": { + "name": "Conjure", + "description": "Immersive Collaboration", + "icon": "conjure" + }, + "keywords": [ + "flux-plugin", + "ad4m-view" + ], + "type": "module", + "files": [ + "dist" + ], + "main": "./dist/main.umd.cjs", + "module": "./dist/main.js", + "exports": { + ".": { + "import": "./dist/main.js", + "require": "./dist/main.umd.cjs" + } + }, + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "overrides": { + "@coasys/ad4m": "0.10.1-release-candidate-4", + "@coasys/ad4m-react-hooks": "0.10.1-release-candidate-4", + "@coasys/flux-api": "0.10.1-rc4" + }, + "dependencies": { + "@coasys/ad4m": "0.10.1-release-candidate-4", + "@coasys/ad4m-react-hooks": "0.10.1-release-candidate-4", + "@coasys/flux-api": "0.10.1-rc4" + }, + "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-decorators": "^7.21.0", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@coasys/flux-container": "0.10.1-rc4", + "@vitejs/plugin-react-swc": "^4.0.1", + "vite-plugin-babel": "1.3.2", + "vite-plugin-css-injected-by-js": "^3.5.2" + } +} diff --git a/flux/src/App.tsx b/flux/src/App.tsx new file mode 100644 index 0000000..44cc09b --- /dev/null +++ b/flux/src/App.tsx @@ -0,0 +1,125 @@ +// tslint:disable:ordered-imports + +import '@ir-engine/engine' + +import { startTimer } from '@ir-engine/spatial/src/startTimer' + +import { PerspectiveProxy } from '@coasys/ad4m' +import { AgentClient } from '@coasys/ad4m/lib/src/agent/AgentClient' +import { + createEngine, + EngineState, + EntityID, + getComponent, + setComponent, + SourceID, + UUIDComponent +} from '@ir-engine/ecs' +import { dispatchAction, getMutableState, getState, useMutableState, type UserID } from '@ir-engine/hyperflux' +import { ReferenceSpaceState, TransformComponent } from '@ir-engine/spatial' +import { useSpatialEngine } from '@ir-engine/spatial/src/initializeEngine' +import { useEngineCanvas } from '@ir-engine/spatial/src/renderer/functions/useEngineCanvas' +import React, { useEffect, useRef, useState } from 'react' +import { SceneState } from '@ir-engine/engine/src/gltf/GLTFState' +import { LocationState } from '@ir-engine/client-core/src/social/services/LocationService' +import { SpectateActions } from '@ir-engine/spatial/src/camera/systems/SpectateSystem' + +import { useTestScene } from '../../src/world/TestScene' +import { CameraOrbitComponent } from '@ir-engine/spatial/src/camera/components/CameraOrbitComponent' +import { InputComponent } from '@ir-engine/spatial/src/input/components/InputComponent' +import { Box3, Vector3 } from 'three' +// import { useBasicScene } from '../../src/world/BasicScene' +// import { useSpawnAvatar } from '../../src/world/useSpawnAvatar' + +createEngine() +startTimer() + +type Props = { + agent: AgentClient + perspective: PerspectiveProxy + source: string +} + +const Scene = (props: { url: string }) => { + useTestScene(props.url) + // useBasicScene(props.url) + // useSpawnAvatar(props.url) + useEffect(() => { + const viewer = getState(ReferenceSpaceState).viewerEntity + setComponent(viewer, CameraOrbitComponent) + setComponent(viewer, InputComponent) + + const transform = getComponent(viewer, TransformComponent) + transform.rotation.setFromAxisAngle(new Vector3(0, 1, 0), -Math.PI / 2) + + CameraOrbitComponent.setFocus( + viewer, + new Vector3(0, 1.5, 0), + new Box3().setFromCenterAndSize(new Vector3(0, 1.5, 0), new Vector3(3, 3, 3)) + ) + + const sceneURL = `${props.url}.gltf` + getMutableState(LocationState).currentLocation.location.sceneURL.set(sceneURL) + return () => { + getMutableState(LocationState).currentLocation.location.sceneURL.set('') + } + }, []) + return null +} + +const Engine = (props: Props) => { + const ref = useRef(null) + + useSpatialEngine() + useEngineCanvas(ref.current) + + const viewerEntity = useMutableState(ReferenceSpaceState).viewerEntity.value + + return ( + <> +
+ {viewerEntity && } + + ) +} + +export default function App({ agent, perspective, source }: Props) { + if (!perspective?.uuid || !agent) return
"No perspective or agent client"
+ + const [ready, setReady] = useState(false) + + useEffect(() => { + if (!agent) return + agent.me().then((me) => { + setReady(true) + getMutableState(EngineState).userID.set(me.did as UserID) + }) + }, []) + + return ( +
+ {ready && } + +
+ ) +} diff --git a/flux/src/main.ts b/flux/src/main.ts new file mode 100644 index 0000000..ebeaef0 --- /dev/null +++ b/flux/src/main.ts @@ -0,0 +1,12 @@ +import '@hookstate/core' +import * as React from 'react' +import * as ReactDOM from 'react-dom/client' +import App from './App' +import { reactToWebComponent } from './reactToWebComponent' + +const CustomElement = reactToWebComponent(App, React, ReactDOM, { + shadow: false, + observedProps: ['perspective', 'agent', 'source'] +}) + +export default CustomElement diff --git a/flux/src/reactToWebComponent.ts b/flux/src/reactToWebComponent.ts new file mode 100644 index 0000000..0d7de3b --- /dev/null +++ b/flux/src/reactToWebComponent.ts @@ -0,0 +1,229 @@ +// Minimal runtime contract for React/ReactDOM (so this file is framework-agnostic) +type ReactLike = { + createElement: (...args: any[]) => any +} +type ReactDOMLike = { + createRoot: (container: Element | DocumentFragment) => { + render: (el: any) => void + unmount: () => void + } +} + +type Options = { + /** Tag name to register (you can also call `customElements.define` yourself). */ + tagName?: string + /** Use Shadow DOM? If false, render into the element itself. */ + shadow?: boolean | ShadowRootInit + /** Attribute prefix to treat as props (e.g. data-foo -> foo). Empty = all attributes. */ + attrPrefix?: string // default: '' (accept all) + /** Element property names to expose as pass-through React props (object/class friendly). */ + observedProps?: string[] + /** Initial props (merged shallowly before anything else). */ + initialProps?: Record + /** Optional styles to inject (adoptedStyleSheets or