mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-22 07:00:58 -05:00
feat(categories): basic dnd kanban board
This commit is contained in:
parent
314ecee95c
commit
da47348273
3 changed files with 217 additions and 18 deletions
|
@ -82,6 +82,7 @@
|
||||||
"@types/node": "^15.12.4",
|
"@types/node": "^15.12.4",
|
||||||
"@types/preact-i18n": "^2.3.0",
|
"@types/preact-i18n": "^2.3.0",
|
||||||
"@types/prismjs": "^1.16.5",
|
"@types/prismjs": "^1.16.5",
|
||||||
|
"@types/react-beautiful-dnd": "^13.1.2",
|
||||||
"@types/react-helmet": "^6.1.1",
|
"@types/react-helmet": "^6.1.1",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/react-scroll": "^1.8.2",
|
"@types/react-scroll": "^1.8.2",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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 { 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 from "styled-components";
|
import styled from "styled-components";
|
||||||
|
@ -28,10 +29,25 @@ const KanbanEntry = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
gap: 4px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
padding: 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
font-size: 0.9em;
|
||||||
background: var(--primary-background);
|
background: var(--primary-background);
|
||||||
|
|
||||||
|
img {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const KanbanList = styled.div`
|
const KanbanList = styled.div`
|
||||||
|
@ -42,15 +58,33 @@ const KanbanList = styled.div`
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
|
|
||||||
|
> [data-rbd-droppable-id] {
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const KanbanListTitle = styled.div`
|
||||||
|
height: 42px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const KanbanBoard = styled.div`
|
const KanbanBoard = styled.div`
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: scroll;
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const FullSize = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
server: Server;
|
server: Server;
|
||||||
}
|
}
|
||||||
|
@ -61,24 +95,181 @@ export const Categories = observer(({ server }: Props) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DragDropContext
|
||||||
|
onDragEnd={(target) => {
|
||||||
|
const { destination, source, draggableId, type } = target;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!destination ||
|
||||||
|
(destination.droppableId === source.droppableId &&
|
||||||
|
destination.index === source.index)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "column") {
|
||||||
|
// Remove from array.
|
||||||
|
const cat = categories.find((x) => x.id === draggableId);
|
||||||
|
const arr = categories.filter((x) => x.id !== draggableId);
|
||||||
|
|
||||||
|
// Insert at new position.
|
||||||
|
arr.splice(destination.index, 0, cat!);
|
||||||
|
setCategories(arr);
|
||||||
|
} else {
|
||||||
|
setCategories(
|
||||||
|
categories.map((category) => {
|
||||||
|
if (category.id === destination.droppableId) {
|
||||||
|
const channels = category.channels.filter(
|
||||||
|
(x) => x !== draggableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
channels.splice(
|
||||||
|
destination.index,
|
||||||
|
0,
|
||||||
|
draggableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...category,
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
} else if (category.id === source.droppableId) {
|
||||||
|
return {
|
||||||
|
...category,
|
||||||
|
channels: category.channels.filter(
|
||||||
|
(x) => x !== draggableId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return category;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<FullSize>
|
||||||
|
<Droppable
|
||||||
|
droppableId="categories"
|
||||||
|
direction="horizontal"
|
||||||
|
type="column">
|
||||||
|
{(provided) =>
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.droppableProps}>
|
||||||
<KanbanBoard>
|
<KanbanBoard>
|
||||||
{categories.map((category, i) => (
|
{categories.map((category, index) => (
|
||||||
<KanbanList key={category.id}>
|
<Draggable
|
||||||
<h3>{category.title}</h3>
|
key={category.id}
|
||||||
{category.channels.map((x) => {
|
draggableId={category.id}
|
||||||
const channel = server.client.channels.get(x);
|
index={index}>
|
||||||
if (!channel) return null;
|
{(provided) =>
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
{...(provided.draggableProps as any)}
|
||||||
|
ref={provided.innerRef}>
|
||||||
|
<KanbanList
|
||||||
|
key={category.id}>
|
||||||
|
<KanbanListTitle
|
||||||
|
{...(provided.dragHandleProps as any)}>
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
category.title
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</KanbanListTitle>
|
||||||
|
<Droppable
|
||||||
|
droppableId={
|
||||||
|
category.id
|
||||||
|
}
|
||||||
|
key={
|
||||||
|
category.id
|
||||||
|
}>
|
||||||
|
{(provided) =>
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
ref={
|
||||||
|
provided.innerRef
|
||||||
|
}
|
||||||
|
{...provided.droppableProps}>
|
||||||
|
{category.channels.map(
|
||||||
|
(
|
||||||
|
x,
|
||||||
|
index,
|
||||||
|
) => {
|
||||||
|
const channel =
|
||||||
|
server.client.channels.get(
|
||||||
|
x,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!channel
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KanbanEntry key={x}>
|
<Draggable
|
||||||
<ChannelIcon target={channel} size={24} />{" "}
|
key={
|
||||||
{channel.name}
|
x
|
||||||
|
}
|
||||||
|
draggableId={
|
||||||
|
x
|
||||||
|
}
|
||||||
|
index={
|
||||||
|
index
|
||||||
|
}>
|
||||||
|
{(
|
||||||
|
provided,
|
||||||
|
) =>
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
{...(provided.draggableProps as any)}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
ref={
|
||||||
|
provided.innerRef
|
||||||
|
}>
|
||||||
|
<KanbanEntry>
|
||||||
|
<ChannelIcon
|
||||||
|
target={
|
||||||
|
channel
|
||||||
|
}
|
||||||
|
size={
|
||||||
|
24
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
channel.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</KanbanEntry>
|
</KanbanEntry>
|
||||||
|
</div>
|
||||||
|
) as any
|
||||||
|
}
|
||||||
|
</Draggable>
|
||||||
);
|
);
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
provided.placeholder
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) as any
|
||||||
|
}
|
||||||
|
</Droppable>
|
||||||
</KanbanList>
|
</KanbanList>
|
||||||
|
</div>
|
||||||
|
) as any
|
||||||
|
}
|
||||||
|
</Draggable>
|
||||||
))}
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
</KanbanBoard>
|
</KanbanBoard>
|
||||||
|
</div>
|
||||||
|
) as any
|
||||||
|
}
|
||||||
|
</Droppable>
|
||||||
|
</FullSize>
|
||||||
|
</DragDropContext>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1398,6 +1398,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
||||||
|
|
||||||
|
"@types/react-beautiful-dnd@^13.1.2":
|
||||||
|
version "13.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130"
|
||||||
|
integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-helmet@^6.1.1":
|
"@types/react-helmet@^6.1.1":
|
||||||
version "6.1.2"
|
version "6.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.2.tgz#e9d7d16b29e4ec5716711c52c35c3cec45819eac"
|
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.2.tgz#e9d7d16b29e4ec5716711c52c35c3cec45819eac"
|
||||||
|
|
Loading…
Reference in a new issue