mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-21 22:50:59 -05:00
Add category editor to servers.
This commit is contained in:
parent
71020e6e8c
commit
8c6947f7d4
10 changed files with 242 additions and 54 deletions
2
external/lang
vendored
2
external/lang
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit b40f8ce53831a590c0ffdd02f8da9fd35b7a3701
|
Subproject commit ab87bdcf15d60cfb6f3bd52af445456e5fd23e5f
|
|
@ -94,7 +94,7 @@
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scroll": "^1.8.2",
|
"react-scroll": "^1.8.2",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
"revolt.js": "4.3.3-alpha.17",
|
"revolt.js": "^4.3.3-alpha.18",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"shade-blend-color": "^1.0.0",
|
"shade-blend-color": "^1.0.0",
|
||||||
|
|
|
@ -172,9 +172,9 @@ export const MessageContent = styled.div`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
// overflow: hidden;
|
// overflow: hidden;
|
||||||
font-size: var(--text-size);
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
font-size: var(--text-size);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DetailBase = styled.div`
|
export const DetailBase = styled.div`
|
||||||
|
|
|
@ -55,11 +55,13 @@ export const TipBase = styled.div<Props>`
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function Tip(props: Props & { children: Children }) {
|
export default function Tip(
|
||||||
const { children, ...tipProps } = props;
|
props: Props & { children: Children; hideSeparator?: boolean },
|
||||||
|
) {
|
||||||
|
const { children, hideSeparator, ...tipProps } = props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Separator />
|
{!hideSeparator && <Separator />}
|
||||||
<TipBase {...tipProps}>
|
<TipBase {...tipProps}>
|
||||||
<InfoCircle size={20} />
|
<InfoCircle size={20} />
|
||||||
<span>{props.children}</span>
|
<span>{props.children}</span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ListUl, ListCheck } from "@styled-icons/boxicons-regular";
|
import { ListUl, ListCheck, ListMinus } from "@styled-icons/boxicons-regular";
|
||||||
import { XSquare, Share, Group } from "@styled-icons/boxicons-solid";
|
import { XSquare, Share, Group } from "@styled-icons/boxicons-solid";
|
||||||
import { Route, useHistory, useParams } from "react-router-dom";
|
import { Route, useHistory, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import Category from "../../components/ui/Category";
|
||||||
|
|
||||||
import { GenericSettings } from "./GenericSettings";
|
import { GenericSettings } from "./GenericSettings";
|
||||||
import { Bans } from "./server/Bans";
|
import { Bans } from "./server/Bans";
|
||||||
|
import { Categories } from "./server/Categories";
|
||||||
import { Invites } from "./server/Invites";
|
import { Invites } from "./server/Invites";
|
||||||
import { Members } from "./server/Members";
|
import { Members } from "./server/Members";
|
||||||
import { Overview } from "./server/Overview";
|
import { Overview } from "./server/Overview";
|
||||||
|
@ -41,6 +42,13 @@ export default function ServerSettings() {
|
||||||
<Text id="app.settings.server_pages.overview.title" />
|
<Text id="app.settings.server_pages.overview.title" />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "categories",
|
||||||
|
icon: <ListMinus size={20} />,
|
||||||
|
title: (
|
||||||
|
<Text id="app.settings.server_pages.categories.title" />
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "members",
|
id: "members",
|
||||||
icon: <Group size={20} />,
|
icon: <Group size={20} />,
|
||||||
|
@ -68,6 +76,9 @@ export default function ServerSettings() {
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
children={[
|
children={[
|
||||||
|
<Route path="/server/:server/settings/categories">
|
||||||
|
<Categories server={server} />
|
||||||
|
</Route>,
|
||||||
<Route path="/server/:server/settings/members">
|
<Route path="/server/:server/settings/members">
|
||||||
<RequiresOnline>
|
<RequiresOnline>
|
||||||
<Members server={server} />
|
<Members server={server} />
|
||||||
|
|
|
@ -83,8 +83,10 @@ export default function Permissions({ channel }: Props) {
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<h2>channel per??issions</h2>
|
<h2>channel permissions</h2>
|
||||||
{Object.keys(ChannelPermission).map((perm) => {
|
{Object.keys(ChannelPermission).map((perm) => {
|
||||||
|
if (perm === "View") return null;
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
ChannelPermission[perm as keyof typeof ChannelPermission];
|
ChannelPermission[perm as keyof typeof ChannelPermission];
|
||||||
if (value & DEFAULT_PERMISSION_DM) {
|
if (value & DEFAULT_PERMISSION_DM) {
|
||||||
|
|
153
src/pages/settings/server/Categories.tsx
Normal file
153
src/pages/settings/server/Categories.tsx
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import { XCircle } from "@styled-icons/boxicons-regular";
|
||||||
|
import isEqual from "lodash.isequal";
|
||||||
|
import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
|
||||||
|
import { Route } from "revolt.js/dist/api/routes";
|
||||||
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
|
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 { useChannels } from "../../../context/revoltjs/hooks";
|
||||||
|
|
||||||
|
import ChannelIcon from "../../../components/common/ChannelIcon";
|
||||||
|
import UserIcon from "../../../components/common/user/UserIcon";
|
||||||
|
import Button from "../../../components/ui/Button";
|
||||||
|
import ComboBox from "../../../components/ui/ComboBox";
|
||||||
|
import IconButton from "../../../components/ui/IconButton";
|
||||||
|
import InputBox from "../../../components/ui/InputBox";
|
||||||
|
import Preloader from "../../../components/ui/Preloader";
|
||||||
|
import Tip from "../../../components/ui/Tip";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
server: Servers.Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ! FIXME: really bad code
|
||||||
|
export function Categories({ server }: Props) {
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const channels = useChannels(server.channels) as (
|
||||||
|
| Channels.TextChannel
|
||||||
|
| Channels.VoiceChannel
|
||||||
|
)[];
|
||||||
|
|
||||||
|
const [cats, setCats] = useState<Servers.Category[]>(
|
||||||
|
server.categories ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tip warning>This section is under construction.</Tip>
|
||||||
|
<p>
|
||||||
|
<Button
|
||||||
|
contrast
|
||||||
|
disabled={isEqual(server.categories ?? [], cats)}
|
||||||
|
onClick={() =>
|
||||||
|
client.servers.edit(server._id, { categories: cats })
|
||||||
|
}>
|
||||||
|
save categories
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
<h2>categories</h2>
|
||||||
|
{cats.map((category) => (
|
||||||
|
<div style={{ background: "var(--hover)" }} key={category.id}>
|
||||||
|
<InputBox
|
||||||
|
value={category.title}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCats(
|
||||||
|
cats.map((y) =>
|
||||||
|
y.id === category.id
|
||||||
|
? {
|
||||||
|
...y,
|
||||||
|
title: e.currentTarget.value,
|
||||||
|
}
|
||||||
|
: y,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
contrast
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
contrast
|
||||||
|
onClick={() =>
|
||||||
|
setCats(cats.filter((x) => x.id !== category.id))
|
||||||
|
}>
|
||||||
|
delete {category.title}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<h2>create new</h2>
|
||||||
|
<p>
|
||||||
|
<InputBox
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.currentTarget.value)}
|
||||||
|
contrast
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
contrast
|
||||||
|
onClick={() => {
|
||||||
|
setName("");
|
||||||
|
setCats([
|
||||||
|
...cats,
|
||||||
|
{
|
||||||
|
id: ulid(),
|
||||||
|
title: name,
|
||||||
|
channels: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}}>
|
||||||
|
create
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
<h2>channels</h2>
|
||||||
|
{channels.map((channel) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "12px",
|
||||||
|
alignItems: "center",
|
||||||
|
}}>
|
||||||
|
<div style={{ flexShrink: 0 }}>
|
||||||
|
<ChannelIcon target={channel} size={24} />{" "}
|
||||||
|
<span>{channel.name}</span>
|
||||||
|
</div>
|
||||||
|
<ComboBox
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
|
value={
|
||||||
|
cats.find((x) =>
|
||||||
|
x.channels.includes(channel._id),
|
||||||
|
)?.id ?? "none"
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCats(
|
||||||
|
cats.map((x) => {
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
channels: [
|
||||||
|
...x.channels.filter(
|
||||||
|
(y) => y !== channel._id,
|
||||||
|
),
|
||||||
|
...(e.currentTarget.value ===
|
||||||
|
x.id
|
||||||
|
? [channel._id]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
<option value="none">Uncategorised</option>
|
||||||
|
{cats.map((x) => (
|
||||||
|
<option value={x.id}>{x.title}</option>
|
||||||
|
))}
|
||||||
|
</ComboBox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
export const REPO_URL = "https://gitlab.insrt.uk/revolt/revite/-/commit";
|
export const REPO_URL: string =
|
||||||
export const GIT_REVISION = "__GIT_REVISION__";
|
"https://gitlab.insrt.uk/revolt/revite/-/commit";
|
||||||
export const GIT_BRANCH = "__GIT_BRANCH__";
|
export const GIT_REVISION: string = "__GIT_REVISION__";
|
||||||
|
export const GIT_BRANCH: string = "__GIT_BRANCH__";
|
||||||
|
|
97
ui/ui.tsx
97
ui/ui.tsx
|
@ -1,41 +1,49 @@
|
||||||
import { useState } from 'preact/hooks';
|
import styled from "styled-components";
|
||||||
import styled from 'styled-components';
|
|
||||||
import '../src/styles/index.scss'
|
|
||||||
import { render } from 'preact'
|
|
||||||
|
|
||||||
import Theme from '../src/context/Theme';
|
import "../src/styles/index.scss";
|
||||||
|
import { render } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import Theme from "../src/context/Theme";
|
||||||
|
|
||||||
|
import Banner from "../src/components/ui/Banner";
|
||||||
|
import Button from "../src/components/ui/Button";
|
||||||
|
import Checkbox from "../src/components/ui/Checkbox";
|
||||||
|
import ColourSwatches from "../src/components/ui/ColourSwatches";
|
||||||
|
import ComboBox from "../src/components/ui/ComboBox";
|
||||||
|
import InputBox from "../src/components/ui/InputBox";
|
||||||
|
import Overline from "../src/components/ui/Overline";
|
||||||
|
import Radio from "../src/components/ui/Radio";
|
||||||
|
import Tip from "../src/components/ui/Tip";
|
||||||
|
|
||||||
export const UIDemo = styled.div`
|
export const UIDemo = styled.div`
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
import Button from '../src/components/ui/Button';
|
|
||||||
import Banner from '../src/components/ui/Banner';
|
|
||||||
import Checkbox from '../src/components/ui/Checkbox';
|
|
||||||
import ComboBox from '../src/components/ui/ComboBox';
|
|
||||||
import InputBox from '../src/components/ui/InputBox';
|
|
||||||
import ColourSwatches from '../src/components/ui/ColourSwatches';
|
|
||||||
import Tip from '../src/components/ui/Tip';
|
|
||||||
import Radio from '../src/components/ui/Radio';
|
|
||||||
import Overline from '../src/components/ui/Overline';
|
|
||||||
|
|
||||||
export function UI() {
|
export function UI() {
|
||||||
let [checked, setChecked] = useState(false);
|
let [checked, setChecked] = useState(false);
|
||||||
let [colour, setColour] = useState('#FD6671');
|
let [colour, setColour] = useState("#FD6671");
|
||||||
let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a');
|
let [selected, setSelected] = useState<"a" | "b" | "c">("a");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button>Button (normal)</Button>
|
<Button>Button (normal)</Button>
|
||||||
<Button contrast>Button (contrast)</Button>
|
<Button contrast>Button (contrast)</Button>
|
||||||
<Button error>Button (error)</Button>
|
<Button error>Button (error)</Button>
|
||||||
<Button contrast error>Button (contrast + error)</Button>
|
<Button contrast error>
|
||||||
|
Button (contrast + error)
|
||||||
|
</Button>
|
||||||
<Banner>I am a banner!</Banner>
|
<Banner>I am a banner!</Banner>
|
||||||
<Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onChange={setChecked}
|
||||||
|
description="ok gamer">
|
||||||
|
Do you want thing??
|
||||||
|
</Checkbox>
|
||||||
<ComboBox>
|
<ComboBox>
|
||||||
<option>Select an option.</option>
|
<option>Select an option.</option>
|
||||||
<option>1</option>
|
<option>1</option>
|
||||||
|
@ -46,24 +54,35 @@ export function UI() {
|
||||||
<InputBox placeholder="Contrast input box..." contrast />
|
<InputBox placeholder="Contrast input box..." contrast />
|
||||||
<InputBox value="Input box with value" />
|
<InputBox value="Input box with value" />
|
||||||
<InputBox value="Contrast with value" contrast />
|
<InputBox value="Contrast with value" contrast />
|
||||||
<ColourSwatches value={colour} onChange={v => setColour(v)} />
|
<ColourSwatches value={colour} onChange={(v) => setColour(v)} />
|
||||||
<Tip>I am a tip! I provide valuable information.</Tip>
|
<Tip hideSeparator>I am a tip! I provide valuable information.</Tip>
|
||||||
<Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
|
<Radio checked={selected === "a"} onSelect={() => setSelected("a")}>
|
||||||
<Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
|
First option
|
||||||
<Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
|
</Radio>
|
||||||
|
<Radio checked={selected === "b"} onSelect={() => setSelected("b")}>
|
||||||
|
Second option
|
||||||
|
</Radio>
|
||||||
|
<Radio checked={selected === "c"} onSelect={() => setSelected("c")}>
|
||||||
|
Last option
|
||||||
|
</Radio>
|
||||||
<Overline>Normal overline</Overline>
|
<Overline>Normal overline</Overline>
|
||||||
<Overline type="subtle">Subtle overline</Overline>
|
<Overline type="subtle">Subtle overline</Overline>
|
||||||
<Overline type="error">Error overline</Overline>
|
<Overline type="error">Error overline</Overline>
|
||||||
<Overline error="with error">Normal overline</Overline>
|
<Overline error="with error">Normal overline</Overline>
|
||||||
<Overline type="subtle" error="with error">Subtle overline</Overline>
|
<Overline type="subtle" error="with error">
|
||||||
|
Subtle overline
|
||||||
|
</Overline>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<>
|
render(
|
||||||
<Theme>
|
<>
|
||||||
<UIDemo>
|
<Theme>
|
||||||
<UI />
|
<UIDemo>
|
||||||
</UIDemo>
|
<UI />
|
||||||
</Theme>
|
</UIDemo>
|
||||||
</>, document.getElementById('app')!)
|
</Theme>
|
||||||
|
</>,
|
||||||
|
document.getElementById("app")!,
|
||||||
|
);
|
||||||
|
|
|
@ -3563,10 +3563,10 @@ reusify@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||||
|
|
||||||
revolt.js@4.3.3-alpha.17:
|
revolt.js@^4.3.3-alpha.18:
|
||||||
version "4.3.3-alpha.17"
|
version "4.3.3-alpha.18"
|
||||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.17.tgz#0745d251c695840b87e98098bcc4d67c7cc15de5"
|
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.18.tgz#a46cef600099ea22d2f6dc8d09def7e9135839af"
|
||||||
integrity sha512-MjxVnkkeX5md5NxZNRS9fl06jsjcDciAxKnbZ2rkBYJofQ94tvr1CYBWvFhS/u/tAR80HAPIEjJVC9HKJDK9Fg==
|
integrity sha512-3QTgX1407bLZEkxkhUsetalUGxcogpFLiTm+mPE3T9bAKgHlTC7y6F5JgHGtmMGWxsjKCDLHgHoAllwGwXJaig==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@insertish/mutable" "1.1.0"
|
"@insertish/mutable" "1.1.0"
|
||||||
axios "^0.19.2"
|
axios "^0.19.2"
|
||||||
|
|
Loading…
Reference in a new issue