diff --git a/package-lock.json b/package-lock.json
index e900c91..c7d1f18 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,6 +6,10 @@
"": {
"name": "mml-website",
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.4.2",
+ "@fortawesome/free-regular-svg-icons": "^6.4.2",
+ "@fortawesome/free-solid-svg-icons": "^6.4.2",
+ "@fortawesome/react-fontawesome": "^0.2.0",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@mml-io/3d-web-client-core": "0.9.0",
@@ -817,6 +821,63 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
+ "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
+ "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-regular-svg-icons": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
+ "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
+ "integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/react-fontawesome": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
+ "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6",
+ "react": ">=16.3"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
@@ -30318,6 +30379,43 @@
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz",
"integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng=="
},
+ "@fortawesome/fontawesome-common-types": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
+ "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA=="
+ },
+ "@fortawesome/fontawesome-svg-core": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
+ "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ }
+ },
+ "@fortawesome/free-regular-svg-icons": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
+ "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ }
+ },
+ "@fortawesome/free-solid-svg-icons": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
+ "integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.4.2"
+ }
+ },
+ "@fortawesome/react-fontawesome": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
+ "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
+ "requires": {
+ "prop-types": "^15.8.1"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
diff --git a/package.json b/package.json
index 521a82a..15201bd 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,10 @@
}
},
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.4.2",
+ "@fortawesome/free-regular-svg-icons": "^6.4.2",
+ "@fortawesome/free-solid-svg-icons": "^6.4.2",
+ "@fortawesome/react-fontawesome": "^0.2.0",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@mml-io/3d-web-client-core": "0.9.0",
@@ -48,16 +52,17 @@
"@monaco-editor/react": "^4.5.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
+ "@types/codemirror": "5.60.10",
+ "@types/js-beautify": "^1.14.1",
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
- "@types/three": "^0.153.0",
"@types/react-syntax-highlighter": "^15.5.8",
- "@types/codemirror": "5.60.10",
- "@types/js-beautify": "^1.14.1",
+ "@types/three": "^0.153.0",
"@typescript-eslint/eslint-plugin": "5.59.2",
"@typescript-eslint/parser": "5.59.2",
"autoprefixer": "^10.4.14",
"canvas": "^2.11.2",
+ "cross-env": "7.0.3",
"eslint-config-prettier": "8.8.0",
"eslint-config-standard": "17.0.0",
"eslint-import-resolver-typescript": "3.5.5",
@@ -75,7 +80,6 @@
"prettier-plugin-tailwindcss": "^0.2.8",
"raw-loader": "^4.0.2",
"tailwindcss": "^3.3.2",
- "typescript": "5.0.4",
- "cross-env": "7.0.3"
+ "typescript": "5.0.4"
}
-}
\ No newline at end of file
+}
diff --git a/src/components/AnimatedExampleView/AnimatedExampleView.tsx b/src/components/AnimatedExampleView/AnimatedExampleView.tsx
index e8d6ba8..2d583d5 100644
--- a/src/components/AnimatedExampleView/AnimatedExampleView.tsx
+++ b/src/components/AnimatedExampleView/AnimatedExampleView.tsx
@@ -6,10 +6,10 @@ import * as React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { AnimatedEditorContainer, CodemirrorEditor } from "@/src/components/AnimatedEditor";
-import { ExampleAvatarClient } from "@/src/components/AnimatedExampleView/ExampleAvatarClient";
import { LocalAvatarServer } from "@/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer";
+import { ExampleAvatarClient } from "@/src/components/ExampleView/ExampleAvatarClient";
-import { ExampleClient } from "./ExampleClient";
+import { ExampleFloatingClient } from "../ExampleView/ExampleFloatingClient";
function createDocumentCode(code: string): string {
return `${''}${code}`;
@@ -103,7 +103,7 @@ export function AnimatedExampleView() {
{networkedDOMDocument && (
<>
-
+
-
+
>
)}
diff --git a/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer.ts b/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer.ts
index 3857313..66b1f79 100644
--- a/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer.ts
+++ b/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer.ts
@@ -23,5 +23,9 @@ export class LocalAvatarServer {
removeClient(clientId: number) {
this.callbacks.delete(clientId);
+
+ this.callbacks.forEach((callback) => {
+ callback(clientId, null);
+ });
}
}
diff --git a/src/components/ExampleView/DocsExampleView.tsx b/src/components/ExampleView/DocsExampleView.tsx
index 2c5d572..1e1cc28 100644
--- a/src/components/ExampleView/DocsExampleView.tsx
+++ b/src/components/ExampleView/DocsExampleView.tsx
@@ -1,36 +1,41 @@
"use client";
+import { faArrowRotateRight } from "@fortawesome/free-solid-svg-icons/faArrowRotateRight";
+import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EditableNetworkedDOM } from "@mml-io/networked-dom-document";
import { IframeObservableDOMFactory } from "@mml-io/networked-dom-web-runner";
import * as React from "react";
import { useCallback, useEffect, useState } from "react";
+import { twMerge } from "tailwind-merge";
-import { ExampleClient } from "@/src/components/AnimatedExampleView/ExampleClient";
+import ExampleClientsSection from "@/src/components/ExampleView/ExampleClientsSection";
import HTMLEditor from "@/src/components/ExampleView/HTMLEditor";
+import { getClientIdFunctionGenerator } from "@/src/util/clients-utils";
+import { CLIENT_TYPES, ClientType } from "@/types/docs-reference";
-function createDocumentCode(code: string, lightOn: boolean): string {
- return `${
- lightOn &&
- ''
- }${code}`;
-}
+const getNextClientId = getClientIdFunctionGenerator();
export type DocsExampleViewProps = {
code: string;
- initialClientCount?: number;
- baseScene: boolean;
+ initialClients: ClientType[];
+ baseScene?: boolean;
description: string;
- showClientsControls?: boolean;
};
export function DocsExampleView(props: DocsExampleViewProps) {
+ const [clients, setClients] = useState<{ type: ClientType; id: number }[]>(() =>
+ props.initialClients.map((type) => ({
+ type,
+ id: getNextClientId(),
+ })),
+ );
+
const [code, setCode] = useState(props.code);
const [networkedDOMDocument, setNetworkedDOMDocument] = useState(
null,
);
- const [clients, setClients] = useState([
- ...Array(props.initialClientCount ?? 1).keys(),
- ]);
+ const [showAddButtons, setShowAddButtons] = useState(false);
const { baseScene } = props;
@@ -40,7 +45,7 @@ export function DocsExampleView(props: DocsExampleViewProps) {
IframeObservableDOMFactory,
true,
);
- document.load(createDocumentCode(code, baseScene));
+ document.load(code);
setNetworkedDOMDocument(document);
return () => {
@@ -49,40 +54,50 @@ export function DocsExampleView(props: DocsExampleViewProps) {
}, []);
useEffect(() => {
- networkedDOMDocument?.load(createDocumentCode(code, baseScene));
- }, [code, baseScene]);
+ networkedDOMDocument?.load(code);
+ }, [code]);
const handleResetClick = useCallback(() => {
setCode(props.code);
}, []);
- const addNewClient = () => {
- setClients((oldClients) => [...oldClients, oldClients.length]);
+ const handlePlusButton = () => {
+ if (clients.length >= 4) return;
+ setShowAddButtons((prev) => !prev);
};
- const removeClient = () => {
- setClients((oldClients) => oldClients.slice(0, oldClients.length - 1));
+ const addNewClient = (clientType: ClientType) => {
+ if (clients.length >= 4) return;
+ const newClient = {
+ type: clientType,
+ id: getNextClientId(),
+ };
+ setClients((oldClients) => [...oldClients, newClient]);
+ setShowAddButtons(false);
+ };
+
+ const removeClient = (elemId: number) => {
+ setClients((oldClients) => oldClients.filter(({ id }) => id !== elemId));
};
return (
- <>
-
+
+
{props.description}
-
+
+
+
+
-
+
CODE
@@ -90,33 +105,29 @@ export function DocsExampleView(props: DocsExampleViewProps) {
-
- {networkedDOMDocument && (
- <>
- {clients.map((clientId, index) => {
- const isLast = index === clients.length - 1 && index !== 0;
- return (
-
- {props.showClientsControls && (isLast || clientId === 0) && (
-
- )}
-
- );
- })}
- >
- )}
-
+
- >
+ {showAddButtons && (
+
+
+
+
+ )}
+
);
}
diff --git a/src/components/ExampleView/DocsExampleViewDynamic.tsx b/src/components/ExampleView/DocsExampleViewDynamic.tsx
index e1d811f..48fc6ca 100644
--- a/src/components/ExampleView/DocsExampleViewDynamic.tsx
+++ b/src/components/ExampleView/DocsExampleViewDynamic.tsx
@@ -34,5 +34,9 @@ export default function ExampleView(props: DocsExampleViewProps) {
return () => observer.disconnect();
}, []);
- return
{isVisible && }
;
+ return (
+
+ {isVisible && }
+
+ );
}
diff --git a/src/components/AnimatedExampleView/ExampleAvatarClient.tsx b/src/components/ExampleView/ExampleAvatarClient.tsx
similarity index 70%
rename from src/components/AnimatedExampleView/ExampleAvatarClient.tsx
rename to src/components/ExampleView/ExampleAvatarClient.tsx
index ea3611d..5714ecf 100644
--- a/src/components/AnimatedExampleView/ExampleAvatarClient.tsx
+++ b/src/components/ExampleView/ExampleAvatarClient.tsx
@@ -3,14 +3,11 @@ import * as React from "react";
import { useEffect, useState } from "react";
import { Euler, Vector3 } from "three";
+import ExampleClientView from "@/src/components/ExampleView/ExampleClientView";
import { getIframeTargetWindow } from "@/src/util/iframe-target";
-import { LocalAvatarClient } from "./LocalAvatar/LocalAvatarClient";
-import { LocalAvatarServer } from "./LocalAvatar/LocalAvatarServer";
-
-function Container(props: { refProp: React.Ref
}) {
- return ;
-}
+import { LocalAvatarClient } from "../AnimatedExampleView/LocalAvatar/LocalAvatarClient";
+import { LocalAvatarServer } from "../AnimatedExampleView/LocalAvatar/LocalAvatarServer";
export const ExampleAvatarClient = React.memo(function ExampleAvatarClient(props: {
server: LocalAvatarServer;
@@ -18,6 +15,7 @@ export const ExampleAvatarClient = React.memo(function ExampleAvatarClient(props
clientId: number;
position: { x: number; y: number; z: number };
rotation: { x: number; y: number; z: number };
+ children?: React.ReactNode;
}) {
const [client, setClient] = useState(null);
const elementRef = React.useRef(null);
@@ -50,6 +48,7 @@ export const ExampleAvatarClient = React.memo(function ExampleAvatarClient(props
useEffect(() => {
if (elementRef.current && client) {
+ client.element.setAttribute("style", "height: 100%");
elementRef.current.appendChild(client.element);
}
}, [elementRef.current, client]);
@@ -58,14 +57,5 @@ export const ExampleAvatarClient = React.memo(function ExampleAvatarClient(props
return null;
}
- return (
- <>
-
-
- Client {props.clientId + 1}
-
-
-
- >
- );
+ return {props.children};
});
diff --git a/src/components/ExampleView/ExampleClientView.tsx b/src/components/ExampleView/ExampleClientView.tsx
new file mode 100644
index 0000000..49d6742
--- /dev/null
+++ b/src/components/ExampleView/ExampleClientView.tsx
@@ -0,0 +1,14 @@
+import * as React from "react";
+
+export default function ExampleClientView(props: {
+ children?: React.ReactNode;
+ elementRef: React.Ref;
+}) {
+ const { children, elementRef } = props;
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/ExampleView/ExampleClientsSection.tsx b/src/components/ExampleView/ExampleClientsSection.tsx
new file mode 100644
index 0000000..a4e2611
--- /dev/null
+++ b/src/components/ExampleView/ExampleClientsSection.tsx
@@ -0,0 +1,87 @@
+import { EditableNetworkedDOM } from "@mml-io/networked-dom-document";
+import * as React from "react";
+import { useRef } from "react";
+
+import { LocalAvatarServer } from "@/src/components/AnimatedExampleView/LocalAvatar/LocalAvatarServer";
+import { ExampleAvatarClient } from "@/src/components/ExampleView/ExampleAvatarClient";
+import { ExampleFloatingClient } from "@/src/components/ExampleView/ExampleFloatingClient";
+import { CLIENT_TYPES, ClientType } from "@/types/docs-reference";
+
+type ExampleClientsSectionProps = {
+ clients: { type: ClientType; id: number }[];
+ removeClient?: (index: number) => void;
+ networkedDOMDocument: EditableNetworkedDOM | null;
+ sectionWidth?: string;
+ baseScene?: boolean;
+ hideButtons?: boolean;
+};
+
+export default function ExampleClientsSection({
+ clients,
+ removeClient,
+ networkedDOMDocument,
+ sectionWidth = "50%",
+ baseScene,
+ hideButtons,
+}: ExampleClientsSectionProps) {
+ const server = useRef(new LocalAvatarServer());
+
+ // Determine the number of columns and rows based on the number of clients
+ const columns = clients.length < 3 ? 1 : 2; // We want one or two clients per row
+ const rows = Math.min(clients.length, 2); // Calculate the number of rows needed
+
+ const gridStyle = {
+ display: "grid",
+ gridTemplateColumns: `repeat(${columns}, 1fr)`, // Create as many columns as needed
+ gridTemplateRows: `repeat(${rows}, 1fr)`, // Create as many rows as needed
+ height: "100%",
+ width: "100%",
+ };
+
+ return (
+
+ {networkedDOMDocument && (
+
+ {clients.map(({ type, id }, index) => {
+ const children = hideButtons ? null : (
+
+ );
+
+ const avatarPositionForIndex = {
+ x: [0, 2].includes(index) ? -1 : 1,
+ y: 0.5,
+ z: [0, 1].includes(index) ? 5 : 7,
+ };
+
+ return type === CLIENT_TYPES.FLOATING ? (
+
+ ) : (
+
+ );
+ })}
+
+ )}
+
+ );
+}
diff --git a/src/components/AnimatedExampleView/ExampleClient.tsx b/src/components/ExampleView/ExampleFloatingClient.tsx
similarity index 66%
rename from src/components/AnimatedExampleView/ExampleClient.tsx
rename to src/components/ExampleView/ExampleFloatingClient.tsx
index 0f6934a..2b9946d 100644
--- a/src/components/AnimatedExampleView/ExampleClient.tsx
+++ b/src/components/ExampleView/ExampleFloatingClient.tsx
@@ -3,18 +3,16 @@ import { MMLScene } from "mml-web";
import { MMLWebRunnerClient } from "mml-web-runner";
import * as React from "react";
import { useEffect, useState } from "react";
+import * as THREE from "three";
+import ExampleClientView from "@/src/components/ExampleView/ExampleClientView";
import { getIframeTargetWindow } from "@/src/util/iframe-target";
-function Container(props: { refProp: React.Ref; clientHeight: number }) {
- return ;
-}
-
-export const ExampleClient = React.memo(function ExampleClient(props: {
+export const ExampleFloatingClient = React.memo(function ExampleClient(props: {
document: NetworkedDOM | EditableNetworkedDOM;
clientId: number;
children?: React.ReactNode;
- clientsNumber?: number;
+ baseScene?: boolean;
}) {
const [clientState, setClientState] = useState<{
client: MMLWebRunnerClient;
@@ -32,6 +30,19 @@ export const ExampleClient = React.memo(function ExampleClient(props: {
return;
}
mmlScene = new MMLScene();
+ if (props.baseScene) {
+ const pointLight = new THREE.PointLight(0xffffff, 1, 100);
+ pointLight.position.set(10, 10, 10);
+ pointLight.castShadow = true;
+ const plane = new THREE.Mesh(
+ new THREE.PlaneGeometry(20, 20),
+ new THREE.MeshStandardMaterial({ color: 0xffffff }),
+ );
+ plane.receiveShadow = true;
+ plane.rotation.x = -Math.PI / 2;
+ mmlScene.getThreeScene().add(plane);
+ mmlScene.getThreeScene().add(pointLight);
+ }
runnerClient = new MMLWebRunnerClient(wrapper.iframeWindow, wrapper.iframeBody, mmlScene);
runnerClient.connect(props.document);
setClientState({ client: runnerClient, scene: mmlScene });
@@ -62,19 +73,5 @@ export const ExampleClient = React.memo(function ExampleClient(props: {
clientState?.scene.fitContainer();
}, 1);
- const { children, clientsNumber = 1 } = props;
-
- const clientHeight = Math.floor(368 / clientsNumber);
-
- return (
- <>
-
- {children}
-
- Client
-
-
-
- >
- );
+ return {props.children};
});
diff --git a/src/components/ExampleView/ExamplePageExampleView.tsx b/src/components/ExampleView/ExamplePageExampleView.tsx
index 548d0a1..4b291fb 100644
--- a/src/components/ExampleView/ExamplePageExampleView.tsx
+++ b/src/components/ExampleView/ExamplePageExampleView.tsx
@@ -3,28 +3,29 @@ import { IframeObservableDOMFactory } from "@mml-io/networked-dom-web-runner";
import * as React from "react";
import { useCallback, useEffect, useState } from "react";
-import { ExampleClient } from "@/src/components/AnimatedExampleView/ExampleClient";
+import ExampleClientsSection from "@/src/components/ExampleView/ExampleClientsSection";
import HTMLEditor from "@/src/components/ExampleView/HTMLEditor";
+import { getClientIdFunctionGenerator } from "@/src/util/clients-utils";
+import { CLIENT_TYPES, ClientType } from "@/types/docs-reference";
-function createDocumentCode(code: string, lightOn: boolean): string {
- return `${
- lightOn &&
- ''
- }${code}`;
-}
+const getNextClientId = getClientIdFunctionGenerator();
export function ExamplePageExampleView(props: {
code: string;
- initialClientCount?: number;
+ initialClients?: ClientType[];
baseScene: boolean;
description: string;
}) {
+ const { baseScene, initialClients = [CLIENT_TYPES.FLOATING] } = props;
const [code, setCode] = useState(props.code);
const [networkedDOMDocument, setNetworkedDOMDocument] = useState(
null,
);
- const clients = [...Array(props.initialClientCount || 1).keys()];
- const { baseScene } = props;
+
+ const clients = initialClients.map((type) => ({
+ type,
+ id: getNextClientId(),
+ }));
useEffect(() => {
const document = new EditableNetworkedDOM(
@@ -32,7 +33,7 @@ export function ExamplePageExampleView(props: {
IframeObservableDOMFactory,
true,
);
- document.load(createDocumentCode(code, baseScene));
+ document.load(code);
setNetworkedDOMDocument(document);
return () => {
@@ -41,7 +42,7 @@ export function ExamplePageExampleView(props: {
}, []);
useEffect(() => {
- networkedDOMDocument?.load(createDocumentCode(code, baseScene));
+ networkedDOMDocument?.load(code);
}, [code, baseScene]);
const handleResetClick = useCallback(() => {
@@ -78,17 +79,13 @@ export function ExamplePageExampleView(props: {
setCode={setCode}
/>
-
- {networkedDOMDocument && (
- <>
- {clients.map((clientId) => {
- return (
-
- );
- })}
- >
- )}
-
+
>
);
diff --git a/src/components/ExampleView/ExamplePageExampleViewDynamic.tsx b/src/components/ExampleView/ExamplePageExampleViewDynamic.tsx
index 99b243f..8516c14 100644
--- a/src/components/ExampleView/ExamplePageExampleViewDynamic.tsx
+++ b/src/components/ExampleView/ExamplePageExampleViewDynamic.tsx
@@ -1,10 +1,12 @@
import dynamic from "next/dynamic";
+import { ClientType } from "@/types/docs-reference";
+
type ExampleViewProps = {
code: string;
+ initialClients: ClientType[];
baseScene?: boolean;
- initialClientCount?: number;
- description?: string;
+ description: string;
};
const ExamplePageExampleViewStatic = dynamic>(
@@ -15,18 +17,6 @@ const ExamplePageExampleViewStatic = dynamic>(
{ ssr: false },
);
-export default function ExamplePageExampleView({
- code,
- description,
- baseScene,
- initialClientCount,
-}: ExampleViewProps) {
- return (
-
- );
+export default function ExamplePageExampleView(props: ExampleViewProps) {
+ return ;
}
diff --git a/src/content/blogPosts/introducing-mml/body.mdx b/src/content/blogPosts/introducing-mml/body.mdx
index 714109c..40d6877 100644
--- a/src/content/blogPosts/introducing-mml/body.mdx
+++ b/src/content/blogPosts/introducing-mml/body.mdx
@@ -1,5 +1,6 @@
import * as React from "react";
import DocsExampleView from "@/src/components/ExampleView/DocsExampleViewDynamic";
+import { CLIENT_TYPES } from "@/types/docs-reference";
# Introducing MML: An Open Source Metaverse Markup Language for Multi-User Interactive 3D Experiences
_[Marcus Longmuir](https://twitter.com/marcuslongmuir) - 2023-06-15_
@@ -54,7 +55,7 @@ demonstrating one document seen by multiple clients complete with live editing.
some attributes:
-
+
diff --git a/src/content/docs/connectionEvent/index.ts b/src/content/docs/connectionEvent/index.ts
index 1cd2746..9cc1f87 100644
--- a/src/content/docs/connectionEvent/index.ts
+++ b/src/content/docs/connectionEvent/index.ts
@@ -1,4 +1,4 @@
-import { DocsExamples } from "@/types/docs-reference";
+import { CLIENT_TYPES, DocsExamples } from "@/types/docs-reference";
import disconnected from "./disconnected.mml";
import primary from "./primary.mml";
@@ -9,21 +9,18 @@ export const examples: DocsExamples = {
title: "Basic connection event",
description: "A label that shows the client ids that have connected.",
code: primary,
- clientsNumber: 2,
- showClientsControls: true,
+ clients: [CLIENT_TYPES.FLOATING, CLIENT_TYPES.FLOATING],
},
test: {
title: "Basic disconnection event",
description: "A label that shows the client ids that have disconnected.",
code: disconnected,
- clientsNumber: 2,
- showClientsControls: true,
+ clients: [CLIENT_TYPES.FLOATING, CLIENT_TYPES.FLOATING],
},
visibleTo: {
title: "Visible to event",
description: "A label that shows only the client id of one specific client",
code: visibleTo,
- clientsNumber: 2,
- showClientsControls: true,
+ clients: [CLIENT_TYPES.FLOATING, CLIENT_TYPES.FLOATING],
},
};
diff --git a/src/content/docs/m-light/point.mml b/src/content/docs/m-light/point.mml
index 4b81302..9c744df 100644
--- a/src/content/docs/m-light/point.mml
+++ b/src/content/docs/m-light/point.mml
@@ -10,4 +10,4 @@
-
+
diff --git a/src/content/docs/m-light/primary.mml b/src/content/docs/m-light/primary.mml
index c05e3c7..5d10330 100644
--- a/src/content/docs/m-light/primary.mml
+++ b/src/content/docs/m-light/primary.mml
@@ -11,4 +11,4 @@
-
+
diff --git a/src/content/docs/m-light/spotlight.mml b/src/content/docs/m-light/spotlight.mml
index 2fa6530..d382966 100644
--- a/src/content/docs/m-light/spotlight.mml
+++ b/src/content/docs/m-light/spotlight.mml
@@ -13,4 +13,4 @@
-
+
diff --git a/src/content/docs/mmlClickEvent/index.ts b/src/content/docs/mmlClickEvent/index.ts
index 700522d..661237f 100644
--- a/src/content/docs/mmlClickEvent/index.ts
+++ b/src/content/docs/mmlClickEvent/index.ts
@@ -1,4 +1,4 @@
-import { DocsExamples } from "@/types/docs-reference";
+import { CLIENT_TYPES, DocsExamples } from "@/types/docs-reference";
import clickedBy from "./clicked-by.mml";
import position from "./position.mml";
@@ -14,8 +14,7 @@ export const examples: DocsExamples = {
title: "Clicked by",
description: "This example shows which client clicked the cube.",
code: clickedBy,
- clientsNumber: 2,
- showClientsControls: true,
+ clients: [CLIENT_TYPES.FLOATING, CLIENT_TYPES.FLOATING],
},
position: {
title: "Position",
diff --git a/src/content/docs/mmlCollisionStartEvent/index.ts b/src/content/docs/mmlCollisionStartEvent/index.ts
index c4db6f1..5cdc890 100644
--- a/src/content/docs/mmlCollisionStartEvent/index.ts
+++ b/src/content/docs/mmlCollisionStartEvent/index.ts
@@ -1,3 +1,12 @@
-import { DocsExamples } from "@/types/docs-reference";
+import primary from "@/src/content/docs/mmlCollisionStartEvent/primary.mml";
+import { CLIENT_TYPES, DocsExamples } from "@/types/docs-reference";
-export const examples: DocsExamples = {};
+export const examples: DocsExamples = {
+ primary: {
+ title: "Collision start event",
+ description: "The label shows the connection id of the user colliding with the platform.",
+ code: primary,
+ clients: [CLIENT_TYPES.AVATAR, CLIENT_TYPES.AVATAR],
+ baseSceneOn: false,
+ },
+};
diff --git a/src/content/docs/mmlCollisionStartEvent/primary.mml b/src/content/docs/mmlCollisionStartEvent/primary.mml
new file mode 100644
index 0000000..202d2a1
--- /dev/null
+++ b/src/content/docs/mmlCollisionStartEvent/primary.mml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/src/pages/docs/reference/elements/[reference-id].tsx b/src/pages/docs/reference/elements/[reference-id].tsx
index c737efd..f86f50d 100644
--- a/src/pages/docs/reference/elements/[reference-id].tsx
+++ b/src/pages/docs/reference/elements/[reference-id].tsx
@@ -12,6 +12,7 @@ import ExampleView from "@/src/components/ExampleView/DocsExampleViewDynamic";
import { MarkDown } from "@/src/config/mdx";
import * as docsExamples from "@/src/content/docs";
import { getPageTitle } from "@/src/util";
+import { CLIENT_TYPES } from "@/types/docs-reference";
// This function gets called at build time to generate all the files
export function getStaticPaths() {
@@ -86,13 +87,13 @@ const DocsPage = ({ referenceId }: { referenceId: string }) => {
Try it
>
)}
@@ -141,7 +142,7 @@ const DocsPage = ({ referenceId }: { referenceId: string }) => {
description={example.title}
baseScene={example.baseSceneOn !== undefined ? example.baseSceneOn : true}
code={example.code}
- initialClientCount={1}
+ initialClients={example.clients ?? [CLIENT_TYPES.FLOATING]}
/>
);
diff --git a/src/pages/docs/reference/events/[event-id].tsx b/src/pages/docs/reference/events/[event-id].tsx
index 844bf7b..c73a99d 100644
--- a/src/pages/docs/reference/events/[event-id].tsx
+++ b/src/pages/docs/reference/events/[event-id].tsx
@@ -15,6 +15,7 @@ import { MarkDown } from "@/src/config/mdx";
import * as docsExamples from "@/src/content/docs";
import { getPageTitle } from "@/src/util";
import { eventClasses } from "@/src/util/event-classes";
+import { CLIENT_TYPES } from "@/types/docs-reference";
// This function gets called at build time to generate all the files
export function getStaticPaths() {
@@ -110,21 +111,19 @@ const DocsPage = ({ eventId }: { eventId: string }) => {
)}
{primaryExample && (
- <>
+
Try it
- >
+
)}
Properties
@@ -186,8 +185,7 @@ const DocsPage = ({ eventId }: { eventId: string }) => {
description={example.title}
baseScene={example.baseSceneOn !== undefined ? example.baseSceneOn : true}
code={example.code}
- initialClientCount={example?.clientsNumber ?? 1}
- showClientsControls={example?.showClientsControls}
+ initialClients={example.clients ?? [CLIENT_TYPES.FLOATING]}
/>
);
diff --git a/src/pages/examples.tsx b/src/pages/examples.tsx
index e24d600..2d8bd9f 100644
--- a/src/pages/examples.tsx
+++ b/src/pages/examples.tsx
@@ -8,6 +8,7 @@ import { ChangeEvent, useEffect } from "react";
import ExampleView from "@/src/components/ExampleView/ExamplePageExampleViewDynamic";
import { examples } from "@/src/content/examples";
import { getPageTitle } from "@/src/util";
+import { CLIENT_TYPES } from "@/types/docs-reference";
import { Example, ExamplesByName } from "@/types/example";
const ExamplesPage = () => {
@@ -109,7 +110,7 @@ const ExamplesPage = () => {
{
+ return clientId++;
+ };
+}
diff --git a/types/docs-reference.ts b/types/docs-reference.ts
index 53ed952..9caec1a 100644
--- a/types/docs-reference.ts
+++ b/types/docs-reference.ts
@@ -1,10 +1,17 @@
+// Technically not a type but I'm not sure where to put this constant
+export const CLIENT_TYPES = {
+ FLOATING: "floating",
+ AVATAR: "avatar",
+} as const;
+
+export type ClientType = (typeof CLIENT_TYPES)[keyof typeof CLIENT_TYPES];
+
export type DocsReference = {
title: string;
description: string;
code: string;
baseSceneOn?: boolean;
- clientsNumber?: number;
- showClientsControls?: boolean;
+ clients?: ClientType[];
};
export type DocsExamples = {
diff --git a/types/example.ts b/types/example.ts
index 66d476f..2682966 100644
--- a/types/example.ts
+++ b/types/example.ts
@@ -1,9 +1,12 @@
+import { ClientType } from "@/types/docs-reference";
+
export type Example = {
name: string;
image: string;
description: string;
code: string;
baseScene?: boolean;
+ clients?: ClientType[];
};
export type ExamplesByName = {