From ddaca7b0c4e73f379a400db2d411717715eeea55 Mon Sep 17 00:00:00 2001 From: Declan Chidlow Date: Thu, 12 Jun 2025 09:59:57 +0800 Subject: [PATCH 1/3] feat: remove legacy role ranking --- src/pages/settings/server/Roles.tsx | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 7527bb51..b5e690d5 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -229,26 +229,6 @@ export const Roles = observer(({ server }: Props) => { />

-
- - - -

- - setValue({ - ...value, - rank: parseInt( - e.currentTarget.value, - ), - }) - } - palette="secondary" - /> -

-
)}

From 20c8dde197fb6cacf28792a3a079e3e428e988f4 Mon Sep 17 00:00:00 2001 From: Declan Chidlow Date: Thu, 12 Jun 2025 12:33:39 +0800 Subject: [PATCH 2/3] feat: Add role re-ordering Doesn't currently update order in UI --- src/pages/settings/server/Roles.tsx | 570 +++++++++++++++++++--------- 1 file changed, 392 insertions(+), 178 deletions(-) diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index b5e690d5..1623e598 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -1,4 +1,8 @@ -import { HelpCircle } from "@styled-icons/boxicons-solid"; +import { + HelpCircle, + ChevronUp, + ChevronDown, +} from "@styled-icons/boxicons-solid"; import isEqual from "lodash.isequal"; import { observer } from "mobx-react-lite"; import { Server } from "revolt.js"; @@ -21,12 +25,191 @@ import { import Tooltip from "../../../components/common/Tooltip"; import { PermissionList } from "../../../components/settings/roles/PermissionList"; import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection"; +import { useSession } from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; interface Props { server: Server; } +const RoleReorderContainer = styled.div` + margin: 16px 0; +`; + +const RoleItem = styled.div` + display: flex; + align-items: center; + padding: 12px 16px; + margin: 12px 0; + background: var(--secondary-background); + border-radius: var(--border-radius); +`; + +const RoleInfo = styled.div` + flex: 1; + display: flex; + flex-direction: column; +`; + +const RoleName = styled.div` + font-weight: 600; + color: var(--foreground); +`; + +const RoleRank = styled.div` + font-size: 12px; + color: var(--secondary-foreground); +`; + +const RoleControls = styled.div` + display: flex; + gap: 4px; +`; + +const MoveButton = styled(Button)` + padding: 4px 8px; + min-width: auto; +`; + +/** + * Hook to memo-ize role information with proper ordering + * @param server Target server + * @returns Role array with default at bottom + */ +export function useRolesForReorder(server: Server) { + return useMemo(() => { + const roles = [...server.orderedRoles] as RoleOrDefault[]; + + roles.push({ + id: "default", + name: "Default", + permissions: server.default_permissions, + }); + + return roles; + }, [server.roles, server.default_permissions]); +} + +/** + * Role reordering component + */ +const RoleReorderPanel = observer(({ server }: Props) => { + const initialRoles = useRolesForReorder(server); + const [roles, setRoles] = useState(initialRoles); + const [isReordering, setIsReordering] = useState(false); + + // Update local state when server roles change + useMemo(() => { + setRoles(useRolesForReorder(server)); + }, [server.roles, server.default_permissions]); + + const moveRoleUp = (index: number) => { + if (index === 0 || roles[index].id === "default") return; + + const newRoles = [...roles]; + [newRoles[index - 1], newRoles[index]] = [ + newRoles[index], + newRoles[index - 1], + ]; + setRoles(newRoles); + }; + + const moveRoleDown = (index: number) => { + // Can't move down if it's the last non-default role or if it's default + if (index >= roles.length - 2 || roles[index].id === "default") return; + + const newRoles = [...roles]; + [newRoles[index], newRoles[index + 1]] = [ + newRoles[index + 1], + newRoles[index], + ]; + setRoles(newRoles); + }; + + const saveReorder = async () => { + setIsReordering(true); + try { + const nonDefaultRoles = roles.filter( + (role) => role.id !== "default", + ); + const roleIds = nonDefaultRoles.map((role) => role.id); + + const session = useSession()!; + const client = session.client!; + + // Make direct API request since it's not in r.js as of writing + await client.api.patch(`/servers/${server._id}/roles/ranks`, { + ranks: roleIds, + }); + + console.log("Roles reordered successfully"); + } catch (error) { + console.error("Failed to reorder roles:", error); + setRoles(initialRoles); + } finally { + setIsReordering(false); + } + }; + + const hasChanges = !isEqual( + roles.filter((r) => r.id !== "default").map((r) => r.id), + initialRoles.filter((r) => r.id !== "default").map((r) => r.id), + ); + + return ( +
+ +

+ +

+ +
+ + + {roles.map((role, index) => ( + + + {role.name} + + {role.id === "default" ? ( + + ) : ( + <> + {" "} + {index} + + )} + + + + {role.id !== "default" && ( + + moveRoleUp(index)}> + + + = roles.length - 2} + onClick={() => moveRoleDown(index)}> + + + + )} + + ))} + +
+ ); +}); + /** * Hook to memo-ize role information. * @param server Target server @@ -50,9 +233,11 @@ export function useRoles(server: Server) { } /** - * Roles settings menu + * Updated Roles settings menu with reordering panel */ export const Roles = observer(({ server }: Props) => { + const [showReorderPanel, setShowReorderPanel] = useState(false); + // Consolidate all permissions that we can change right now. const currentRoles = useRoles(server); @@ -74,193 +259,222 @@ export const Roles = observer(({ server }: Props) => { margin: 16px 0; `; + const ReorderButton = styled(Button)` + margin: 0 0 16px 0; + `; + + if (showReorderPanel) { + return ( +
+ + +
+ ); + } + return ( - - modalController.push({ - type: "create_role", - server, - callback, - }) - } - editor={({ selected }) => { - const currentRole = currentRoles.find( - (x) => x.id === selected, - )!; +
+ setShowReorderPanel(true)}> + + + + modalController.push({ + type: "create_role", + server, + callback, + }) + } + editor={({ selected }) => { + const currentRole = currentRoles.find( + (x) => x.id === selected, + )!; - if (!currentRole) return null; + if (!currentRole) return null; - // Keep track of whatever role we're editing right now. - const [value, setValue] = useState>({}); + const [value, setValue] = useState>( + {}, + ); - const currentRoleValue = { ...currentRole, ...value }; + const currentRoleValue = { ...currentRole, ...value }; - // Calculate permissions we have access to on this server. - const current = server.permission; + function save() { + const { permissions: permsCurrent, ...current } = + currentRole; + const { permissions: permsValue, ...value } = + currentRoleValue; - // Upload new role information to server. - function save() { - const { permissions: permsCurrent, ...current } = - currentRole; - const { permissions: permsValue, ...value } = - currentRoleValue; + if (!isEqual(permsCurrent, permsValue)) { + server.setPermissions( + selected, + typeof permsValue === "number" + ? permsValue + : { + allow: permsValue.a, + deny: permsValue.d, + }, + ); + } - if (!isEqual(permsCurrent, permsValue)) { - server.setPermissions( - selected, - typeof permsValue === "number" - ? permsValue - : { - allow: permsValue.a, - deny: permsValue.d, - }, - ); + if (!isEqual(current, value)) { + server.editRole(selected, value); + } } - if (!isEqual(current, value)) { - server.editRole(selected, value); + function deleteRole() { + server.deleteRole(selected); } - } - // Delete the role from this server. - function deleteRole() { - server.deleteRole(selected); - } - - return ( -
- -

- -

- -
-
- {selected !== "default" && ( - <> -
- - - -

- - setValue({ - ...value, - name: e.currentTarget.value, - }) - } - palette="secondary" - /> -

-
-
- {"Role ID"} - - - - - - }> - - modalController.writeText( - currentRole.id, - ) + return ( +
+ +

+ +

+ +
+
+ {selected !== "default" && ( + <> +
+ + + +

+ + setValue({ + ...value, + name: e.currentTarget + .value, + }) + } + palette="secondary" + /> +

+
+
+ {"Role ID"} + + - {currentRole.id} - - - -
-
- - - -

- - setValue({ ...value, colour }) - } - /> -

-
-
- - - -

- - setValue({ ...value, hoist }) - } - title={ - - } - description={ - - } - /> -

-
- - )} -

