From c208064d2c7a383e18cb3edf69f10dc8896911d8 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 30 Oct 2021 19:38:18 +0100 Subject: [PATCH] feat(categories): include uncategorised channels; add category / channel; delete category --- src/context/intermediate/Intermediate.tsx | 14 +- src/context/intermediate/modals/Prompt.tsx | 23 +- src/pages/settings/server/Categories.tsx | 515 +++++++++++---------- 3 files changed, 297 insertions(+), 255 deletions(-) diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 149e1043..252e44da 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -2,6 +2,7 @@ import { Prompt } from "react-router"; import { useHistory } from "react-router-dom"; import type { Attachment } from "revolt-api/types/Autumn"; import { Bot } from "revolt-api/types/Bots"; +import { TextChannel, VoiceChannel } from "revolt-api/types/Channels"; import type { EmbedImage } from "revolt-api/types/January"; import { Channel } from "revolt.js/dist/maps/Channels"; import { Message } from "revolt.js/dist/maps/Messages"; @@ -42,7 +43,12 @@ export type Screen = | { type: "leave_server"; target: Server } | { type: "delete_server"; target: Server } | { 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: "create_invite"; @@ -52,7 +58,11 @@ export type Screen = | { type: "ban_member"; target: Server; user: User } | { type: "unfriend_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 } )) | ({ id: "special_input" } & ( diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index 79258ce5..f5ba994c 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react-lite"; import { useHistory } from "react-router-dom"; +import { TextChannel, VoiceChannel } from "revolt-api/types/Channels"; import { Channel } from "revolt.js/dist/maps/Channels"; import { Message as MessageI } from "revolt.js/dist/maps/Messages"; import { Server } from "revolt.js/dist/maps/Servers"; @@ -60,7 +61,7 @@ type SpecialProps = { onClose: () => void } & ( | { type: "leave_server"; target: Server } | { type: "delete_server"; target: Server } | { 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: "create_invite"; @@ -70,7 +71,11 @@ type SpecialProps = { onClose: () => void } & ( | { type: "ban_member"; target: Server; user: User } | { type: "unfriend_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 } ); @@ -158,7 +163,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { break; case "delete_bot": client.bots.delete(props.target); - props.cb(); + props.cb?.(); break; } @@ -424,9 +429,14 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { nonce: ulid(), }); - history.push( - `/server/${props.target._id}/channel/${channel._id}`, - ); + if (props.cb) { + props.cb(channel); + } else { + history.push( + `/server/${props.target._id}/channel/${channel._id}`, + ); + } + onClose(); } catch (err) { setError(takeError(err)); @@ -472,7 +482,6 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { } case "create_category": { const [name, setName] = useState(""); - const history = useHistory(); return ( void; -} - -function CreateCategory({ callback }: CreateCategoryProps) { - const [name, setName] = useState(""); - - return <>; -} */ - const KanbanEntry = styled.div` padding: 2px 4px; @@ -73,16 +67,43 @@ const KanbanList = styled.div<{ last: boolean }>` flex-direction: column; 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] { min-height: 24px; } } `; -const KanbanListTitle = styled.div` - height: 42px; +const Row = styled.div` + gap: 2px; + margin: 4px; + display: flex; + + > :first-child { + flex-grow: 1; + } +`; + +const KanbanListHeader = styled.div` + height: 34px; display: grid; + min-width: 34px; place-items: center; + cursor: pointer !important; + transition: 0.2s ease background-color; + + &:hover { + background: var(--background); + } `; const KanbanBoard = styled.div` @@ -129,6 +150,19 @@ export const Categories = observer(({ server }: Props) => { () => 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 ( <>
@@ -150,6 +184,8 @@ export const Categories = observer(({ server }: Props) => { } if (type === "column") { + if (destination.index === 0) return; + // Remove from array. const cat = categories.find( (x) => x.id === draggableId, @@ -159,7 +195,7 @@ export const Categories = observer(({ server }: Props) => { ); // Insert at new position. - arr.splice(destination.index, 0, cat!); + arr.splice(destination.index - 1, 0, cat!); setCategories(arr); } else { setCategories( @@ -204,125 +240,75 @@ export const Categories = observer(({ server }: Props) => { ref={provided.innerRef} {...provided.droppableProps}> + {categories.map((category, index) => ( - - {(provided) => - ( -
- -
- - - { - category.title - } - - - - {( - provided, - ) => - ( -
- {category.channels.map( - ( - x, - index, - ) => { - const channel = - server.client.channels.get( - x, - ); - if ( - !channel - ) - return null; - - return ( - - {( - provided, - ) => - ( -
- -
- - - { - channel.name - } - -
-
-
- ) as any - } -
- ); - }, - )} - { - provided.placeholder - } -
- ) as any - } -
-
-
-
- ) as any + 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, + ), + ); + }} + /> ))} + +
+ + setCategories([ + ...categories, + { + id: ulid(), + title: "New Category", + channels: [], + }, + ]) + }> + + +
+
{provided.placeholder}
@@ -335,124 +321,161 @@ export const Categories = observer(({ server }: Props) => { ); }); -// ! FIXME: really bad code -export const Categories0 = observer(({ server }: Props) => { - const channels = server.channels.filter((x) => typeof x !== "undefined"); +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(); + const startEditing = () => setTitle && setEditing(category.title); - const [cats, setCats] = useState(server.categories ?? []); - const [name, setName] = useState(""); + 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 ( -
- This section is under construction. -

- -

-

categories

- {cats.map((category) => ( -
- - setCats( - cats.map((y) => - y.id === category.id - ? { - ...y, - title: e.currentTarget.value, - } - : y, - ), - ) - } - contrast - /> - -
- ))} -

create new

-

- setName(e.currentTarget.value)} - contrast - /> - -

-

channels

- {channels.map((channel) => { - return ( + + {(provided) => + (
-
- {" "} - {channel!.name} -
- - 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] - : []), - ], - }; - }), - ) - }> - - {cats.map((x) => ( - - ))} - + {...(provided.draggableProps as any)} + ref={provided.innerRef}> + +
+ + + {editing ? ( + + setEditing( + e.currentTarget.value, + ) + } + onKeyDown={(e) => + e.key === "Enter" && save() + } + id={category.id} + /> + ) : ( + + {category.title} + + )} + + {deleteSelf && ( + + + + )} + + + {(provided) => + ( +
+ {category.channels.map( + (x, index) => { + const channel = + server.client.channels.get( + x, + ); + if (!channel) + return null; + + return ( + + {(provided) => + ( +
+ +
+ + + { + channel.name + } + +
+
+
+ ) as any + } +
+ ); + }, + )} + {provided.placeholder} +
+ ) as any + } +
+ + openScreen({ + id: "special_prompt", + type: "create_channel", + target: server, + cb: addChannel, + }) + }> + + +
+
- ); - })} -
+ ) as any + } + ); -}); +}