mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-26 00:50:56 -05:00
feat(categories): include uncategorised channels; add category / channel; delete category
This commit is contained in:
parent
bb5509f660
commit
c208064d2c
3 changed files with 297 additions and 255 deletions
|
@ -2,6 +2,7 @@ import { Prompt } from "react-router";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import type { Attachment } from "revolt-api/types/Autumn";
|
import type { Attachment } from "revolt-api/types/Autumn";
|
||||||
import { Bot } from "revolt-api/types/Bots";
|
import { Bot } from "revolt-api/types/Bots";
|
||||||
|
import { TextChannel, VoiceChannel } from "revolt-api/types/Channels";
|
||||||
import type { EmbedImage } from "revolt-api/types/January";
|
import type { EmbedImage } from "revolt-api/types/January";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
import { Message } from "revolt.js/dist/maps/Messages";
|
import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
|
@ -42,7 +43,12 @@ export type Screen =
|
||||||
| { type: "leave_server"; target: Server }
|
| { type: "leave_server"; target: Server }
|
||||||
| { type: "delete_server"; target: Server }
|
| { type: "delete_server"; target: Server }
|
||||||
| { type: "delete_channel"; target: Channel }
|
| { type: "delete_channel"; target: Channel }
|
||||||
| { type: "delete_bot"; target: string; name: string; cb: () => void }
|
| {
|
||||||
|
type: "delete_bot";
|
||||||
|
target: string;
|
||||||
|
name: string;
|
||||||
|
cb?: () => void;
|
||||||
|
}
|
||||||
| { type: "delete_message"; target: Message }
|
| { type: "delete_message"; target: Message }
|
||||||
| {
|
| {
|
||||||
type: "create_invite";
|
type: "create_invite";
|
||||||
|
@ -52,7 +58,11 @@ export type Screen =
|
||||||
| { type: "ban_member"; target: Server; user: User }
|
| { type: "ban_member"; target: Server; user: User }
|
||||||
| { type: "unfriend_user"; target: User }
|
| { type: "unfriend_user"; target: User }
|
||||||
| { type: "block_user"; target: User }
|
| { type: "block_user"; target: User }
|
||||||
| { type: "create_channel"; target: Server }
|
| {
|
||||||
|
type: "create_channel";
|
||||||
|
target: Server;
|
||||||
|
cb?: (channel: TextChannel | VoiceChannel) => void;
|
||||||
|
}
|
||||||
| { type: "create_category"; target: Server }
|
| { type: "create_category"; target: Server }
|
||||||
))
|
))
|
||||||
| ({ id: "special_input" } & (
|
| ({ id: "special_input" } & (
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { TextChannel, VoiceChannel } from "revolt-api/types/Channels";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
|
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
|
||||||
import { Server } from "revolt.js/dist/maps/Servers";
|
import { Server } from "revolt.js/dist/maps/Servers";
|
||||||
|
@ -60,7 +61,7 @@ type SpecialProps = { onClose: () => void } & (
|
||||||
| { type: "leave_server"; target: Server }
|
| { type: "leave_server"; target: Server }
|
||||||
| { type: "delete_server"; target: Server }
|
| { type: "delete_server"; target: Server }
|
||||||
| { type: "delete_channel"; target: Channel }
|
| { type: "delete_channel"; target: Channel }
|
||||||
| { type: "delete_bot"; target: string; name: string; cb: () => void }
|
| { type: "delete_bot"; target: string; name: string; cb?: () => void }
|
||||||
| { type: "delete_message"; target: MessageI }
|
| { type: "delete_message"; target: MessageI }
|
||||||
| {
|
| {
|
||||||
type: "create_invite";
|
type: "create_invite";
|
||||||
|
@ -70,7 +71,11 @@ type SpecialProps = { onClose: () => void } & (
|
||||||
| { type: "ban_member"; target: Server; user: User }
|
| { type: "ban_member"; target: Server; user: User }
|
||||||
| { type: "unfriend_user"; target: User }
|
| { type: "unfriend_user"; target: User }
|
||||||
| { type: "block_user"; target: User }
|
| { type: "block_user"; target: User }
|
||||||
| { type: "create_channel"; target: Server }
|
| {
|
||||||
|
type: "create_channel";
|
||||||
|
target: Server;
|
||||||
|
cb?: (channel: TextChannel | VoiceChannel) => void;
|
||||||
|
}
|
||||||
| { type: "create_category"; target: Server }
|
| { type: "create_category"; target: Server }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -158,7 +163,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
|
||||||
break;
|
break;
|
||||||
case "delete_bot":
|
case "delete_bot":
|
||||||
client.bots.delete(props.target);
|
client.bots.delete(props.target);
|
||||||
props.cb();
|
props.cb?.();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,9 +429,14 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
|
||||||
nonce: ulid(),
|
nonce: ulid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (props.cb) {
|
||||||
|
props.cb(channel);
|
||||||
|
} else {
|
||||||
history.push(
|
history.push(
|
||||||
`/server/${props.target._id}/channel/${channel._id}`,
|
`/server/${props.target._id}/channel/${channel._id}`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(takeError(err));
|
setError(takeError(err));
|
||||||
|
@ -472,7 +482,6 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
|
||||||
}
|
}
|
||||||
case "create_category": {
|
case "create_category": {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PromptModal
|
<PromptModal
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import { Check } from "@styled-icons/boxicons-regular";
|
import { Filter, Plus, X } from "@styled-icons/boxicons-regular";
|
||||||
import isEqual from "lodash.isequal";
|
import isEqual from "lodash.isequal";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
|
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
|
||||||
|
import { TextChannel, VoiceChannel } from "revolt-api/types/Channels";
|
||||||
import { Category } from "revolt-api/types/Servers";
|
import { Category } from "revolt-api/types/Servers";
|
||||||
import { Server } from "revolt.js/dist/maps/Servers";
|
import { Server } from "revolt.js/dist/maps/Servers";
|
||||||
import styled, { css } from "styled-components";
|
import styled, { css } from "styled-components";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useEffect, useErrorBoundary, useState } from "preact/hooks";
|
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { useAutosave, useAutosaveCallback } from "../../../lib/debounce";
|
import { useAutosave } from "../../../lib/debounce";
|
||||||
|
import { noop } from "../../../lib/js";
|
||||||
|
|
||||||
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
import ChannelIcon from "../../../components/common/ChannelIcon";
|
import ChannelIcon from "../../../components/common/ChannelIcon";
|
||||||
import Button from "../../../components/ui/Button";
|
import Button from "../../../components/ui/Button";
|
||||||
|
@ -19,16 +23,6 @@ import InputBox from "../../../components/ui/InputBox";
|
||||||
import SaveStatus, { EditStatus } from "../../../components/ui/SaveStatus";
|
import SaveStatus, { EditStatus } from "../../../components/ui/SaveStatus";
|
||||||
import Tip from "../../../components/ui/Tip";
|
import Tip from "../../../components/ui/Tip";
|
||||||
|
|
||||||
/* interface CreateCategoryProps {
|
|
||||||
callback: (name: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CreateCategory({ callback }: CreateCategoryProps) {
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
} */
|
|
||||||
|
|
||||||
const KanbanEntry = styled.div`
|
const KanbanEntry = styled.div`
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
|
|
||||||
|
@ -73,16 +67,43 @@ const KanbanList = styled.div<{ last: boolean }>`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: center;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
> [data-rbd-droppable-id] {
|
> [data-rbd-droppable-id] {
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const KanbanListTitle = styled.div`
|
const Row = styled.div`
|
||||||
height: 42px;
|
gap: 2px;
|
||||||
|
margin: 4px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> :first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const KanbanListHeader = styled.div`
|
||||||
|
height: 34px;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
min-width: 34px;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: 0.2s ease background-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const KanbanBoard = styled.div`
|
const KanbanBoard = styled.div`
|
||||||
|
@ -129,6 +150,19 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
() => setStatus("editing"),
|
() => setStatus("editing"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const defaultCategory = useMemo(() => {
|
||||||
|
return {
|
||||||
|
title: "Uncategorized",
|
||||||
|
channels: [...server.channels]
|
||||||
|
.filter((x) => x)
|
||||||
|
.map((x) => x!._id)
|
||||||
|
.filter(
|
||||||
|
(x) => !categories.find((cat) => cat.channels.includes(x)),
|
||||||
|
),
|
||||||
|
id: "none",
|
||||||
|
};
|
||||||
|
}, [categories, server.channels]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header>
|
<Header>
|
||||||
|
@ -150,6 +184,8 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "column") {
|
if (type === "column") {
|
||||||
|
if (destination.index === 0) return;
|
||||||
|
|
||||||
// Remove from array.
|
// Remove from array.
|
||||||
const cat = categories.find(
|
const cat = categories.find(
|
||||||
(x) => x.id === draggableId,
|
(x) => x.id === draggableId,
|
||||||
|
@ -159,7 +195,7 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Insert at new position.
|
// Insert at new position.
|
||||||
arr.splice(destination.index, 0, cat!);
|
arr.splice(destination.index - 1, 0, cat!);
|
||||||
setCategories(arr);
|
setCategories(arr);
|
||||||
} else {
|
} else {
|
||||||
setCategories(
|
setCategories(
|
||||||
|
@ -204,8 +240,129 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.droppableProps}>
|
{...provided.droppableProps}>
|
||||||
<KanbanBoard>
|
<KanbanBoard>
|
||||||
|
<ListElement
|
||||||
|
category={defaultCategory}
|
||||||
|
server={server}
|
||||||
|
index={0}
|
||||||
|
addChannel={noop}
|
||||||
|
/>
|
||||||
{categories.map((category, index) => (
|
{categories.map((category, index) => (
|
||||||
|
<ListElement
|
||||||
|
draggable
|
||||||
|
category={category}
|
||||||
|
server={server}
|
||||||
|
index={index + 1}
|
||||||
|
key={category.id}
|
||||||
|
setTitle={(title) => {
|
||||||
|
setCategories(
|
||||||
|
categories.map((x) =>
|
||||||
|
x.id === category.id
|
||||||
|
? {
|
||||||
|
...x,
|
||||||
|
title,
|
||||||
|
}
|
||||||
|
: x,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
deleteSelf={() =>
|
||||||
|
setCategories(
|
||||||
|
categories.filter(
|
||||||
|
(x) =>
|
||||||
|
x.id !==
|
||||||
|
category.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addChannel={(channel) => {
|
||||||
|
setCategories(
|
||||||
|
categories.map((x) =>
|
||||||
|
x.id === category.id
|
||||||
|
? {
|
||||||
|
...x,
|
||||||
|
channels:
|
||||||
|
[
|
||||||
|
...x.channels,
|
||||||
|
channel._id,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: x,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<KanbanList last>
|
||||||
|
<div class="inner">
|
||||||
|
<KanbanListHeader
|
||||||
|
onClick={() =>
|
||||||
|
setCategories([
|
||||||
|
...categories,
|
||||||
|
{
|
||||||
|
id: ulid(),
|
||||||
|
title: "New Category",
|
||||||
|
channels: [],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}>
|
||||||
|
<Plus size={24} />
|
||||||
|
</KanbanListHeader>
|
||||||
|
</div>
|
||||||
|
</KanbanList>
|
||||||
|
{provided.placeholder}
|
||||||
|
</KanbanBoard>
|
||||||
|
</div>
|
||||||
|
) as any
|
||||||
|
}
|
||||||
|
</Droppable>
|
||||||
|
</FullSize>
|
||||||
|
</DragDropContext>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function ListElement({
|
||||||
|
category,
|
||||||
|
server,
|
||||||
|
index,
|
||||||
|
setTitle,
|
||||||
|
deleteSelf,
|
||||||
|
addChannel,
|
||||||
|
draggable,
|
||||||
|
}: {
|
||||||
|
category: Category;
|
||||||
|
server: Server;
|
||||||
|
index: number;
|
||||||
|
setTitle?: (title: string) => void;
|
||||||
|
deleteSelf?: () => void;
|
||||||
|
addChannel: (channel: TextChannel | VoiceChannel) => void;
|
||||||
|
draggable?: boolean;
|
||||||
|
}) {
|
||||||
|
const { openScreen } = useIntermediate();
|
||||||
|
const [editing, setEditing] = useState<string>();
|
||||||
|
const startEditing = () => setTitle && setEditing(category.title);
|
||||||
|
|
||||||
|
const save = useCallback(() => {
|
||||||
|
setEditing(undefined);
|
||||||
|
setTitle!(editing!);
|
||||||
|
}, [editing, setTitle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editing) return;
|
||||||
|
|
||||||
|
function onClick(ev: MouseEvent) {
|
||||||
|
if ((ev.target as HTMLElement)?.id !== category.id) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", onClick);
|
||||||
|
return () => document.removeEventListener("mousedown", onClick);
|
||||||
|
}, [editing, category.id, save]);
|
||||||
|
|
||||||
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
|
isDragDisabled={!draggable}
|
||||||
key={category.id}
|
key={category.id}
|
||||||
draggableId={category.id}
|
draggableId={category.id}
|
||||||
index={index}>
|
index={index}>
|
||||||
|
@ -213,71 +370,60 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
(
|
(
|
||||||
<div
|
<div
|
||||||
{...(provided.draggableProps as any)}
|
{...(provided.draggableProps as any)}
|
||||||
ref={
|
ref={provided.innerRef}>
|
||||||
provided.innerRef
|
<KanbanList last={false} key={category.id}>
|
||||||
}>
|
|
||||||
<KanbanList
|
|
||||||
last={
|
|
||||||
index ===
|
|
||||||
categories.length -
|
|
||||||
1
|
|
||||||
}
|
|
||||||
key={
|
|
||||||
category.id
|
|
||||||
}>
|
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<KanbanListTitle
|
<Row>
|
||||||
|
<KanbanListHeader
|
||||||
{...(provided.dragHandleProps as any)}>
|
{...(provided.dragHandleProps as any)}>
|
||||||
<span>
|
{editing ? (
|
||||||
{
|
<input
|
||||||
category.title
|
value={editing}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditing(
|
||||||
|
e.currentTarget.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
onKeyDown={(e) =>
|
||||||
|
e.key === "Enter" && save()
|
||||||
|
}
|
||||||
|
id={category.id}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span onClick={startEditing}>
|
||||||
|
{category.title}
|
||||||
</span>
|
</span>
|
||||||
</KanbanListTitle>
|
)}
|
||||||
|
</KanbanListHeader>
|
||||||
|
{deleteSelf && (
|
||||||
|
<KanbanListHeader onClick={deleteSelf}>
|
||||||
|
<X size={24} />
|
||||||
|
</KanbanListHeader>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
<Droppable
|
<Droppable
|
||||||
droppableId={
|
droppableId={category.id}
|
||||||
category.id
|
key={category.id}>
|
||||||
}
|
{(provided) =>
|
||||||
key={
|
|
||||||
category.id
|
|
||||||
}>
|
|
||||||
{(
|
|
||||||
provided,
|
|
||||||
) =>
|
|
||||||
(
|
(
|
||||||
<div
|
<div
|
||||||
ref={
|
ref={provided.innerRef}
|
||||||
provided.innerRef
|
|
||||||
}
|
|
||||||
{...provided.droppableProps}>
|
{...provided.droppableProps}>
|
||||||
{category.channels.map(
|
{category.channels.map(
|
||||||
(
|
(x, index) => {
|
||||||
x,
|
|
||||||
index,
|
|
||||||
) => {
|
|
||||||
const channel =
|
const channel =
|
||||||
server.client.channels.get(
|
server.client.channels.get(
|
||||||
x,
|
x,
|
||||||
);
|
);
|
||||||
if (
|
if (!channel)
|
||||||
!channel
|
|
||||||
)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
key={
|
key={x}
|
||||||
x
|
draggableId={x}
|
||||||
}
|
index={index}>
|
||||||
draggableId={
|
{(provided) =>
|
||||||
x
|
|
||||||
}
|
|
||||||
index={
|
|
||||||
index
|
|
||||||
}>
|
|
||||||
{(
|
|
||||||
provided,
|
|
||||||
) =>
|
|
||||||
(
|
(
|
||||||
<div
|
<div
|
||||||
{...(provided.draggableProps as any)}
|
{...(provided.draggableProps as any)}
|
||||||
|
@ -309,150 +455,27 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
{
|
{provided.placeholder}
|
||||||
provided.placeholder
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
) as any
|
) as any
|
||||||
}
|
}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
|
<KanbanListHeader
|
||||||
|
onClick={() =>
|
||||||
|
openScreen({
|
||||||
|
id: "special_prompt",
|
||||||
|
type: "create_channel",
|
||||||
|
target: server,
|
||||||
|
cb: addChannel,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<Plus size={24} />
|
||||||
|
</KanbanListHeader>
|
||||||
</div>
|
</div>
|
||||||
</KanbanList>
|
</KanbanList>
|
||||||
</div>
|
</div>
|
||||||
) as any
|
) as any
|
||||||
}
|
}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
))}
|
|
||||||
{provided.placeholder}
|
|
||||||
</KanbanBoard>
|
|
||||||
</div>
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
</Droppable>
|
|
||||||
</FullSize>
|
|
||||||
</DragDropContext>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
// ! FIXME: really bad code
|
|
||||||
export const Categories0 = observer(({ server }: Props) => {
|
|
||||||
const channels = server.channels.filter((x) => typeof x !== "undefined");
|
|
||||||
|
|
||||||
const [cats, setCats] = useState<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={() => server.edit({ 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
|
|
||||||
key={channel!._id}
|
|
||||||
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 key={x.id} value={x.id}>
|
|
||||||
{x.title}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</ComboBox>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in a new issue