mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-28 18:00:59 -05:00
Merge pull request #360 from revoltchat/rework/categories-kanban
This commit is contained in:
commit
92597ab1cd
10 changed files with 699 additions and 152 deletions
|
@ -43,6 +43,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"klaw": "^3.0.0",
|
"klaw": "^3.0.0",
|
||||||
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"sirv-cli": "^1.0.14",
|
"sirv-cli": "^1.0.14",
|
||||||
"vite": "npm:@insertish/vite@2.4.0-beta.3-dynamic-import-css-3c1466b"
|
"vite": "npm:@insertish/vite@2.4.0-beta.3-dynamic-import-css-3c1466b"
|
||||||
},
|
},
|
||||||
|
@ -81,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",
|
||||||
|
|
|
@ -48,11 +48,11 @@ export type UploadState =
|
||||||
| { type: "none" }
|
| { type: "none" }
|
||||||
| { type: "attached"; files: File[] }
|
| { type: "attached"; files: File[] }
|
||||||
| {
|
| {
|
||||||
type: "uploading";
|
type: "uploading";
|
||||||
files: File[];
|
files: File[];
|
||||||
percent: number;
|
percent: number;
|
||||||
cancel: CancelTokenSource;
|
cancel: CancelTokenSource;
|
||||||
}
|
}
|
||||||
| { type: "sending"; files: File[] }
|
| { type: "sending"; files: File[] }
|
||||||
| { type: "failed"; files: File[]; error: string };
|
| { type: "failed"; files: File[]; error: string };
|
||||||
|
|
||||||
|
@ -173,9 +173,9 @@ export default observer(({ channel }: Props) => {
|
||||||
const text =
|
const text =
|
||||||
action === "quote"
|
action === "quote"
|
||||||
? `${content
|
? `${content
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((x) => `> ${x}`)
|
.map((x) => `> ${x}`)
|
||||||
.join("\n")}\n\n`
|
.join("\n")}\n\n`
|
||||||
: `${content} `;
|
: `${content} `;
|
||||||
|
|
||||||
if (!draft || draft.length === 0) {
|
if (!draft || draft.length === 0) {
|
||||||
|
@ -225,8 +225,8 @@ export default observer(({ channel }: Props) => {
|
||||||
toReplace == ""
|
toReplace == ""
|
||||||
? msg.content.toString() + newText
|
? msg.content.toString() + newText
|
||||||
: msg.content
|
: msg.content
|
||||||
.toString()
|
.toString()
|
||||||
.replace(new RegExp(toReplace, flags), newText);
|
.replace(new RegExp(toReplace, flags), newText);
|
||||||
|
|
||||||
if (newContent != msg.content) {
|
if (newContent != msg.content) {
|
||||||
if (newContent.length == 0) {
|
if (newContent.length == 0) {
|
||||||
|
@ -305,10 +305,10 @@ export default observer(({ channel }: Props) => {
|
||||||
files,
|
files,
|
||||||
percent: Math.round(
|
percent: Math.round(
|
||||||
(i * 100 + (100 * e.loaded) / e.total) /
|
(i * 100 + (100 * e.loaded) / e.total) /
|
||||||
Math.min(
|
Math.min(
|
||||||
files.length,
|
files.length,
|
||||||
CAN_UPLOAD_AT_ONCE,
|
CAN_UPLOAD_AT_ONCE,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
cancel,
|
cancel,
|
||||||
}),
|
}),
|
||||||
|
@ -398,6 +398,7 @@ export default observer(({ channel }: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: change to useDebounceCallback
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const debouncedStopTyping = useCallback(
|
const debouncedStopTyping = useCallback(
|
||||||
debounce(stopTyping as (...args: unknown[]) => void, 1000),
|
debounce(stopTyping as (...args: unknown[]) => void, 1000),
|
||||||
|
@ -553,13 +554,13 @@ export default observer(({ channel }: Props) => {
|
||||||
placeholder={
|
placeholder={
|
||||||
channel.channel_type === "DirectMessage"
|
channel.channel_type === "DirectMessage"
|
||||||
? translate("app.main.channel.message_who", {
|
? translate("app.main.channel.message_who", {
|
||||||
person: channel.recipient?.username,
|
person: channel.recipient?.username,
|
||||||
})
|
})
|
||||||
: channel.channel_type === "SavedMessages"
|
: channel.channel_type === "SavedMessages"
|
||||||
? translate("app.main.channel.message_saved")
|
? translate("app.main.channel.message_saved")
|
||||||
: translate("app.main.channel.message_where", {
|
: translate("app.main.channel.message_where", {
|
||||||
channel_name: channel.name ?? undefined,
|
channel_name: channel.name ?? undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
uploadState.type === "uploading" ||
|
uploadState.type === "uploading" ||
|
||||||
|
|
32
src/components/ui/SaveStatus.tsx
Normal file
32
src/components/ui/SaveStatus.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Check, CloudUpload } from "@styled-icons/boxicons-regular";
|
||||||
|
import { Pencil } from "@styled-icons/boxicons-solid";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const StatusBase = styled.div`
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type EditStatus = "saved" | "editing" | "saving";
|
||||||
|
interface Props {
|
||||||
|
status: EditStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SaveStatus({ status }: Props) {
|
||||||
|
return (
|
||||||
|
<StatusBase>
|
||||||
|
{status === "saved" ? (
|
||||||
|
<Check size={20} />
|
||||||
|
) : status === "editing" ? (
|
||||||
|
<Pencil size={20} />
|
||||||
|
) : (
|
||||||
|
<CloudUpload size={20} />
|
||||||
|
)}
|
||||||
|
{/* FIXME: add i18n */}
|
||||||
|
<span>{status}</span>
|
||||||
|
</StatusBase>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
|
|
||||||
history.push(
|
if (props.cb) {
|
||||||
`/server/${props.target._id}/channel/${channel._id}`,
|
props.cb(channel);
|
||||||
);
|
} else {
|
||||||
|
history.push(
|
||||||
|
`/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,3 +1,7 @@
|
||||||
|
import isEqual from "lodash.isequal";
|
||||||
|
|
||||||
|
import { Inputs, useCallback, useEffect, useRef } from "preact/hooks";
|
||||||
|
|
||||||
export function debounce(cb: (...args: unknown[]) => void, duration: number) {
|
export function debounce(cb: (...args: unknown[]) => void, duration: number) {
|
||||||
// Store the timer variable.
|
// Store the timer variable.
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
|
@ -13,3 +17,60 @@ export function debounce(cb: (...args: unknown[]) => void, duration: number) {
|
||||||
}, duration);
|
}, duration);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useDebounceCallback(
|
||||||
|
cb: (...args: unknown[]) => void,
|
||||||
|
inputs: Inputs,
|
||||||
|
duration = 1000,
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return useCallback(
|
||||||
|
debounce(cb as (...args: unknown[]) => void, duration),
|
||||||
|
inputs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAutosaveCallback(
|
||||||
|
cb: (...args: unknown[]) => void,
|
||||||
|
inputs: Inputs,
|
||||||
|
duration = 1000,
|
||||||
|
) {
|
||||||
|
const ref = useRef(cb);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const callback = useCallback(
|
||||||
|
debounce(() => ref.current(), duration),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = cb;
|
||||||
|
callback();
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [cb, callback, ...inputs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAutosave<T>(
|
||||||
|
cb: () => void,
|
||||||
|
dependency: T,
|
||||||
|
initialValue: T,
|
||||||
|
onBeginChange?: () => void,
|
||||||
|
duration?: number,
|
||||||
|
) {
|
||||||
|
if (onBeginChange) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
!isEqual(dependency, initialValue) && onBeginChange();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line
|
||||||
|
[dependency],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return useAutosaveCallback(
|
||||||
|
() => !isEqual(dependency, initialValue) && cb(),
|
||||||
|
[dependency],
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
56
src/lib/dnd.ts
Normal file
56
src/lib/dnd.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import {
|
||||||
|
Draggable as rbdDraggable,
|
||||||
|
DraggableProps as rbdDraggableProps,
|
||||||
|
DraggableProvided as rbdDraggableProvided,
|
||||||
|
DraggableProvidedDraggableProps as rbdDraggableProvidedDraggableProps,
|
||||||
|
DraggableProvidedDragHandleProps as rbdDraggableProvidedDragHandleProps,
|
||||||
|
DraggableRubric,
|
||||||
|
DraggableStateSnapshot,
|
||||||
|
Droppable as rbdDroppable,
|
||||||
|
DroppableProps,
|
||||||
|
DroppableProvided,
|
||||||
|
DroppableStateSnapshot,
|
||||||
|
} from "react-beautiful-dnd";
|
||||||
|
|
||||||
|
export type DraggableProvidedDraggableProps = Omit<
|
||||||
|
rbdDraggableProvidedDraggableProps,
|
||||||
|
"style" | "onTransitionEnd"
|
||||||
|
> & {
|
||||||
|
style?: string;
|
||||||
|
onTransitionEnd?: JSX.TransitionEventHandler<HTMLElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DraggableProvidedDragHandleProps = Omit<
|
||||||
|
rbdDraggableProvidedDragHandleProps,
|
||||||
|
"onDragStart"
|
||||||
|
> & {
|
||||||
|
onDragStart?: JSX.DragEventHandler<HTMLElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DraggableProvided = rbdDraggableProvided & {
|
||||||
|
draggableProps: DraggableProvidedDraggableProps;
|
||||||
|
dragHandleProps?: DraggableProvidedDragHandleProps | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DraggableChildrenFn = (
|
||||||
|
provided: DraggableProvided,
|
||||||
|
snapshot: DraggableStateSnapshot,
|
||||||
|
rubric: DraggableRubric,
|
||||||
|
) => JSX.Element;
|
||||||
|
|
||||||
|
export type DraggableProps = Omit<rbdDraggableProps, "children"> & {
|
||||||
|
children: DraggableChildrenFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Draggable = rbdDraggable as unknown as (
|
||||||
|
props: DraggableProps,
|
||||||
|
) => JSX.Element;
|
||||||
|
|
||||||
|
export const Droppable = rbdDroppable as unknown as (
|
||||||
|
props: Omit<DroppableProps, "children"> & {
|
||||||
|
children(
|
||||||
|
provided: DroppableProvided,
|
||||||
|
snapshot: DroppableStateSnapshot,
|
||||||
|
): JSX.Element;
|
||||||
|
},
|
||||||
|
) => JSX.Element;
|
|
@ -50,6 +50,7 @@ export default observer(() => {
|
||||||
title: (
|
title: (
|
||||||
<Text id="app.settings.server_pages.categories.title" />
|
<Text id="app.settings.server_pages.categories.title" />
|
||||||
),
|
),
|
||||||
|
hideTitle: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "members",
|
id: "members",
|
||||||
|
|
|
@ -1,139 +1,460 @@
|
||||||
import isEqual from "lodash.isequal";
|
import { Plus, X } from "@styled-icons/boxicons-regular";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { DragDropContext } 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 { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
import { useState } from "preact/hooks";
|
import { Text } from "preact-i18n";
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useAutosave } from "../../../lib/debounce";
|
||||||
|
import { Draggable, Droppable } from "../../../lib/dnd";
|
||||||
|
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 SaveStatus, { EditStatus } from "../../../components/ui/SaveStatus";
|
||||||
import ComboBox from "../../../components/ui/ComboBox";
|
|
||||||
import InputBox from "../../../components/ui/InputBox";
|
const KanbanEntry = styled.div`
|
||||||
import Tip from "../../../components/ui/Tip";
|
padding: 2px 4px;
|
||||||
|
|
||||||
|
> .inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 4px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: var(--primary-background);
|
||||||
|
|
||||||
|
img {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const KanbanList = styled.div<{ last: boolean }>`
|
||||||
|
${(props) =>
|
||||||
|
!props.last &&
|
||||||
|
css`
|
||||||
|
padding-inline-end: 4px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
> .inner {
|
||||||
|
width: 180px;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
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 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`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FullSize = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
server: Server;
|
server: Server;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! FIXME: really bad code
|
|
||||||
export const Categories = observer(({ server }: Props) => {
|
export const Categories = observer(({ server }: Props) => {
|
||||||
const channels = server.channels.filter((x) => typeof x !== "undefined");
|
const [status, setStatus] = useState<EditStatus>("saved");
|
||||||
|
const [categories, setCategories] = useState<Category[]>(
|
||||||
|
server.categories ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
const [cats, setCats] = useState<Category[]>(server.categories ?? []);
|
useAutosave(
|
||||||
const [name, setName] = useState("");
|
async () => {
|
||||||
|
setStatus("saving");
|
||||||
|
await server.edit({ categories });
|
||||||
|
setStatus("saved");
|
||||||
|
},
|
||||||
|
categories,
|
||||||
|
server.categories,
|
||||||
|
() => 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 (
|
||||||
<div>
|
<>
|
||||||
<Tip warning>This section is under construction.</Tip>
|
<Header>
|
||||||
<p>
|
<h1>
|
||||||
<Button
|
<Text id={`app.settings.server_pages.categories.title`} />
|
||||||
contrast
|
</h1>
|
||||||
disabled={isEqual(server.categories ?? [], cats)}
|
<SaveStatus status={status} />
|
||||||
onClick={() => server.edit({ categories: cats })}>
|
</Header>
|
||||||
save categories
|
<DragDropContext
|
||||||
</Button>
|
onDragEnd={(target) => {
|
||||||
</p>
|
const { destination, source, draggableId, type } = target;
|
||||||
<h2>categories</h2>
|
|
||||||
{cats.map((category) => (
|
if (
|
||||||
<div style={{ background: "var(--hover)" }} key={category.id}>
|
!destination ||
|
||||||
<InputBox
|
(destination.droppableId === source.droppableId &&
|
||||||
value={category.title}
|
destination.index === source.index)
|
||||||
onChange={(e) =>
|
) {
|
||||||
setCats(
|
return;
|
||||||
cats.map((y) =>
|
}
|
||||||
y.id === category.id
|
|
||||||
? {
|
if (type === "column") {
|
||||||
...y,
|
if (destination.index === 0) return;
|
||||||
title: e.currentTarget.value,
|
|
||||||
}
|
// Remove from array.
|
||||||
: y,
|
const cat = categories.find(
|
||||||
),
|
(x) => x.id === draggableId,
|
||||||
)
|
);
|
||||||
}
|
const arr = categories.filter(
|
||||||
contrast
|
(x) => x.id !== draggableId,
|
||||||
/>
|
);
|
||||||
<Button
|
|
||||||
contrast
|
// Insert at new position.
|
||||||
onClick={() =>
|
arr.splice(destination.index - 1, 0, cat!);
|
||||||
setCats(cats.filter((x) => x.id !== category.id))
|
setCategories(arr);
|
||||||
}>
|
} else {
|
||||||
delete {category.title}
|
setCategories(
|
||||||
</Button>
|
categories.map((category) => {
|
||||||
</div>
|
if (category.id === destination.droppableId) {
|
||||||
))}
|
const channels = category.channels.filter(
|
||||||
<h2>create new</h2>
|
(x) => x !== draggableId,
|
||||||
<p>
|
);
|
||||||
<InputBox
|
|
||||||
value={name}
|
channels.splice(
|
||||||
onChange={(e) => setName(e.currentTarget.value)}
|
destination.index,
|
||||||
contrast
|
0,
|
||||||
/>
|
draggableId,
|
||||||
<Button
|
);
|
||||||
contrast
|
|
||||||
onClick={() => {
|
return {
|
||||||
setName("");
|
...category,
|
||||||
setCats([
|
channels,
|
||||||
...cats,
|
};
|
||||||
{
|
} else if (category.id === source.droppableId) {
|
||||||
id: ulid(),
|
return {
|
||||||
title: name,
|
...category,
|
||||||
channels: [],
|
channels: category.channels.filter(
|
||||||
},
|
(x) => x !== draggableId,
|
||||||
]);
|
),
|
||||||
}}>
|
};
|
||||||
create
|
}
|
||||||
</Button>
|
|
||||||
</p>
|
return category;
|
||||||
<h2>channels</h2>
|
}),
|
||||||
{channels.map((channel) => {
|
);
|
||||||
return (
|
}
|
||||||
<div
|
}}>
|
||||||
key={channel!._id}
|
<FullSize>
|
||||||
style={{
|
<Droppable
|
||||||
display: "flex",
|
droppableId="categories"
|
||||||
gap: "12px",
|
direction="horizontal"
|
||||||
alignItems: "center",
|
type="column">
|
||||||
}}>
|
{(provided) => (
|
||||||
<div style={{ flexShrink: 0 }}>
|
<div
|
||||||
<ChannelIcon target={channel} size={24} />{" "}
|
ref={provided.innerRef}
|
||||||
<span>{channel!.name}</span>
|
{...provided.droppableProps}>
|
||||||
</div>
|
<KanbanBoard>
|
||||||
<ComboBox
|
<ListElement
|
||||||
style={{ flexGrow: 1 }}
|
category={defaultCategory}
|
||||||
value={
|
server={server}
|
||||||
cats.find((x) =>
|
index={0}
|
||||||
x.channels.includes(channel!._id),
|
addChannel={noop}
|
||||||
)?.id ?? "none"
|
/>
|
||||||
}
|
{categories.map((category, index) => (
|
||||||
onChange={(e) =>
|
<ListElement
|
||||||
setCats(
|
draggable
|
||||||
cats.map((x) => {
|
category={category}
|
||||||
return {
|
server={server}
|
||||||
...x,
|
index={index + 1}
|
||||||
channels: [
|
key={category.id}
|
||||||
...x.channels.filter(
|
setTitle={(title) => {
|
||||||
(y) => y !== channel!._id,
|
setCategories(
|
||||||
),
|
categories.map((x) =>
|
||||||
...(e.currentTarget.value ===
|
x.id === category.id
|
||||||
x.id
|
? {
|
||||||
? [channel!._id]
|
...x,
|
||||||
: []),
|
title,
|
||||||
],
|
}
|
||||||
};
|
: x,
|
||||||
}),
|
),
|
||||||
)
|
);
|
||||||
}>
|
}}
|
||||||
<option value="none">Uncategorised</option>
|
deleteSelf={() =>
|
||||||
{cats.map((x) => (
|
setCategories(
|
||||||
<option key={x.id} value={x.id}>
|
categories.filter(
|
||||||
{x.title}
|
(x) =>
|
||||||
</option>
|
x.id !==
|
||||||
))}
|
category.id,
|
||||||
</ComboBox>
|
),
|
||||||
</div>
|
)
|
||||||
);
|
}
|
||||||
})}
|
addChannel={(channel) => {
|
||||||
</div>
|
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>
|
||||||
|
)}
|
||||||
|
</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
|
||||||
|
isDragDisabled={!draggable}
|
||||||
|
key={category.id}
|
||||||
|
draggableId={category.id}
|
||||||
|
index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div {...provided.draggableProps} ref={provided.innerRef}>
|
||||||
|
<KanbanList last={false} key={category.id}>
|
||||||
|
<div class="inner">
|
||||||
|
<Row>
|
||||||
|
<KanbanListHeader {...provided.dragHandleProps}>
|
||||||
|
{editing ? (
|
||||||
|
<input
|
||||||
|
value={editing}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditing(
|
||||||
|
e.currentTarget.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onKeyDown={(e) =>
|
||||||
|
e.key === "Enter" && save()
|
||||||
|
}
|
||||||
|
id={category.id}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span onClick={startEditing}>
|
||||||
|
{category.title}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</KanbanListHeader>
|
||||||
|
{deleteSelf && (
|
||||||
|
<KanbanListHeader onClick={deleteSelf}>
|
||||||
|
<X size={24} />
|
||||||
|
</KanbanListHeader>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
<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 (
|
||||||
|
<Draggable
|
||||||
|
key={x}
|
||||||
|
draggableId={x}
|
||||||
|
index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
ref={
|
||||||
|
provided.innerRef
|
||||||
|
}>
|
||||||
|
<KanbanEntry>
|
||||||
|
<div class="inner">
|
||||||
|
<ChannelIcon
|
||||||
|
target={
|
||||||
|
channel
|
||||||
|
}
|
||||||
|
size={
|
||||||
|
24
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
channel.name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</KanbanEntry>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
<KanbanListHeader
|
||||||
|
onClick={() =>
|
||||||
|
openScreen({
|
||||||
|
id: "special_prompt",
|
||||||
|
type: "create_channel",
|
||||||
|
target: server,
|
||||||
|
cb: addChannel,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<Plus size={24} />
|
||||||
|
</KanbanListHeader>
|
||||||
|
</div>
|
||||||
|
</KanbanList>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
58
yarn.lock
58
yarn.lock
|
@ -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"
|
||||||
|
@ -1981,6 +1988,13 @@ crypto-random-string@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||||
|
|
||||||
|
css-box-model@^1.2.0:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||||
|
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||||
|
dependencies:
|
||||||
|
tiny-invariant "^1.0.6"
|
||||||
|
|
||||||
css-color-keywords@^1.0.0:
|
css-color-keywords@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
||||||
|
@ -3069,6 +3083,11 @@ mdurl@^1.0.1:
|
||||||
sdp-transform "^2.14.1"
|
sdp-transform "^2.14.1"
|
||||||
supports-color "^8.1.1"
|
supports-color "^8.1.1"
|
||||||
|
|
||||||
|
memoize-one@^5.1.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
|
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||||
|
|
||||||
merge-stream@^2.0.0:
|
merge-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
|
@ -3365,6 +3384,11 @@ queue-microtask@^1.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
raf-schd@^4.0.2:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
|
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||||
|
|
||||||
randombytes@^2.1.0:
|
randombytes@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||||
|
@ -3372,6 +3396,19 @@ randombytes@^2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.1.0"
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
|
react-beautiful-dnd@^13.1.0:
|
||||||
|
version "13.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
|
||||||
|
integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
css-box-model "^1.2.0"
|
||||||
|
memoize-one "^5.1.1"
|
||||||
|
raf-schd "^4.0.2"
|
||||||
|
react-redux "^7.2.0"
|
||||||
|
redux "^4.0.4"
|
||||||
|
use-memo-one "^1.1.1"
|
||||||
|
|
||||||
react-device-detect@^1.17.0:
|
react-device-detect@^1.17.0:
|
||||||
version "1.17.0"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.17.0.tgz#a00b4fd6880cebfab3fd8a42a79dc0290cdddca9"
|
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.17.0.tgz#a00b4fd6880cebfab3fd8a42a79dc0290cdddca9"
|
||||||
|
@ -3409,6 +3446,18 @@ react-overlapping-panels@1.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/react-overlapping-panels/-/react-overlapping-panels-1.2.2.tgz#16b60ed60045a7fa40bcf321de113c655f6e0acd"
|
resolved "https://registry.yarnpkg.com/react-overlapping-panels/-/react-overlapping-panels-1.2.2.tgz#16b60ed60045a7fa40bcf321de113c655f6e0acd"
|
||||||
integrity sha512-jZ8ZT4tnqM2YQF91Ct+9dLk7rSjnNiudxzgKlsaVfgwEjdBAWtE8nWJX9d2jDZZ9qimWgg43u5+SF6U+ELjyKQ==
|
integrity sha512-jZ8ZT4tnqM2YQF91Ct+9dLk7rSjnNiudxzgKlsaVfgwEjdBAWtE8nWJX9d2jDZZ9qimWgg43u5+SF6U+ELjyKQ==
|
||||||
|
|
||||||
|
react-redux@^7.2.0:
|
||||||
|
version "7.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8"
|
||||||
|
integrity sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.1"
|
||||||
|
"@types/react-redux" "^7.1.16"
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.13.1"
|
||||||
|
|
||||||
react-redux@^7.2.4:
|
react-redux@^7.2.4:
|
||||||
version "7.2.4"
|
version "7.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
|
||||||
|
@ -3484,7 +3533,7 @@ readdirp@~3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
redux@^4.0.0, redux@^4.1.0:
|
redux@^4.0.0, redux@^4.0.4, redux@^4.1.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
|
||||||
integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==
|
integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==
|
||||||
|
@ -3963,7 +4012,7 @@ text-table@^0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||||
|
|
||||||
tiny-invariant@^1.0.2:
|
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
||||||
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
||||||
|
@ -4115,6 +4164,11 @@ uri-js@^4.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
use-memo-one@^1.1.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
|
||||||
|
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
|
||||||
|
|
||||||
use-resize-observer@^7.0.0:
|
use-resize-observer@^7.0.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-7.1.0.tgz#709ea7540fbe0a60ceae41ee2bef933d7782e4d4"
|
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-7.1.0.tgz#709ea7540fbe0a60ceae41ee2bef933d7782e4d4"
|
||||||
|
|
Loading…
Reference in a new issue