From 023b68d0a1b63e41840f5a56f4c78d6000ea90f5 Mon Sep 17 00:00:00 2001 From: Sacha Morgese Date: Tue, 25 Jun 2024 18:16:32 +0100 Subject: [PATCH] Updated style Fixed problems and inconsistencies Added new icon --- .../client/src/index.ts | 25 +--- package-lock.json | 1 + .../3d-web-avatar-selection-ui/package.json | 1 + .../avatar-selection-ui/AvatarSelectionUI.tsx | 6 +- .../AvatarPanel/AvatarSectionUIComponent.tsx | 134 +++++++++-------- .../AvatarSelectionUIComponent.module.css | 138 +++++++++++++++--- .../src/avatar-selection-ui/icons/Avatar.svg | 7 + .../src/avatar-selection-ui/icons/Chat.svg | 1 - .../3d-web-avatar-selection-ui/src/index.ts | 6 +- .../src/Networked3dWebExperienceClient.ts | 7 +- 10 files changed, 213 insertions(+), 113 deletions(-) create mode 100644 packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Avatar.svg delete mode 100644 packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Chat.svg diff --git a/example/multi-user-3d-web-experience/client/src/index.ts b/example/multi-user-3d-web-experience/client/src/index.ts index 3ce46b0e..7450ed98 100644 --- a/example/multi-user-3d-web-experience/client/src/index.ts +++ b/example/multi-user-3d-web-experience/client/src/index.ts @@ -27,30 +27,7 @@ const app = new Networked3dWebExperienceClient(holder, { skyboxHdrJpgUrl: hdrJpgUrl, mmlDocuments: [{ url: `${protocol}//${host}/mml-documents/example-mml.html` }], environmentConfiguration: {}, - avatarConfig: { - availableAvatars: [ - { - thumbnailUrl:"https://e7.pngegg.com/pngimages/799/987/png-clipart-computer-icons-avatar-icon-design-avatar-heroes-computer-wallpaper-thumbnail.png", - mmlCharacterUrl: "https://mmlstorage.com/eYIAFx/1706889930376.html", - name: "Avatar 1", - isDefaultAvatar: false, - }, - { - thumbnailUrl: "https://models.readyplayer.me/65a8dba831b23abb4f401bae.png", - meshFileUrl: "https://models.readyplayer.me/65a8dba831b23abb4f401bae.glb", - name: "Avatar 2", - isDefaultAvatar: true, - }, - { - thumbnailUrl: "https://static.vecteezy.com/system/resources/previews/019/896/008/original/male-user-avatar-icon-in-flat-design-style-person-signs-illustration-png.png", - mmlCharacterString: "\n" + - "", - name: "Avatar 3", - isDefaultAvatar: false, - }, - ], - allowCustomAvatars: true, - } + avatarConfig: {} }); app.update(); diff --git a/package-lock.json b/package-lock.json index 706a0bcb..1cdaa2c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17042,6 +17042,7 @@ "name": "@mml-io/3d-web-avatar-selection-ui", "version": "0.17.0", "dependencies": { + "@mml-io/3d-web-experience-client": "^0.17.0", "express": "4.19.2", "express-ws": "5.0.2", "react": "^18.2.0", diff --git a/packages/3d-web-avatar-selection-ui/package.json b/packages/3d-web-avatar-selection-ui/package.json index 24d92512..714c797a 100644 --- a/packages/3d-web-avatar-selection-ui/package.json +++ b/packages/3d-web-avatar-selection-ui/package.json @@ -18,6 +18,7 @@ "lint-fix": "eslint \"./{src,test}/**/*.{js,jsx,ts,tsx}\" --fix" }, "dependencies": { + "@mml-io/3d-web-experience-client": "^0.17.0", "express": "4.19.2", "express-ws": "5.0.2", "react": "^18.2.0", diff --git a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/AvatarSelectionUI.tsx b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/AvatarSelectionUI.tsx index 229568d2..f5e718ea 100644 --- a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/AvatarSelectionUI.tsx +++ b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/AvatarSelectionUI.tsx @@ -1,9 +1,10 @@ +import { AvatarType, CustomAvatarType } from "@mml-io/3d-web-experience-client"; import { createRef, forwardRef } from "react"; +import * as React from "react"; import { flushSync } from "react-dom"; import { createRoot, Root } from "react-dom/client"; -import * as React from "react"; + import { AvatarSelectionUIComponent } from "./components/AvatarPanel/AvatarSectionUIComponent"; -import { AvatarType, CustomAvatarType } from "@mml-io/3d-web-experience-client"; export type StringToHslOptions = { hueThresholds?: [number, number][]; @@ -29,7 +30,6 @@ export type AvatarSelectionUIProps = { visibleByDefault?: boolean; stringToHslOptions?: StringToHslOptions; availableAvatars: AvatarType[]; - selectedAvatar?: CustomAvatarType; sendMessageToServerMethod: (avatar: CustomAvatarType) => void; enableCustomAvatar?: boolean; }; diff --git a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx index 75ccea46..a836d353 100644 --- a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx +++ b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx @@ -1,12 +1,16 @@ -import React from "react"; -import { useRef, useState, ForwardRefRenderFunction, MouseEvent } from "react"; +import { AvatarType } from "@mml-io/3d-web-experience-client"; +import React, { + KeyboardEvent, + useRef, + useState, + ForwardRefRenderFunction, + MouseEvent, +} from "react"; -import { useClickOutside } from "../../helpers"; -import ChatIcon from "../../icons/Chat.svg"; import { StringToHslOptions } from "../../AvatarSelectionUI"; +import AvatarIcon from "../../icons/Avatar.svg"; import styles from "./AvatarSelectionUIComponent.module.css"; -import { AvatarType } from "@mml-io/3d-web-experience-client"; type AvatarSelectionUIProps = { onUpdateUserAvatar: (avatar: AvatarType) => void; @@ -28,80 +32,86 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction(undefined); const [customAvatarType, setCustomAvatarType] = useState<"glb" | "html" | "mml">("glb"); const [customAvatarValue, setCustomAvatarValue] = useState(""); + const inputRef = useRef(null); const handleRootClick = (e: MouseEvent) => { e.stopPropagation(); }; - const avatarPanelRef = useClickOutside(() => { - setIsVisible(false); - }); - const selectAvatar = (avatar: CustomAvatarType) => { setSelectedAvatar(avatar); props.onUpdateUserAvatar(avatar); }; + const handleInputChange = (e: React.ChangeEvent) => { + setCustomAvatarValue(e.target.value); + }; + const addCustomAvatar = () => { if (!customAvatarValue) { return; } - const selectedAvatar = { + const newSelectedAvatar = { mmlCharacterString: customAvatarType === "mml" ? customAvatarValue : undefined, mmlCharacterUrl: customAvatarType === "html" ? customAvatarValue : undefined, meshFileUrl: customAvatarType === "glb" ? customAvatarValue : undefined, isCustomAvatar: true, } as CustomAvatarType; - setSelectedAvatar(selectedAvatar); - props.onUpdateUserAvatar(selectedAvatar); + setSelectedAvatar(newSelectedAvatar); + props.onUpdateUserAvatar(newSelectedAvatar); + }; + + const handleKeyPress = (e: KeyboardEvent) => { + e.stopPropagation(); }; return ( <>
-
setIsVisible(true)}> - -
-
-
-
-
-

Choose your avatar

- + {!isVisible && ( +
setIsVisible(true)}> +
-
- {props.availableAvatars.map((avatar, index) => { - const isSelected = - !selectedAvatar?.isCustomAvatar && - ((selectedAvatar?.meshFileUrl && - selectedAvatar?.meshFileUrl === avatar.meshFileUrl) || - (selectedAvatar?.mmlCharacterUrl && - selectedAvatar?.mmlCharacterUrl === avatar.mmlCharacterUrl) || - (selectedAvatar?.mmlCharacterString && - selectedAvatar?.mmlCharacterString === avatar.mmlCharacterString)); + )} +
+ {isVisible && ( +
+
+
+

Choose your avatar

+ +
+
+ {props.availableAvatars.map((avatar, index) => { + const isSelected = + !selectedAvatar?.isCustomAvatar && + ((selectedAvatar?.meshFileUrl && + selectedAvatar?.meshFileUrl === avatar.meshFileUrl) || + (selectedAvatar?.mmlCharacterUrl && + selectedAvatar?.mmlCharacterUrl === avatar.mmlCharacterUrl) || + (selectedAvatar?.mmlCharacterString && + selectedAvatar?.mmlCharacterString === avatar.mmlCharacterString)); - return ( -
selectAvatar(avatar)} - > - {avatar.name} -

{avatar.name}

-
- ); - })} + return ( +
selectAvatar(avatar)} + > + {avatar.name} +

{avatar.name}

+
+ ); + })} +
{props.enableCustomAvatar && (
@@ -133,14 +143,18 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction - setCustomAvatarValue(value)} - /> - +
+ + +
{selectedAvatar?.isCustomAvatar && (

Custom Avatar Selected

@@ -149,7 +163,7 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction )}
-
+ )} ); }; diff --git a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css index b9a227da..734b768c 100644 --- a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css +++ b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css @@ -3,31 +3,88 @@ min-height: 70px; position: fixed; top: 12px; - left: 12px; + right: 12px; z-index: 102; + user-select: none; } +.openTab { + background-color: rgba(0, 0, 0, 0.8); + border-radius: 50%; + width: 42px; + height: 42px; + position: fixed; + top: 12px; + right: 12px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.8); + border: 1px solid rgba(255, 255, 255, 0.21); + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; +} + +.openTab img { + filter: invert(60%); + width: 35px; + height: 35px; + transition: all 0.3s ease-in-out; + position: relative; + top: 3px; + left: 4px; + transform: scale(0.7); +} .avatarSelectionContainer { - position: absolute; - display: flex; - top: 0; - left: 0; - right: 0; - bottom: 0; - justify-content: center; - align-items: center; + position: fixed; + top: 11px; + right: 60px; + width: 30%; + max-width: 720px; + padding: 8px; + background: #000000b3; + border-radius: 8px; + font-family: "Helvetica", "Lucida Sans", "Lucida Sans Regular", "Lucida Grande", + "Lucida Sans Unicode", Geneva, Verdana, sans-serif; + z-index: 103; + user-select: none; + padding-bottom: 20px; } .avatarSelectionUi { - width: 95%; - height: 90%; - max-width: 1200px; - border-radius: 10px; - background: #000; - padding: 7px; - opacity: 0.7; + padding-right: 4px; overflow-y: scroll; + max-height: 630px; +} + +.avatarSelectionUi::-webkit-scrollbar { + -webkit-appearance: none; + width: 10px; +} + +.avatarSelectionUi::-webkit-scrollbar-thumb { + margin: 4px; + border-radius: 5px; + background-color: #ffffff90; + box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); +} + +.closeButton { + font-family: "arial black"; + background: #000000b3; + color: #ffffff90; + border-radius: 50%; + width: 42px; + height: 42px; + position: fixed; + top: 12px; + right: 12px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.8); + border: 1px solid rgba(255, 255, 255, 0.21); + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; } .avatarSelectionUiHeader { @@ -46,11 +103,12 @@ padding: 10px; margin-top: 20px; display: flex; - justify-content: space-between; + gap: 4%; + flex-wrap: wrap; } .avatarSelectionUiAvatar { - flex: 0 0 32%; + flex: 0 0 22%; aspect-ratio: 1; position: relative; color: #ffffff; @@ -60,7 +118,7 @@ .avatarSelectionUiAvatar img { aspect-ratio: 1; - width: 100% + width: 100%; } .selectedAvatar { @@ -82,7 +140,47 @@ text-align: center; } +.customAvatarSection label { + font-size: 20px; + margin-right: 8px; +} + +.customAvatarSection input[type="radio"] { + transform: scale(1.5); + margin-right: 4px; +} + +.customAvatarInputSection { + margin-top: 10px; + display: flex; +} + .customAvatarInput { + font-family: 'Helvetica', 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; + font-size: 15px; + flex: 1; + padding: 10px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.5); + margin-right: 8px; + color: #000000; + display: inline-block; + width: 40%; + background-color: #ffffff; +} + +.customAvatarInputSection button { + width: 60px; + border-radius: 4px; + border: 0; + color: #ffffff; + background-color: #ffffff90; + box-sizing: border-box; + cursor: pointer; + padding: 0; display: block; - width: 20%; +} + +.customAvatarInputSection button[disabled] { + background-color: #ffffff50; } \ No newline at end of file diff --git a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Avatar.svg b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Avatar.svg new file mode 100644 index 00000000..657bb6c3 --- /dev/null +++ b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Avatar.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Chat.svg b/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Chat.svg deleted file mode 100644 index e8eb6b4b..00000000 --- a/packages/3d-web-avatar-selection-ui/src/avatar-selection-ui/icons/Chat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/3d-web-avatar-selection-ui/src/index.ts b/packages/3d-web-avatar-selection-ui/src/index.ts index 7c0fe75b..39ae11f6 100644 --- a/packages/3d-web-avatar-selection-ui/src/index.ts +++ b/packages/3d-web-avatar-selection-ui/src/index.ts @@ -1 +1,5 @@ -export { AvatarSelectionUI, AvatarSelectionUIProps, type StringToHslOptions } from "./avatar-selection-ui/AvatarSelectionUI"; \ No newline at end of file +export { + AvatarSelectionUI, + AvatarSelectionUIProps, + type StringToHslOptions, +} from "./avatar-selection-ui/AvatarSelectionUI"; diff --git a/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts b/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts index e0e93a4d..2541f89c 100644 --- a/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts +++ b/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts @@ -1,3 +1,4 @@ +import { AvatarSelectionUI } from "@mml-io/3d-web-avatar-selection-ui"; import { AnimationConfig, CameraManager, @@ -45,7 +46,6 @@ import { setGlobalMMLScene, } from "mml-web"; import { AudioListener, Euler, Scene, Vector3 } from "three"; -import { AvatarSelectionUI } from "@mml-io/3d-web-avatar-selection-ui"; type MMLDocumentConfiguration = { url: string; @@ -74,7 +74,7 @@ export type AvatarType = mmlCharacterString?: null; mmlCharacterUrl?: null; isDefaultAvatar?: boolean; - } + } | { thumbnailUrl?: string; name?: string; @@ -93,7 +93,7 @@ export type AvatarType = }; type AvatarConfig = { - availableAvatars: Array; + availableAvatars?: Array; allowCustomAvatars?: boolean; customAvatarWebhookUrl?: string; }; @@ -159,7 +159,6 @@ export class Networked3dWebExperienceClient { private errorScreen?: ErrorScreen; private currentRequestAnimationFrame: number | null = null; - constructor( private holderElement: HTMLElement, private config: Networked3dWebExperienceClientConfig,