diff --git a/editor/resources/editor/icons/light/semantic/star-black-14x14@2x.png b/editor/resources/editor/icons/light/semantic/star-black-14x14@2x.png new file mode 100644 index 000000000000..aa4d22846057 Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/star-black-14x14@2x.png differ diff --git a/editor/resources/editor/icons/light/semantic/star-white-14x14@2x.png b/editor/resources/editor/icons/light/semantic/star-white-14x14@2x.png new file mode 100644 index 000000000000..2becdb79332f Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/star-white-14x14@2x.png differ diff --git a/editor/src/components/user-bar.tsx b/editor/src/components/user-bar.tsx index dd8cc073ab22..4ede5292ec95 100644 --- a/editor/src/components/user-bar.tsx +++ b/editor/src/components/user-bar.tsx @@ -14,7 +14,7 @@ import { } from '../core/shared/multiplayer' import { MultiplayerWrapper } from '../utils/multiplayer-wrapper' import { when } from '../utils/react-conditionals' -import { Avatar, Tooltip, useColorTheme } from '../uuiui' +import { Avatar, FlexRow, Icn, Tooltip, colorTheme } from '../uuiui' import { notice } from './common/notice' import type { EditorAction } from './editor/action-types' import { showToast, switchEditorMode } from './editor/actions/action-creators' @@ -37,15 +37,18 @@ export const UserBar = React.memo(() => { if (!isLoggedIn(loginState)) { return null - } else if (roomStatus !== 'connected') { - return - } else { - return ( - - - - ) } + return ( + + {when( + roomStatus === 'connected', + + + , + )} + + + ) }) UserBar.displayName = 'UserBar' @@ -55,9 +58,23 @@ export const SinglePlayerUserBar = React.memo(() => { (store) => getUserPicture(store.userState.loginState), 'SinglePlayerUserBar userPicture', ) + const amIOwner = useEditorState( + Substores.projectServerState, + (store) => store.projectServerState.isMyProject === 'yes', + 'SinglePlayerUserBar amIOwner', + ) return ( - +
+ + {amIOwner ? : null} +
) }) @@ -65,11 +82,9 @@ SinglePlayerUserBar.displayName = 'SinglePlayerUserBar' const MultiplayerUserBar = React.memo(() => { const dispatch = useDispatch() - const colorTheme = useColorTheme() const collabs = useStorage((store) => store.collaborators) const { user: myUser } = useMyUserAndPresence() - const myName = React.useMemo(() => normalizeMultiplayerName(myUser.name), [myUser]) const others = useOthers((list) => normalizeOthersList(myUser.id, list).map((other) => { @@ -93,6 +108,12 @@ const MultiplayerUserBar = React.memo(() => { 'MultiplayerUserBar mode', ) + const ownerId = useEditorState( + Substores.projectServerState, + (store) => store.projectServerState.ownerId, + 'MultiplayerUserBar ownerId', + ) + const toggleFollowing = React.useCallback( (targetId: string) => () => { let actions: EditorAction[] = [] @@ -135,64 +156,43 @@ const MultiplayerUserBar = React.memo(() => { style={{ display: 'flex', alignItems: 'center', + justifyContent: 'center', gap: 4, }} > + {visibleOthers.map((other) => { + if (other == null) { + return null + } + const name = normalizeMultiplayerName(other.name) + const isOwner = ownerId === other.id + return ( + + ) + })} {when( - visibleOthers.length > 0, -
0, + normalizeMultiplayerName(c.name)).join(', ')} + color={{ + background: colorTheme.fg8.value, + foreground: colorTheme.fg0.value, }} - > - {visibleOthers.map((other) => { - if (other == null) { - return null - } - const name = normalizeMultiplayerName(other.name) - return ( - - ) - })} - {when( - hiddenOthers.length > 0, - normalizeMultiplayerName(c.name)).join(', ')} - color={{ - background: colorTheme.fg8.value, - foreground: colorTheme.fg0.value, - }} - picture={null} - />, - )} -
, + picture={null} + />, )} - - - ) }) @@ -221,12 +221,13 @@ MultiplayerAvatarWithTooltip.displayName = 'MultiplayerAvatarWithTooltip' export type MultiplayerAvatarProps = { name: string color: MultiplayerColor + coloredTooltip?: boolean picture?: string | null - border?: boolean onClick?: () => void - active?: boolean + isBeingFollowed?: boolean size?: number follower?: boolean + isOwner?: boolean style?: CSSProperties } @@ -234,8 +235,6 @@ export const MultiplayerAvatar = React.memo((props: MultiplayerAvatarProps) => { const picture = React.useMemo(() => { return isDefaultAuth0AvatarURL(props.picture ?? null) ? null : props.picture }, [props.picture]) - - const colorTheme = useColorTheme() return (
{ alignItems: 'center', justifyContent: 'center', borderRadius: '100%', - border: `3px solid ${props.active === true ? colorTheme.primary.value : 'transparent'}`, fontSize: 9, fontWeight: 700, cursor: props.onClick != null ? 'pointer' : 'default', - boxShadow: props.active === true ? `0px 0px 15px ${colorTheme.primary.value}` : undefined, position: 'relative', + outline: `.3px solid ${colorTheme.bg1.value}`, + boxShadow: + props.isBeingFollowed === true + ? `0px 0px 8px ${colorTheme.dynamicBlue.value}` + : undefined, ...props.style, }} onClick={props.onClick} > - - {when(props.follower === true, )} + + {props.isOwner ? : null} + {props.follower ? : null}
) }) MultiplayerAvatar.displayName = 'MultiplayerAvatar' const FollowerBadge = React.memo(() => { - const colorTheme = useColorTheme() - return (
{ display: 'flex', alignItems: 'center', justifyContent: 'center', - border: `1px solid ${colorTheme.white.value}`, + border: `1px solid ${colorTheme.bg1.value}`, }} /> ) }) FollowerBadge.displayName = 'FollowerBadge' +const OwnerBadge = React.memo(() => { + return ( + + ) +}) + +OwnerBadge.displayName = 'OwnerBadge' + interface AvatarPictureProps { url: string | null | undefined initials: string