mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-06 17:11:55 +00:00
feat(permission): implement new server / channel permission menus
This commit is contained in:
79
src/components/settings/roles/OverrideSwitch.tsx
Normal file
79
src/components/settings/roles/OverrideSwitch.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Check, Square, X } from "@styled-icons/boxicons-regular";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
type State = "Allow" | "Neutral" | "Deny";
|
||||
|
||||
const SwitchContainer = styled.div.attrs({
|
||||
role: "radiogroup",
|
||||
"aria-orientiation": "horizontal",
|
||||
})`
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
margin: 4px 16px;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--secondary-background);
|
||||
border: 2px solid var(--tertiary-background);
|
||||
`;
|
||||
|
||||
const Switch = styled.div.attrs({
|
||||
role: "radio",
|
||||
})<{ state: State; selected: boolean }>`
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease all;
|
||||
|
||||
color: ${(props) =>
|
||||
props.state === "Allow"
|
||||
? "var(--success)"
|
||||
: props.state === "Deny"
|
||||
? "var(--error)"
|
||||
: "var(--tertiary-foreground)"};
|
||||
|
||||
${(props) =>
|
||||
props.selected &&
|
||||
css`
|
||||
color: white;
|
||||
|
||||
background: ${props.state === "Allow"
|
||||
? "var(--success)"
|
||||
: props.state === "Deny"
|
||||
? "var(--error)"
|
||||
: "var(--primary-background)"};
|
||||
`}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
state: State;
|
||||
onChange: (state: State) => void;
|
||||
}
|
||||
|
||||
export function OverrideSwitch({ state, onChange }: Props) {
|
||||
return (
|
||||
<SwitchContainer>
|
||||
<Switch
|
||||
onClick={() => onChange("Deny")}
|
||||
state="Deny"
|
||||
selected={state === "Deny"}>
|
||||
<X size={24} />
|
||||
</Switch>
|
||||
<Switch
|
||||
onClick={() => onChange("Neutral")}
|
||||
state="Neutral"
|
||||
selected={state === "Neutral"}>
|
||||
<Square size={24} />
|
||||
</Switch>
|
||||
<Switch
|
||||
onClick={() => onChange("Allow")}
|
||||
state="Allow"
|
||||
selected={state === "Allow"}>
|
||||
<Check size={24} />
|
||||
</Switch>
|
||||
</SwitchContainer>
|
||||
);
|
||||
}
|
||||
33
src/components/settings/roles/PermissionList.tsx
Normal file
33
src/components/settings/roles/PermissionList.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { OverrideField } from "revolt-api/types/_common";
|
||||
import { Permission } from "revolt.js";
|
||||
|
||||
import { PermissionSelect } from "./PermissionSelect";
|
||||
|
||||
interface Props {
|
||||
value: OverrideField | number;
|
||||
onChange: (v: OverrideField | number) => void;
|
||||
|
||||
filter?: (keyof typeof Permission)[];
|
||||
}
|
||||
|
||||
export function PermissionList({ value, onChange, filter }: Props) {
|
||||
return (
|
||||
<>
|
||||
{(Object.keys(Permission) as (keyof typeof Permission)[])
|
||||
.filter(
|
||||
(key) =>
|
||||
key !== "GrantAllSafe" &&
|
||||
(!filter || filter.includes(key)),
|
||||
)
|
||||
.map((x) => (
|
||||
<PermissionSelect
|
||||
id={x}
|
||||
key={x}
|
||||
permission={Permission[x]}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
124
src/components/settings/roles/PermissionSelect.tsx
Normal file
124
src/components/settings/roles/PermissionSelect.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import Long from "long";
|
||||
import { OverrideField } from "revolt-api/types/_common";
|
||||
import { Permission } from "revolt.js";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
import { useMemo } from "preact/hooks";
|
||||
|
||||
import Checkbox from "../../ui/Checkbox";
|
||||
|
||||
import { OverrideSwitch } from "./OverrideSwitch";
|
||||
|
||||
interface PermissionSelectProps {
|
||||
id: keyof typeof Permission;
|
||||
permission: number;
|
||||
value: OverrideField | number;
|
||||
onChange: (value: OverrideField | number) => void;
|
||||
}
|
||||
|
||||
type State = "Allow" | "Neutral" | "Deny";
|
||||
|
||||
const PermissionEntry = styled.label`
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
font-size: 1.1em;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8em;
|
||||
color: var(--secondary-foreground);
|
||||
}
|
||||
`;
|
||||
|
||||
export function PermissionSelect({
|
||||
id,
|
||||
permission,
|
||||
value,
|
||||
onChange,
|
||||
}: PermissionSelectProps) {
|
||||
const state: State = useMemo(() => {
|
||||
if (typeof value === "object") {
|
||||
if (Long.fromNumber(value.d).and(permission).eq(permission)) {
|
||||
return "Deny";
|
||||
}
|
||||
|
||||
if (Long.fromNumber(value.a).and(permission).eq(permission)) {
|
||||
return "Allow";
|
||||
}
|
||||
|
||||
return "Neutral";
|
||||
} else {
|
||||
if (Long.fromNumber(value).and(permission).eq(permission)) {
|
||||
return "Allow";
|
||||
}
|
||||
|
||||
return "Neutral";
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
function onSwitch(state: State) {
|
||||
if (typeof value !== "object") throw "!";
|
||||
|
||||
// Convert to Long so we can do bitwise ops.
|
||||
let allow = Long.fromNumber(value.a);
|
||||
let deny = Long.fromNumber(value.d);
|
||||
|
||||
// Clear the current permission value.
|
||||
if (allow.and(permission).eq(permission)) {
|
||||
allow = allow.xor(permission);
|
||||
}
|
||||
|
||||
if (deny.and(permission).eq(permission)) {
|
||||
deny = deny.xor(permission);
|
||||
}
|
||||
|
||||
// Apply the current permission state.
|
||||
if (state === "Allow") {
|
||||
allow = allow.or(permission);
|
||||
}
|
||||
|
||||
if (state === "Deny") {
|
||||
deny = deny.or(permission);
|
||||
}
|
||||
|
||||
// Invoke state change.
|
||||
onChange({
|
||||
a: allow.toNumber(),
|
||||
d: deny.toNumber(),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PermissionEntry>
|
||||
<span class="title">
|
||||
<Text id={`permissions.server.${id}.t`}>{id}</Text>
|
||||
<span class="description">
|
||||
<Text id={`permissions.server.${id}.d`} />
|
||||
</span>
|
||||
</span>
|
||||
{typeof value === "object" ? (
|
||||
<OverrideSwitch state={state} onChange={onSwitch} />
|
||||
) : (
|
||||
<Checkbox
|
||||
checked={state === "Allow"}
|
||||
onChange={() =>
|
||||
onChange(
|
||||
Long.fromNumber(value, false)
|
||||
.xor(permission)
|
||||
.toNumber(),
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PermissionEntry>
|
||||
);
|
||||
}
|
||||
35
src/components/settings/roles/RoleSelection.tsx
Normal file
35
src/components/settings/roles/RoleSelection.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Role } from "revolt-api/types/Servers";
|
||||
|
||||
import Checkbox from "../../ui/Checkbox";
|
||||
|
||||
export type RoleOrDefault = (
|
||||
| Role
|
||||
| {
|
||||
name: string;
|
||||
permissions: number;
|
||||
colour?: string;
|
||||
hoist?: boolean;
|
||||
rank?: number;
|
||||
}
|
||||
) & { id: string };
|
||||
|
||||
interface Props {
|
||||
selected: string;
|
||||
onSelect: (id: string) => void;
|
||||
|
||||
roles: RoleOrDefault[];
|
||||
}
|
||||
|
||||
export function RoleSelection({ selected, onSelect, roles }: Props) {
|
||||
return (
|
||||
<>
|
||||
{roles.map((x) => (
|
||||
<Checkbox
|
||||
checked={x.id === selected}
|
||||
onChange={() => onSelect(x.id)}>
|
||||
{x.name}
|
||||
</Checkbox>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
22
src/components/settings/roles/UnsavedChanges.tsx
Normal file
22
src/components/settings/roles/UnsavedChanges.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import Tip from "../../../components/ui/Tip";
|
||||
import Button from "../../ui/Button";
|
||||
|
||||
interface Props {
|
||||
save: () => void;
|
||||
}
|
||||
|
||||
export function UnsavedChanges({ save }: Props) {
|
||||
return (
|
||||
<Tip hideSeparator>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
}}>
|
||||
You have unsaved changes!
|
||||
<Button onClick={save}>Save</Button>
|
||||
</span>
|
||||
</Tip>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user