From 7374591458b3a3b5f01d67e55a5172dde46eeb7c Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Wed, 14 Jul 2021 13:25:40 +0100
Subject: [PATCH] Add editing role name / colour. Animate avatars on message
 hover. Switch to 24-hour time format by default.

---
 external/lang                                 |  2 +-
 package.json                                  |  2 +-
 src/components/common/messaging/Message.tsx   | 10 ++-
 src/context/Locale.tsx                        |  5 +-
 .../intermediate/popovers/UserProfile.tsx     |  2 +-
 src/pages/settings/Settings.module.scss       | 49 ++++++++----
 src/pages/settings/server/Panes.module.scss   | 14 ++--
 src/pages/settings/server/Roles.tsx           | 76 ++++++++++++++++---
 yarn.lock                                     |  8 +-
 9 files changed, 123 insertions(+), 45 deletions(-)

diff --git a/external/lang b/external/lang
index 3931bf8..9f72b06 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit 3931bf87e94264d92556a2f3ee96c6051be75b02
+Subproject commit 9f72b064aad85293f332c3f7ce3f4fe5965def37
diff --git a/package.json b/package.json
index 767f300..d531110 100644
--- a/package.json
+++ b/package.json
@@ -94,7 +94,7 @@
     "react-router-dom": "^5.2.0",
     "react-scroll": "^1.8.2",
     "redux": "^4.1.0",
-    "revolt.js": "4.3.3-alpha.14",
+    "revolt.js": "4.3.3-alpha.15",
     "rimraf": "^3.0.2",
     "sass": "^1.35.1",
     "shade-blend-color": "^1.0.0",
diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 8e87ae2..c55d251 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -1,6 +1,6 @@
 import { attachContextMenu } from "preact-context-menu";
 import { memo } from "preact/compat";
-import { useContext } from "preact/hooks";
+import { useContext, useState } from "preact/hooks";
 
 import { QueuedMessage } from "../../../redux/reducers/queue";
 