- -

- - setValue({ - ...value, - permissions, - } as RoleOrDefault) - } - target={server} - /> - {selected !== "default" && ( - <> -
-

- -

- - - - - )} -
- ); - }} - /> + +
+ + }> + + modalController.writeText( + currentRole.id, + ) + }> + {currentRole.id} + + +
+
+
+ + + +

+ + setValue({ + ...value, + colour, + }) + } + /> +

+
+
+ + + +

+ + setValue({ + ...value, + hoist, + }) + } + title={ + + } + description={ + + } + /> +

+
+ + )} +

+ +

+ + setValue({ + ...value, + permissions, + } as RoleOrDefault) + } + target={server} + /> + {selected !== "default" && ( + <> +
+

+ +

+ + + + + )} +
+ ); + }} + /> +
); }); From e7d7420d5ba21de5cf06e6ddbecd7d0ed420b531 Mon Sep 17 00:00:00 2001 From: Declan Chidlow Date: Mon, 4 Aug 2025 22:28:24 +0800 Subject: [PATCH 3/3] feat: Finish crappy role ranking implementation that works but is full of sin --- src/pages/settings/server/Roles.tsx | 240 +++++++++++++++------------- 1 file changed, 129 insertions(+), 111 deletions(-) diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 1623e598..927e9bb8 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -93,122 +93,129 @@ export function useRolesForReorder(server: Server) { /** * Role reordering component */ -const RoleReorderPanel = observer(({ server }: Props) => { - const initialRoles = useRolesForReorder(server); - const [roles, setRoles] = useState(initialRoles); - const [isReordering, setIsReordering] = useState(false); +const RoleReorderPanel = observer( + ({ + server, + onRolesReordered, + }: Props & { onRolesReordered: () => void }) => { + const initialRoles = useRolesForReorder(server); + const [roles, setRoles] = useState(initialRoles); + const [isReordering, setIsReordering] = useState(false); - // Update local state when server roles change - useMemo(() => { - setRoles(useRolesForReorder(server)); - }, [server.roles, server.default_permissions]); + // Update local state when server roles change + useMemo(() => { + setRoles(useRolesForReorder(server)); + }, [server.roles, server.default_permissions]); - const moveRoleUp = (index: number) => { - if (index === 0 || roles[index].id === "default") return; + const moveRoleUp = (index: number) => { + if (index === 0 || roles[index].id === "default") return; - const newRoles = [...roles]; - [newRoles[index - 1], newRoles[index]] = [ - newRoles[index], - newRoles[index - 1], - ]; - setRoles(newRoles); - }; + const newRoles = [...roles]; + [newRoles[index - 1], newRoles[index]] = [ + newRoles[index], + newRoles[index - 1], + ]; + setRoles(newRoles); + }; - const moveRoleDown = (index: number) => { - // Can't move down if it's the last non-default role or if it's default - if (index >= roles.length - 2 || roles[index].id === "default") return; + const moveRoleDown = (index: number) => { + // Can't move down if it's the last non-default role or if it's default + if (index >= roles.length - 2 || roles[index].id === "default") + return; - const newRoles = [...roles]; - [newRoles[index], newRoles[index + 1]] = [ - newRoles[index + 1], - newRoles[index], - ]; - setRoles(newRoles); - }; + const newRoles = [...roles]; + [newRoles[index], newRoles[index + 1]] = [ + newRoles[index + 1], + newRoles[index], + ]; + setRoles(newRoles); + }; - const saveReorder = async () => { - setIsReordering(true); - try { - const nonDefaultRoles = roles.filter( - (role) => role.id !== "default", - ); - const roleIds = nonDefaultRoles.map((role) => role.id); + const saveReorder = async () => { + setIsReordering(true); + try { + const nonDefaultRoles = roles.filter( + (role) => role.id !== "default", + ); + const roleIds = nonDefaultRoles.map((role) => role.id); - const session = useSession()!; - const client = session.client!; + const session = useSession()!; + const client = session.client!; - // Make direct API request since it's not in r.js as of writing - await client.api.patch(`/servers/${server._id}/roles/ranks`, { - ranks: roleIds, - }); + // Make direct API request since it's not in r.js as of writing + await client.api.patch(`/servers/${server._id}/roles/ranks`, { + ranks: roleIds, + }); - console.log("Roles reordered successfully"); - } catch (error) { - console.error("Failed to reorder roles:", error); - setRoles(initialRoles); - } finally { - setIsReordering(false); - } - }; + console.log("Roles reordered successfully"); + onRolesReordered(); + } catch (error) { + console.error("Failed to reorder roles:", error); + setRoles(initialRoles); + } finally { + setIsReordering(false); + } + }; - const hasChanges = !isEqual( - roles.filter((r) => r.id !== "default").map((r) => r.id), - initialRoles.filter((r) => r.id !== "default").map((r) => r.id), - ); + const hasChanges = !isEqual( + roles.filter((r) => r.id !== "default").map((r) => r.id), + initialRoles.filter((r) => r.id !== "default").map((r) => r.id), + ); - return ( -
- -

- -

- -
+ return ( +
+ +

+ +

+ +
- - {roles.map((role, index) => ( - - - {role.name} - - {role.id === "default" ? ( - - ) : ( - <> - {" "} - {index} - - )} - - + + {roles.map((role, index) => ( + + + {role.name} + + {role.id === "default" ? ( + + ) : ( + <> + {" "} + {index} + + )} + + - {role.id !== "default" && ( - - moveRoleUp(index)}> - - - = roles.length - 2} - onClick={() => moveRoleDown(index)}> - - - - )} - - ))} - -
- ); -}); + {role.id !== "default" && ( + + moveRoleUp(index)}> + + + = roles.length - 2} + onClick={() => moveRoleDown(index)}> + + + + )} + + ))} + +
+ ); + }, +); /** * Hook to memo-ize role information. @@ -237,6 +244,7 @@ export function useRoles(server: Server) { */ export const Roles = observer(({ server }: Props) => { const [showReorderPanel, setShowReorderPanel] = useState(false); + const [rolesWereReordered, setRolesWereReordered] = useState(false); // Consolidate all permissions that we can change right now. const currentRoles = useRoles(server); @@ -260,16 +268,26 @@ export const Roles = observer(({ server }: Props) => { `; const ReorderButton = styled(Button)` - margin: 0 0 16px 0; + margin-inline: auto 8px; `; + const handleBackFromReorder = () => { + setShowReorderPanel(false); + if (rolesWereReordered) { + window.location.reload(); // Refresh because I don't actually care anymore. + } + }; + if (showReorderPanel) { return (
- + setRolesWereReordered(true)} + /> @@ -279,11 +297,6 @@ export const Roles = observer(({ server }: Props) => { return (
- setShowReorderPanel(true)}> - - { fields={{ name: currentRole.name }} />

+ setShowReorderPanel(true)}> + +