diff --git a/external/lang b/external/lang index dad00381aaaafc1ab829816dffc31f0f37ee56a9..5cad406a2fb09d90803c5604d6f27701c3bf140b 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit dad00381aaaafc1ab829816dffc31f0f37ee56a9 +Subproject commit 5cad406a2fb09d90803c5604d6f27701c3bf140b diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx index 9d7bb8017b35a240c007f022b223813eae35ad36..724f042ee7eb13688cfd98d470b67e51b2b4e2cf 100644 --- a/src/components/common/user/UserIcon.tsx +++ b/src/components/common/user/UserIcon.tsx @@ -9,6 +9,7 @@ import { AppContext } from "../../../context/revoltjs/RevoltClient"; type VoiceStatus = "muted"; interface Props extends IconBaseProps<User> { + mask?: string; status?: boolean; voice?: VoiceStatus; } @@ -52,7 +53,7 @@ import fallback from '../assets/user.png'; export default function UserIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGElement>, keyof Props>) { const client = useContext(AppContext); - const { target, attachment, size, voice, status, animate, children, as, ...svgProps } = props; + const { target, attachment, size, voice, status, animate, mask, children, as, ...svgProps } = props; const iconURL = client.generateFileURL(target?.avatar ?? attachment, { max_side: 256 }, animate) ?? (target ? client.users.getDefaultAvatarURL(target._id) : fallback); @@ -62,7 +63,7 @@ export default function UserIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGEle height={size} aria-hidden="true" viewBox="0 0 32 32"> - <foreignObject x="0" y="0" width="32" height="32" mask={props.status ? "url(#user)" : undefined}> + <foreignObject x="0" y="0" width="32" height="32" mask={mask ?? (status ? "url(#user)" : undefined)}> { <img src={iconURL} draggable={false} /> diff --git a/src/components/ui/Masks.tsx b/src/components/ui/Masks.tsx index 3a1513f13abab8b58dc11769ae965be8b629d6be..eba85108a99bd53b8845392b888f1a9132579150 100644 --- a/src/components/ui/Masks.tsx +++ b/src/components/ui/Masks.tsx @@ -12,6 +12,10 @@ export default function Masks() { <rect x="0" y="0" width="32" height="32" fill="white" /> <circle cx="27" cy="27" r="7" fill={"black"} /> </mask> + <mask id="overlap"> + <rect x="0" y="0" width="32" height="32" fill="white" /> + <circle cx="32" cy="16" r="18" fill={"black"} /> + </mask> </defs> </svg> ) diff --git a/src/context/intermediate/popovers/PendingRequests.tsx b/src/context/intermediate/popovers/PendingRequests.tsx index 710cb6b2f250a588a66a9588a2e68e8fc55376ae..5b6a68b843d45a4fd03feb65e552e219808301b8 100644 --- a/src/context/intermediate/popovers/PendingRequests.tsx +++ b/src/context/intermediate/popovers/PendingRequests.tsx @@ -1,3 +1,4 @@ +import { Text } from "preact-i18n"; import styles from "./UserPicker.module.scss"; import { useUsers } from "../../revoltjs/hooks"; import Modal from "../../../components/ui/Modal"; @@ -14,7 +15,7 @@ export function PendingRequests({ users: ids, onClose }: Props) { return ( <Modal visible={true} - title={"Pending requests"} + title={<Text id="app.special.friends.pending" />} onClose={onClose}> <div className={styles.list}> { users diff --git a/src/pages/friends/Friend.module.scss b/src/pages/friends/Friend.module.scss index 2fd1da5b8bdebaa695149f5d99ff0345ba0dda5f..5d1b5aec8288a1ef160aac929b0acafbfba9cb58 100644 --- a/src/pages/friends/Friend.module.scss +++ b/src/pages/friends/Friend.module.scss @@ -150,6 +150,11 @@ display: flex; flex-shrink: 0; margin-inline-end: 15px; + + svg:not(:first-child) { + position: relative; + margin-left: -32px; + } } .details { diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index 7e11fe682631a59460cf5ee7b8ded8d95aa51972..b713eddc70ab6c543fbba882341a6dc8f6410100 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -12,6 +12,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useIntermediate } from "../../context/intermediate/Intermediate"; import { ChevronDown, ChevronRight } from "@styled-icons/boxicons-regular"; import { UserDetail, Conversation, UserPlus } from "@styled-icons/boxicons-solid"; +import { TextReact } from "../../lib/i18n"; +import { Children } from "../../types/Preact"; export default function Friends() { const { openScreen } = useIntermediate(); @@ -22,10 +24,10 @@ export default function Friends() { const friends = users.filter(x => x.relationship === Users.Relationship.Friend); const lists = [ - [ 'app.special.friends.pending', users.filter(x => + [ '', users.filter(x => x.relationship === Users.Relationship.Incoming ) ], - [ 'app.special.friends.pending', users.filter(x => + [ 'app.special.friends.sent', users.filter(x => x.relationship === Users.Relationship.Outgoing ) ], [ 'app.status.online', friends.filter(x => @@ -39,6 +41,10 @@ export default function Friends() { ) ] ] as [ string, User[] ][]; + const incoming = lists[0][1]; + const userlist: Children[] = incoming.map(x => <b>{ x.username }</b>); + for (let i=incoming.length-1;i>0;i--) userlist.splice(i, 0, ', '); + const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0; return ( <> @@ -74,15 +80,20 @@ export default function Friends() { </> )} - { lists[0][1].length > 0 && <div className={styles.pending} - onClick={() => openScreen({ id: 'pending_requests', users: lists[0][1].map(x => x._id) })}> + { incoming.length > 0 && <div className={styles.pending} + onClick={() => openScreen({ id: 'pending_requests', users: incoming.map(x => x._id) })}> <div className={styles.avatars}> - { lists[0][1].map((x, i) => i < 3 && <UserIcon target={x} size={54} />) } + { incoming.map((x, i) => i < 3 && <UserIcon target={x} size={64} mask={ i < Math.min(incoming.length - 1, 2) ? "url(#overlap)" : undefined } />) } </div> <div className={styles.details}> - {/* ! FIXME: i18n */} - <div className={styles.title}>Pending requests<span>{ lists[0][1].length }</span></div> - <span className={styles.from}>From <span className={styles.user}>{ lists[0][1].map(x => x.username).join(', ') }</span></span> + <div><Text id="app.special.friends.pending" /> <span>{ incoming.length }</span></div> + <span> + { + incoming.length > 3 ? <TextReact id="app.special.friends.from.several" fields={{ userlist: userlist.slice(0, 6), count: incoming.length - 3 }} /> + : incoming.length > 1 ? <TextReact id="app.special.friends.from.multiple" fields={{ user: userlist.shift()!, userlist: userlist.slice(1) }} /> + : <TextReact id="app.special.friends.from.single" fields={{ user: userlist[0] }} /> + } + </span> </div> <ChevronRight size={28} /> </div> }