@@ -64,6 +64,9 @@ function Message({
     const openProfile = () =>
         openScreen({ id: "profile", user_id: message.author });
 
+    // ! FIXME: animate on hover
+    const [animate, setAnimate] = useState(false);
+
     return (
         <div id={message._id}>
             {message.replies?.map((message_id, index) => (
@@ -88,7 +91,9 @@ function Message({
                               queued,
                           })
                         : undefined
-                }>
+                }
+                onMouseEnter={() => setAnimate(true)}
+                onMouseLeave={() => setAnimate(false)}>
                 <MessageInfo>
                     {head ? (
                         <UserIcon
@@ -96,6 +101,7 @@ function Message({
                             size={36}
                             onContextMenu={userContext}
                             onClick={openProfile}
+                            animate={animate}
                         />
                     ) : (
                         <MessageDetail message={message} position="left" />
diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx
index a0d1836..5366027 100644
--- a/src/context/Locale.tsx
+++ b/src/context/Locale.tsx
@@ -150,7 +150,10 @@ function Locale({ children, locale }: Props) {
         const dayjs = obj.dayjs;
         const defaults = dayjs.defaults;
 
-        const twelvehour = defaults?.twelvehour === "yes" || true;
+        const twelvehour = defaults?.twelvehour
+            ? defaults.twelvehour === "yes"
+            : false;
+
         const separator: string = defaults?.date_separator ?? "/";
         const date: "traditional" | "simplified" | "ISO8601" =
             defaults?.date_format ?? "traditional";
diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx
index bc07e4d..ff77647 100644
--- a/src/context/intermediate/popovers/UserProfile.tsx
+++ b/src/context/intermediate/popovers/UserProfile.tsx
@@ -141,7 +141,7 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
                         `linear-gradient( rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7) ), url('${backgroundURL}')`,
                 }}>
                 <div className={styles.profile}>
-                    <UserIcon size={80} target={user} status />
+                    <UserIcon size={80} target={user} status animate />
                     <div className={styles.details}>
                         <Localizer>
                             <span
diff --git a/src/pages/settings/Settings.module.scss b/src/pages/settings/Settings.module.scss
index 6a8b310..6fa0666 100644
--- a/src/pages/settings/Settings.module.scss
+++ b/src/pages/settings/Settings.module.scss
@@ -1,24 +1,41 @@
 @keyframes open {
-    0% {transform: scale(1.2);};
-    100% {transform: scale(1);};
+    0% {
+        transform: scale(1.2);
+    }
+    100% {
+        transform: scale(1);
+    }
 }
 
 @keyframes close {
-    0% {transform: scale(1); opacity: 1;};
-    100% {transform: scale(1.2); opacity: 0;};
+    0% {
+        transform: scale(1);
+        opacity: 1;
+    }
+    100% {
+        transform: scale(1.2);
+        opacity: 0;
+    }
 }
 
 @keyframes opacity {
-    0% {opacity: 0;};
-    20% {opacity: .5;}
-    50% {opacity: 1;}
+    0% {
+        opacity: 0;
+    }
+    20% {
+        opacity: 0.5;
+    }
+    50% {
+        opacity: 1;
+    }
 }
 
 .settings[data-mobile="true"] {
     flex-direction: column;
     background: var(--primary-header);
 
-    .sidebar, .content {
+    .sidebar,
+    .content {
         background: var(--primary-background);
     }
 
@@ -36,7 +53,6 @@
 
         .version {
             place-items: center;
-
         }
     }
 
@@ -52,8 +68,7 @@
     width: 100%;
     height: 100%;
     position: fixed;
-    animation: open .18s ease-out,
-               opacity .18s;
+    animation: open 0.18s ease-out, opacity 0.18s;
 }
 
 .settings {
@@ -166,7 +181,7 @@
         flex-shrink: 0;
         padding: 60px 8px;
         color: var(--tertiary-background);
-        
+
         &:after {
             content: "ESC";
             margin-top: 4px;
@@ -177,8 +192,8 @@
             position: relative;
             color: var(--foreground);
             width: 40px;
-            opacity: .5;
-            font-size: .75em;
+            opacity: 0.5;
+            font-size: 0.75em;
         }
 
         .closeButton {
@@ -190,7 +205,7 @@
             width: 40px;
             border: 3px solid var(--tertiary-background);
             cursor: pointer;
-            
+
             svg {
                 color: var(--secondary-foreground);
             }
@@ -208,6 +223,10 @@
             display: inline;
         }
     }
+
+    section {
+        margin-bottom: 1em;
+    }
 }
 
 .loader {
diff --git a/src/pages/settings/server/Panes.module.scss b/src/pages/settings/server/Panes.module.scss
index 93ca2cc..deb0d42 100644
--- a/src/pages/settings/server/Panes.module.scss
+++ b/src/pages/settings/server/Panes.module.scss
@@ -39,7 +39,8 @@
         flex-direction: row;
         background: var(--secondary-background);
 
-        code, span {
+        code,
+        span {
             flex: 1;
         }
 
@@ -69,7 +70,7 @@
         color: var(--secondary-foreground);
         font-weight: 700;
     }
-    
+
     .member {
         gap: 8px;
         padding: 10px;
@@ -95,10 +96,6 @@
         flex-grow: 1;
         padding: 0 8px;
         overflow-y: scroll;
-        
-        section {
-            margin-bottom: 1em;
-        }
     }
 
     .title {
@@ -107,7 +104,8 @@
         margin-bottom: 1em;
         align-items: center;
 
-        h1, h2 {
+        h1,
+        h2 {
             margin: 0;
             min-width: 0;
             flex-grow: 1;
@@ -126,4 +124,4 @@
         display: flex;
         padding: 8px 0;
     }
-}
\ No newline at end of file
+}
diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx
index 489adbf..f765e82 100644
--- a/src/pages/settings/server/Roles.tsx
+++ b/src/pages/settings/server/Roles.tsx
@@ -15,6 +15,7 @@ import { AppContext } from "../../../context/revoltjs/RevoltClient";
 
 import Button from "../../../components/ui/Button";
 import Checkbox from "../../../components/ui/Checkbox";
+import ColourSwatches from "../../../components/ui/ColourSwatches";
 import IconButton from "../../../components/ui/IconButton";
 import InputBox from "../../../components/ui/InputBox";
 import Overline from "../../../components/ui/Overline";
@@ -40,21 +41,46 @@ export function Roles({ server }: Props) {
         return null;
     }
 
-    const v = (id: string) =>
-        I32ToU32(
+    function getPermissions(id: string) {
+        return I32ToU32(
             id === "default"
                 ? server.default_permissions
                 : roles[id].permissions,
         );
-    const [perm, setPerm] = useState(v(role));
-    useEffect(() => setPerm(v(role)), [role, roles[role]?.permissions]);
-
-    const modified = !isEqual(perm, v(role));
-    const save = () =>
-        client.servers.setPermissions(server._id, role, {
-            server: perm[0],
-            channel: perm[1],
-        });
+    }
+
+    const { name: roleName, colour: roleColour } = roles[role] ?? {};
+
+    const [perm, setPerm] = useState(getPermissions(role));
+    const [name, setName] = useState(roleName);
+    const [colour, setColour] = useState(roleColour);
+
+    useEffect(
+        () => setPerm(getPermissions(role)),
+        [role, roles[role]?.permissions],
+    );
+
+    useEffect(() => setName(roleName), [role, roleName]);
+    useEffect(() => setColour(roleColour), [role, roleColour]);
+
+    const modified =
+        !isEqual(perm, getPermissions(role)) ||
+        !isEqual(name, roleName) ||
+        !isEqual(colour, roleColour);
+
+    const save = () => {
+        if (!isEqual(perm, getPermissions(role))) {
+            client.servers.setPermissions(server._id, role, {
+                server: perm[0],
+                channel: perm[1],
+            });
+        }
+
+        if (!isEqual(name, roleName) || !isEqual(colour, roleColour)) {
+            client.servers.editRole(server._id, role, { name, colour });
+        }
+    };
+
     const deleteRole = () => {
         setRole("default");
         client.servers.deleteRole(server._id, role);
@@ -92,7 +118,8 @@ export function Roles({ server }: Props) {
                     return (
                         <ButtonItem
                             active={role === id}
-                            onClick={() => setRole(id)}>
+                            onClick={() => setRole(id)}
+                            style={{ color: roles[id].colour }}>
                             {roles[id].name}
                         </ButtonItem>
                     );
@@ -111,6 +138,31 @@ export function Roles({ server }: Props) {
                         Save
                     </Button>
                 </div>
+                {role !== "default" && (
+                    <>
+                        <section>
+                            <Overline type="subtle">Role Name</Overline>
+                            <p>
+                                <InputBox
+                                    value={name}
+                                    onChange={(e) =>
+                                        setName(e.currentTarget.value)
+                                    }
+                                    contrast
+                                />
+                            </p>
+                        </section>
+                        <section>
+                            <Overline type="subtle">Role Colour</Overline>
+                            <p>
+                                <ColourSwatches
+                                    value={colour ?? "gray"}
+                                    onChange={(value) => setColour(value)}
+                                />
+                            </p>
+                        </section>
+                    </>
+                )}
                 <section>
                     <Overline type="subtle">
                         <Text id="app.settings.permissions.server" />
diff --git a/yarn.lock b/yarn.lock
index 2926089..05ad5db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3563,10 +3563,10 @@ reusify@^1.0.4:
   resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
-revolt.js@4.3.3-alpha.14:
-  version "4.3.3-alpha.14"
-  resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.14.tgz#f4912ee25725de6e43ba1d4fd8ab84c711678271"
-  integrity sha512-4p3DhEu+GUKZxczCPXR2JM04fzGlFfZdwHYjgkgU48NgPXgzxQrSv4x0FjpyIIv3xNpuO59z35mYRMLxAnBXsQ==
+revolt.js@4.3.3-alpha.15:
+  version "4.3.3-alpha.15"
+  resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.15.tgz#e511ad500a20f658b15b7bad0fdb9e2a5465d1b1"
+  integrity sha512-24hIQEO+FIRIAQXITBH2qVvWH6LA1MeJW2/3lj6cqBgJz7lnb3ZNIXZBu5sHbUEJpIDtJiHcOEeaeh3sE2RwxA==
   dependencies:
     "@insertish/mutable" "1.1.0"
     axios "^0.19.2"
-- 
GitLab