Skip to content

Commit

Permalink
Updated style
Browse files Browse the repository at this point in the history
Fixed problems and inconsistencies
Added new icon
  • Loading branch information
sachamorgese committed Jun 25, 2024
1 parent d1f39e1 commit 023b68d
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 113 deletions.
25 changes: 1 addition & 24 deletions example/multi-user-3d-web-experience/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<m-character src=\"https://mmlstorage.com/fca2e81688f8c26b1671b701e399f0a5c9756307607d78c11739293d2e530e78\">\n" +
"</m-character>",
name: "Avatar 3",
isDefaultAvatar: false,
},
],
allowCustomAvatars: true,
}
avatarConfig: {}
});

app.update();
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/3d-web-avatar-selection-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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][];
Expand All @@ -29,7 +30,6 @@ export type AvatarSelectionUIProps = {
visibleByDefault?: boolean;
stringToHslOptions?: StringToHslOptions;
availableAvatars: AvatarType[];
selectedAvatar?: CustomAvatarType;
sendMessageToServerMethod: (avatar: CustomAvatarType) => void;
enableCustomAvatar?: boolean;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,80 +32,86 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction<any, AvatarSel
const [selectedAvatar, setSelectedAvatar] = useState<CustomAvatarType | undefined>(undefined);
const [customAvatarType, setCustomAvatarType] = useState<"glb" | "html" | "mml">("glb");
const [customAvatarValue, setCustomAvatarValue] = useState<string>("");
const inputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
e.stopPropagation();
};

return (
<>
<div className={styles.menuButton} onClick={handleRootClick}>
<div className={styles.openTab} onClick={() => setIsVisible(true)}>
<img src={`data:image/svg+xml;utf8,${encodeURIComponent(ChatIcon)}`} />
</div>
</div>
<div style={{ zIndex: isVisible ? 1 : -1 }} className={`${styles.avatarSelectionContainer}`}>
<div className={styles.avatarSelectionUi} ref={avatarPanelRef}>
<div className={styles.avatarSelectionUiHeader}>
<h1>Choose your avatar</h1>
<button
className={styles.avatarSelectionUiCloseButton}
onClick={() => setIsVisible(false)}
>
Close
</button>
{!isVisible && (
<div className={styles.openTab} onClick={() => setIsVisible(true)}>
<img src={`data:image/svg+xml;utf8,${encodeURIComponent(AvatarIcon)}`} />
</div>
<div className={styles.avatarSelectionUiContent}>
{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));
)}
</div>
{isVisible && (
<div className={`${styles.avatarSelectionContainer}`}>
<div className={styles.avatarSelectionUi}>
<div className={styles.avatarSelectionUiHeader}>
<h2>Choose your avatar</h2>
<button className={styles.closeButton} onClick={(e) => setIsVisible(false)}>
X
</button>
</div>
<div className={styles.avatarSelectionUiContent}>
{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 (
<div
key={index}
className={styles.avatarSelectionUiAvatar}
onClick={() => selectAvatar(avatar)}
>
<img
className={isSelected ? styles.selectedAvatar : ""}
src={avatar.thumbnailUrl}
alt={avatar.name}
/>
<h2>{avatar.name}</h2>
</div>
);
})}
return (
<div
key={index}
className={styles.avatarSelectionUiAvatar}
onClick={() => selectAvatar(avatar)}
>
<img
className={isSelected ? styles.selectedAvatar : ""}
src={avatar.thumbnailUrl}
alt={avatar.name}
/>
<h2>{avatar.name}</h2>
</div>
);
})}
</div>
</div>
{props.enableCustomAvatar && (
<div className={styles.customAvatarSection}>
Expand Down Expand Up @@ -133,14 +143,18 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction<any, AvatarSel
checked={customAvatarType === "mml"}
/>
<label htmlFor="mml">MML</label>
<input
className={styles.customAvatarInput}
value={customAvatarValue}
onChange={({ target: { value } }) => setCustomAvatarValue(value)}
/>
<button disabled={!customAvatarValue} type="button" onClick={addCustomAvatar}>
Set
</button>
<div className={styles.customAvatarInputSection}>
<input
ref={inputRef}
className={styles.customAvatarInput}
value={customAvatarValue}
onKeyDown={handleKeyPress}
onChange={handleInputChange}
/>
<button disabled={!customAvatarValue} type="button" onClick={addCustomAvatar}>
Set
</button>
</div>
{selectedAvatar?.isCustomAvatar && (
<div>
<h2 className={styles.selectedAvatar}>Custom Avatar Selected</h2>
Expand All @@ -149,7 +163,7 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction<any, AvatarSel
</div>
)}
</div>
</div>
)}
</>
);
};
Loading

0 comments on commit 023b68d

Please sign in to comment.