From dbaf246c27861e2f829f45a7041f77f34d39db2a Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Sat, 24 Jul 2021 17:01:50 +0100
Subject: [PATCH] Use loading="lazy" in more places. i18n invites page. Polish
 the bans page.

---
 external/lang                                 |  2 +-
 src/components/common/AgeGate.tsx             |  1 +
 src/components/common/Emoji.tsx               |  3 +-
 .../common/messaging/bars/FilePreview.tsx     |  2 +-
 .../common/messaging/bars/TypingIndicator.tsx |  1 +
 .../common/messaging/embed/Embed.tsx          |  1 +
 .../navigation/left/HomeSidebar.tsx           |  4 +-
 .../navigation/right/MemberSidebar.tsx        |  8 +-
 .../intermediate/modals/Onboarding.tsx        |  2 +-
 .../intermediate/popovers/ImageViewer.tsx     |  2 +
 src/context/revoltjs/hooks.ts                 |  3 +-
 src/pages/settings/panes/Appearance.tsx       | 34 ++++++--
 src/pages/settings/server/Bans.tsx            | 84 +++++++++++++++----
 src/pages/settings/server/Invites.tsx         | 18 ++--
 src/pages/settings/server/Panes.module.scss   | 18 +++-
 15 files changed, 142 insertions(+), 41 deletions(-)

