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"
- />
-
-
-
+
+
+
+
+
+
+ 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)}>
+
+