diff --git a/external/lang b/external/lang
index b70b4f3..b40f8ce 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit b70b4f395caf4dc4911c5ccbf7188de198875173
+Subproject commit b40f8ce53831a590c0ffdd02f8da9fd35b7a3701
diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx
index 0dbb8f3..324005b 100644
--- a/src/components/common/AgeGate.tsx
+++ b/src/components/common/AgeGate.tsx
@@ -67,6 +67,7 @@ export default function AgeGate(props: Props) {
     return (
         <Base>
             <img
+                loading="eager"
                 src={"https://static.revolt.chat/emoji/mutant/26a0.svg"}
                 draggable={false}
             />
diff --git a/src/components/common/Emoji.tsx b/src/components/common/Emoji.tsx
index f88577c..05d68f7 100644
--- a/src/components/common/Emoji.tsx
+++ b/src/components/common/Emoji.tsx
@@ -55,6 +55,7 @@ export default function Emoji({
     return (
         <img
             alt={emoji}
+            loading="lazy"
             className="emoji"
             draggable={false}
             src={parseEmoji(emoji)}
@@ -66,7 +67,7 @@ export default function Emoji({
 }
 
 export function generateEmoji(emoji: string) {
-    return `<img class="emoji" draggable="false" alt="${emoji}" src="${parseEmoji(
+    return `<img loading="lazy" class="emoji" draggable="false" alt="${emoji}" src="${parseEmoji(
         emoji,
     )}" />`;
 }
diff --git a/src/components/common/messaging/bars/FilePreview.tsx b/src/components/common/messaging/bars/FilePreview.tsx
index 1c4644a..7d18dc3 100644
--- a/src/components/common/messaging/bars/FilePreview.tsx
+++ b/src/components/common/messaging/bars/FilePreview.tsx
@@ -168,7 +168,7 @@ function FileEntry({
     return (
         <Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
             <PreviewBox onClick={remove}>
-                <img class="icon" src={url} alt={file.name} />
+                <img class="icon" src={url} alt={file.name} loading="eager" />
                 <div class="overlay">
                     <XCircle size={36} />
                 </div>
diff --git a/src/components/common/messaging/bars/TypingIndicator.tsx b/src/components/common/messaging/bars/TypingIndicator.tsx
index e58a85b..e319820 100644
--- a/src/components/common/messaging/bars/TypingIndicator.tsx
+++ b/src/components/common/messaging/bars/TypingIndicator.tsx
@@ -97,6 +97,7 @@ export function TypingIndicator({ typing }: Props) {
                     <div className="avatars">
                         {users.map((user) => (
                             <img
+                                loading="eager"
                                 src={client.users.getAvatarURL(
                                     user._id,
                                     { max_side: 256 },
diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx
index d28fba4..4ccfd70 100644
--- a/src/components/common/messaging/embed/Embed.tsx
+++ b/src/components/common/messaging/embed/Embed.tsx
@@ -91,6 +91,7 @@ export default function Embed({ embed }: Props) {
                             <div className={styles.siteinfo}>
                                 {embed.icon_url && (
                                     <img
+                                        loading="lazy"
                                         className={styles.favicon}
                                         src={client.proxyFile(embed.icon_url)}
                                         draggable={false}
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index d91f8d4..46497aa 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -143,7 +143,9 @@ function HomeSidebar(props: Props) {
                         })
                     }
                 />
-                {channelsArr.length === 0 && <img src={placeholderSVG} />}
+                {channelsArr.length === 0 && (
+                    <img src={placeholderSVG} loading="eager" />
+                )}
                 {channelsArr.map((x) => {
                     let user;
                     if (x.channel_type === "DirectMessage") {
diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx
index 0f70a03..0044b4e 100644
--- a/src/components/navigation/right/MemberSidebar.tsx
+++ b/src/components/navigation/right/MemberSidebar.tsx
@@ -144,7 +144,9 @@ export function GroupMemberSidebar({
                             }
                         />
                     }>
-                    {members.length === 0 && <img src={placeholderSVG} />}
+                    {members.length === 0 && (
+                        <img src={placeholderSVG} loading="eager" />
+                    )}
                     {members.map(
                         (user) =>
                             user && (
@@ -257,7 +259,9 @@ export function ServerMemberSidebar({
                                 {users.length}
                             </span>
                         }>
-                        {users.length === 0 && <img src={placeholderSVG} />}
+                        {users.length === 0 && (
+                            <img src={placeholderSVG} loading="eager" />
+                        )}
                         {users.map(
                             (user) =>
                                 user && (
diff --git a/src/context/intermediate/modals/Onboarding.tsx b/src/context/intermediate/modals/Onboarding.tsx
index b5db430..160f521 100644
--- a/src/context/intermediate/modals/Onboarding.tsx
+++ b/src/context/intermediate/modals/Onboarding.tsx
@@ -40,7 +40,7 @@ export function OnboardingModal({ onClose, callback }: Props) {
             <div className={styles.header}>
                 <h1>
                     <Text id="app.special.modals.onboarding.welcome" />
-                    <img src={wideSVG} />
+                    <img src={wideSVG} loading="eager" />
                 </h1>
             </div>
             <div className={styles.form}>
diff --git a/src/context/intermediate/popovers/ImageViewer.tsx b/src/context/intermediate/popovers/ImageViewer.tsx
index 2bd056e..b3f049e 100644
--- a/src/context/intermediate/popovers/ImageViewer.tsx
+++ b/src/context/intermediate/popovers/ImageViewer.tsx
@@ -37,6 +37,7 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
                 {attachment && (
                     <>
                         <img
+                            loading="eager"
                             src={client.generateFileURL(attachment)}
                             width={(attachment.metadata as ImageMetadata).width}
                             height={
@@ -49,6 +50,7 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
                 {embed && (
                     <>
                         <img
+                            loading="eager"
                             src={client.proxyFile(embed.url)}
                             width={embed.width}
                             height={embed.height}
diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts
index 5eb575b..9fe16f5 100644
--- a/src/context/revoltjs/hooks.ts
+++ b/src/context/revoltjs/hooks.ts
@@ -3,10 +3,9 @@ import { Client, PermissionCalculator } from "revolt.js";
 import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
 import Collection from "revolt.js/dist/maps/Collection";
 
-import { useCallback, useContext, useEffect, useState } from "preact/hooks";
+import { useContext, useEffect, useState } from "preact/hooks";
 
 //#region Hooks v1
-// ! Hooks v1 will be deprecated soon.
 import { AppContext } from "./RevoltClient";
 
 export interface HookContext {
diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx
index ff1b6ce..75868ee 100644
--- a/src/pages/settings/panes/Appearance.tsx
+++ b/src/pages/settings/panes/Appearance.tsx
@@ -97,6 +97,7 @@ export function Component(props: Props) {
             <div className={styles.themes}>
                 <div className={styles.theme}>
                     <img
+                        loading="eager"
                         src={lightSVG}
                         draggable={false}
                         data-active={selected === "light"}
@@ -104,7 +105,7 @@ export function Component(props: Props) {
                             selected !== "light" &&
                             setTheme({ preset: "light" })
                         }
-                        onContextMenu={e => e.preventDefault()}
+                        onContextMenu={(e) => e.preventDefault()}
                     />
                     <h4>
                         <Text id="app.settings.pages.appearance.color.light" />
@@ -112,13 +113,14 @@ export function Component(props: Props) {
                 </div>
                 <div className={styles.theme}>
                     <img
+                        loading="eager"
                         src={darkSVG}
                         draggable={false}
                         data-active={selected === "dark"}
                         onClick={() =>
                             selected !== "dark" && setTheme({ preset: "dark" })
                         }
-                        onContextMenu={e => e.preventDefault()}
+                        onContextMenu={(e) => e.preventDefault()}
                     />
                     <h4>
                         <Text id="app.settings.pages.appearance.color.dark" />
@@ -202,7 +204,12 @@ export function Component(props: Props) {
                             className={styles.button}
                             onClick={() => setEmojiPack("mutant")}
                             data-active={emojiPack === "mutant"}>
-                            <img src={mutantSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
+                            <img
+                                loading="eager"
+                                src={mutantSVG}
+                                draggable={false}
+                                onContextMenu={(e) => e.preventDefault()}
+                            />
                         </div>
                         <h4>
                             Mutant Remix{" "}
@@ -219,7 +226,12 @@ export function Component(props: Props) {
                             className={styles.button}
                             onClick={() => setEmojiPack("twemoji")}
                             data-active={emojiPack === "twemoji"}>
-                            <img src={twemojiSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
+                            <img
+                                loading="eager"
+                                src={twemojiSVG}
+                                draggable={false}
+                                onContextMenu={(e) => e.preventDefault()}
+                            />
                         </div>
                         <h4>Twemoji</h4>
                     </div>
@@ -230,7 +242,12 @@ export function Component(props: Props) {
                             className={styles.button}
                             onClick={() => setEmojiPack("openmoji")}
                             data-active={emojiPack === "openmoji"}>
-                            <img src={openmojiSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
+                            <img
+                                loading="eager"
+                                src={openmojiSVG}
+                                draggable={false}
+                                onContextMenu={(e) => e.preventDefault()}
+                            />
                         </div>
                         <h4>Openmoji</h4>
                     </div>
@@ -239,7 +256,12 @@ export function Component(props: Props) {
                             className={styles.button}
                             onClick={() => setEmojiPack("noto")}
                             data-active={emojiPack === "noto"}>
-                            <img src={notoSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
+                            <img
+                                loading="eager"
+                                src={notoSVG}
+                                draggable={false}
+                                onContextMenu={(e) => e.preventDefault()}
+                            />
                         </div>
                         <h4>Noto Emoji</h4>
                     </div>
diff --git a/src/pages/settings/server/Bans.tsx b/src/pages/settings/server/Bans.tsx
index af369d8..6497bba 100644
--- a/src/pages/settings/server/Bans.tsx
+++ b/src/pages/settings/server/Bans.tsx
@@ -1,10 +1,15 @@
-import { Servers } from "revolt.js/dist/api/objects";
+import { XCircle } from "@styled-icons/boxicons-regular";
+import { Servers, Users } from "revolt.js/dist/api/objects";
 
+import styles from "./Panes.module.scss";
+import { Text } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 
-import Tip from "../../../components/ui/Tip";
+import UserIcon from "../../../components/common/user/UserIcon";
+import IconButton from "../../../components/ui/IconButton";
+import Preloader from "../../../components/ui/Preloader";
 
 interface Props {
     server: Servers.Server;
@@ -12,26 +17,71 @@ interface Props {
 
 export function Bans({ server }: Props) {
     const client = useContext(AppContext);
-    const [bans, setBans] = useState<Servers.Ban[] | undefined>(undefined);
+    const [deleting, setDelete] = useState<string[]>([]);
+    const [data, setData] = useState<
+        | {
+              users: Pick<Users.User, "_id" | "username" | "avatar">[];
+              bans: Servers.Ban[];
+          }
+        | undefined
+    >(undefined);
 
     useEffect(() => {
-        client.servers.fetchBans(server._id).then((bans) => setBans(bans));
+        client.servers.fetchBans(server._id).then(setData as any);
     }, []);
 
     return (
-        <div>
-            <Tip warning>This section is under construction.</Tip>
-            {bans?.map((x) => (
-                <div>
-                    {x._id.user}: {x.reason ?? "no reason"}{" "}
-                    <button
-                        onClick={() =>
-                            client.servers.unbanUser(server._id, x._id.user)
-                        }>
-                        unban
-                    </button>
-                </div>
-            ))}
+        <div className={styles.userList}>
+            <div className={styles.subtitle}>
+                <span>
+                    <Text id="app.settings.server_pages.bans.user" />
+                </span>
+                <span class={styles.reason}>
+                    <Text id="app.settings.server_pages.bans.reason" />
+                </span>
+                <span>
+                    <Text id="app.settings.server_pages.bans.revoke" />
+                </span>
+            </div>
+            {typeof data === "undefined" && <Preloader type="ring" />}
+            {data?.bans.map((x) => {
+                let user = data.users.find((y) => y._id === x._id.user);
+
+                return (
+                    <div
+                        className={styles.ban}
+                        data-deleting={deleting.indexOf(x._id.user) > -1}>
+                        <span>
+                            <UserIcon attachment={user?.avatar} size={24} />
+                            {user?.username}
+                        </span>
+                        <div className={styles.reason}>
+                            {x.reason ?? (
+                                <Text id="app.settings.server_pages.bans.no_reason" />
+                            )}
+                        </div>
+                        <IconButton
+                            onClick={async () => {
+                                setDelete([...deleting, x._id.user]);
+
+                                await client.servers.unbanUser(
+                                    server._id,
+                                    x._id.user,
+                                );
+
+                                setData({
+                                    ...data,
+                                    bans: data.bans.filter(
+                                        (y) => y._id.user !== x._id.user,
+                                    ),
+                                });
+                            }}
+                            disabled={deleting.indexOf(x._id.user) > -1}>
+                            <XCircle size={24} />
+                        </IconButton>
+                    </div>
+                );
+            })}
         </div>
     );
 }
diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx
index fa69fbe..594c1c5 100644
--- a/src/pages/settings/server/Invites.tsx
+++ b/src/pages/settings/server/Invites.tsx
@@ -37,12 +37,20 @@ export function Invites({ server }: Props) {
     }, []);
 
     return (
-        <div className={styles.invites}>
+        <div className={styles.userList}>
             <div className={styles.subtitle}>
-                <span>Invite Code</span>
-                <span>Invitor</span>
-                <span>Channel</span>
-                <span>Revoke</span>
+                <span>
+                    <Text id="app.settings.server_pages.invites.code" />
+                </span>
+                <span>
+                    <Text id="app.settings.server_pages.invites.invitor" />
+                </span>
+                <span>
+                    <Text id="app.settings.server_pages.invites.channel" />
+                </span>
+                <span>
+                    <Text id="app.settings.server_pages.invites.revoke" />
+                </span>
             </div>
             {typeof invites === "undefined" && <Preloader type="ring" />}
             {invites?.map((invite) => {
diff --git a/src/pages/settings/server/Panes.module.scss b/src/pages/settings/server/Panes.module.scss
index deb0d42..6837a83 100644
--- a/src/pages/settings/server/Panes.module.scss
+++ b/src/pages/settings/server/Panes.module.scss
@@ -17,21 +17,31 @@
     }
 }
 
-.invites {
+.userList {
     gap: 8px;
     display: flex;
     flex-direction: column;
 
     .subtitle {
+        gap: 8px;
         display: flex;
         justify-content: space-between;
         font-size: 13px;
         text-transform: uppercase;
         color: var(--secondary-foreground);
         font-weight: 700;
+
+        .reason {
+            text-align: center;
+        }
     }
 
-    .invite {
+    .reason {
+        flex: 2;
+    }
+
+    .invite,
+    .ban {
         gap: 8px;
         padding: 10px;
         display: flex;
@@ -39,8 +49,8 @@
         flex-direction: row;
         background: var(--secondary-background);
 
-        code,
-        span {
+        span,
+        code {
             flex: 1;
         }
 
-- 
GitLab