From e6ccc0174a86a0ade240e6551228598cd81f984b Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Aug 2023 00:29:35 +0800 Subject: ... --- FrontEnd/src/pages/timeline/TimelinePostList.tsx | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 FrontEnd/src/pages/timeline/TimelinePostList.tsx (limited to 'FrontEnd/src/pages/timeline/TimelinePostList.tsx') diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx new file mode 100644 index 00000000..a3501b33 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -0,0 +1,76 @@ +import { useMemo, Fragment } from "react"; + +import { HttpTimelinePostInfo } from "@/http/timeline"; + +import TimelinePostView from "./TimelinePostView"; +import TimelineDateLabel from "./TimelineDateLabel"; + +import "./TimelinePostList.css"; + +function dateEqual(left: Date, right: Date): boolean { + return ( + left.getDate() == right.getDate() && + left.getMonth() == right.getMonth() && + left.getFullYear() == right.getFullYear() + ); +} + +interface TimelinePostListViewProps { + posts: HttpTimelinePostInfo[]; + onReload: () => void; +} + +export default function TimelinePostList(props: TimelinePostListViewProps) { + const { posts, onReload } = props; + + const groupedPosts = useMemo< + { + date: Date; + posts: (HttpTimelinePostInfo & { index: number })[]; + }[] + >(() => { + const result: { + date: Date; + posts: (HttpTimelinePostInfo & { index: number })[]; + }[] = []; + let index = 0; + for (const post of posts) { + const time = new Date(post.time); + if (result.length === 0) { + result.push({ date: time, posts: [{ ...post, index }] }); + } else { + const lastGroup = result[result.length - 1]; + if (dateEqual(lastGroup.date, time)) { + lastGroup.posts.push({ ...post, index }); + } else { + result.push({ date: time, posts: [{ ...post, index }] }); + } + } + index++; + } + return result; + }, [posts]); + + return ( +
+
+ {groupedPosts.map((group) => { + return ( + + + {group.posts.map((post) => { + return ( + + ); + })} + + ); + })} +
+ ); +} -- cgit v1.2.3 From f5dfd52f6efece2f4cad227044ecf4dd66301bbc Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Aug 2023 21:36:58 +0800 Subject: ... --- FrontEnd/src/App.tsx | 4 +- FrontEnd/src/components/AppBar.css | 87 ++++ FrontEnd/src/components/AppBar.tsx | 98 +++++ FrontEnd/src/components/BlobImage.tsx | 26 ++ FrontEnd/src/components/Card.css | 20 + FrontEnd/src/components/Card.tsx | 38 ++ FrontEnd/src/components/Icon.css | 3 + FrontEnd/src/components/Icon.tsx | 30 ++ FrontEnd/src/components/ImageCropper.css | 38 ++ FrontEnd/src/components/ImageCropper.tsx | 312 ++++++++++++++ FrontEnd/src/components/LoadFailReload.tsx | 37 ++ FrontEnd/src/components/LoadingPage.tsx | 13 + FrontEnd/src/components/Page.tsx | 15 + FrontEnd/src/components/SearchInput.css | 8 + FrontEnd/src/components/SearchInput.tsx | 50 +++ FrontEnd/src/components/Skeleton.css | 14 + FrontEnd/src/components/Skeleton.tsx | 32 ++ FrontEnd/src/components/Spinner.css | 13 + FrontEnd/src/components/Spinner.tsx | 36 ++ FrontEnd/src/components/TimelineLogo.tsx | 27 ++ FrontEnd/src/components/alert/AlertHost.tsx | 113 +++++ FrontEnd/src/components/alert/alert.css | 33 ++ FrontEnd/src/components/breakpoints.ts | 3 + FrontEnd/src/components/button/Button.css | 64 +++ FrontEnd/src/components/button/Button.tsx | 46 ++ FrontEnd/src/components/button/ButtonRow.css | 0 FrontEnd/src/components/button/ButtonRow.tsx | 62 +++ FrontEnd/src/components/button/ButtonRowV2.tsx | 143 +++++++ FrontEnd/src/components/button/FlatButton.css | 27 ++ FrontEnd/src/components/button/FlatButton.tsx | 36 ++ FrontEnd/src/components/button/IconButton.css | 30 ++ FrontEnd/src/components/button/IconButton.tsx | 30 ++ FrontEnd/src/components/button/LoadingButton.css | 13 + FrontEnd/src/components/button/LoadingButton.tsx | 40 ++ FrontEnd/src/components/button/index.tsx | 15 + FrontEnd/src/components/common.ts | 14 + FrontEnd/src/components/dialog/ConfirmDialog.css | 0 FrontEnd/src/components/dialog/ConfirmDialog.tsx | 59 +++ FrontEnd/src/components/dialog/Dialog.css | 60 +++ FrontEnd/src/components/dialog/Dialog.tsx | 63 +++ FrontEnd/src/components/dialog/DialogContainer.css | 20 + FrontEnd/src/components/dialog/DialogContainer.tsx | 95 +++++ FrontEnd/src/components/dialog/FullPageDialog.css | 44 ++ FrontEnd/src/components/dialog/FullPageDialog.tsx | 53 +++ FrontEnd/src/components/dialog/OperationDialog.css | 8 + FrontEnd/src/components/dialog/OperationDialog.tsx | 230 ++++++++++ FrontEnd/src/components/dialog/index.ts | 64 +++ FrontEnd/src/components/hooks.ts | 14 + FrontEnd/src/components/index.css | 100 +++++ FrontEnd/src/components/input/InputGroup.css | 54 +++ FrontEnd/src/components/input/InputGroup.tsx | 463 +++++++++++++++++++++ FrontEnd/src/components/input/index.ts | 11 + FrontEnd/src/components/list/ListContainer.css | 4 + FrontEnd/src/components/list/ListContainer.tsx | 23 + FrontEnd/src/components/list/ListItemContainer.css | 3 + FrontEnd/src/components/list/ListItemContainer.tsx | 23 + FrontEnd/src/components/list/index.ts | 4 + FrontEnd/src/components/menu/Menu.css | 36 ++ FrontEnd/src/components/menu/Menu.tsx | 62 +++ FrontEnd/src/components/menu/PopupMenu.css | 7 + FrontEnd/src/components/menu/PopupMenu.tsx | 73 ++++ FrontEnd/src/components/tab/TabPages.tsx | 71 ++++ FrontEnd/src/components/tab/Tabs.css | 33 ++ FrontEnd/src/components/tab/Tabs.tsx | 62 +++ FrontEnd/src/components/theme-color.css | 173 ++++++++ FrontEnd/src/components/theme.css | 146 +++++++ FrontEnd/src/components/user/UserAvatar.tsx | 22 + FrontEnd/src/http/bookmark.ts | 2 +- FrontEnd/src/http/timeline.ts | 2 +- FrontEnd/src/migrating/center/CenterBoards.tsx | 10 +- FrontEnd/src/migrating/center/TimelineBoard.tsx | 2 +- .../src/migrating/center/TimelineCreateDialog.tsx | 6 +- FrontEnd/src/migrating/center/index.tsx | 2 +- FrontEnd/src/pages/about/index.tsx | 4 +- FrontEnd/src/pages/loading/index.tsx | 2 +- FrontEnd/src/pages/login/index.tsx | 10 +- FrontEnd/src/pages/register/index.tsx | 10 +- FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx | 16 +- .../src/pages/setting/ChangeNicknameDialog.tsx | 8 +- .../src/pages/setting/ChangePasswordDialog.tsx | 9 +- FrontEnd/src/pages/setting/index.tsx | 18 +- FrontEnd/src/pages/timeline/CollapseButton.tsx | 2 +- FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx | 14 +- FrontEnd/src/pages/timeline/Timeline.tsx | 8 +- FrontEnd/src/pages/timeline/TimelineCard.tsx | 22 +- .../src/pages/timeline/TimelineDeleteDialog.tsx | 4 +- FrontEnd/src/pages/timeline/TimelineMember.tsx | 16 +- FrontEnd/src/pages/timeline/TimelinePostCard.tsx | 2 +- .../src/pages/timeline/TimelinePostContentView.tsx | 12 +- .../src/pages/timeline/TimelinePostCreateView.tsx | 16 +- FrontEnd/src/pages/timeline/TimelinePostList.tsx | 2 +- FrontEnd/src/pages/timeline/TimelinePostView.tsx | 16 +- .../timeline/TimelinePropertyChangeDialog.tsx | 10 +- FrontEnd/src/pages/timeline/index.tsx | 2 +- FrontEnd/src/services/TimelinePostBuilder.ts | 6 +- FrontEnd/src/services/alert.ts | 4 +- FrontEnd/src/services/timeline.ts | 4 +- FrontEnd/src/services/user.ts | 8 +- FrontEnd/src/views/common/AppBar.css | 87 ---- FrontEnd/src/views/common/AppBar.tsx | 98 ----- FrontEnd/src/views/common/BlobImage.tsx | 26 -- FrontEnd/src/views/common/Card.css | 20 - FrontEnd/src/views/common/Card.tsx | 38 -- FrontEnd/src/views/common/Icon.css | 3 - FrontEnd/src/views/common/Icon.tsx | 30 -- FrontEnd/src/views/common/ImageCropper.css | 38 -- FrontEnd/src/views/common/ImageCropper.tsx | 312 -------------- FrontEnd/src/views/common/LoadFailReload.tsx | 37 -- FrontEnd/src/views/common/LoadingPage.tsx | 13 - FrontEnd/src/views/common/Page.tsx | 15 - FrontEnd/src/views/common/SearchInput.css | 8 - FrontEnd/src/views/common/SearchInput.tsx | 50 --- FrontEnd/src/views/common/Skeleton.css | 14 - FrontEnd/src/views/common/Skeleton.tsx | 32 -- FrontEnd/src/views/common/Spinner.css | 13 - FrontEnd/src/views/common/Spinner.tsx | 36 -- FrontEnd/src/views/common/TimelineLogo.tsx | 27 -- FrontEnd/src/views/common/alert/AlertHost.tsx | 113 ----- FrontEnd/src/views/common/alert/alert.css | 33 -- FrontEnd/src/views/common/breakpoints.ts | 3 - FrontEnd/src/views/common/button/Button.css | 64 --- FrontEnd/src/views/common/button/Button.tsx | 46 -- FrontEnd/src/views/common/button/ButtonRow.css | 0 FrontEnd/src/views/common/button/ButtonRow.tsx | 62 --- FrontEnd/src/views/common/button/ButtonRowV2.tsx | 143 ------- FrontEnd/src/views/common/button/FlatButton.css | 27 -- FrontEnd/src/views/common/button/FlatButton.tsx | 36 -- FrontEnd/src/views/common/button/IconButton.css | 30 -- FrontEnd/src/views/common/button/IconButton.tsx | 30 -- FrontEnd/src/views/common/button/LoadingButton.css | 13 - FrontEnd/src/views/common/button/LoadingButton.tsx | 40 -- FrontEnd/src/views/common/button/index.tsx | 15 - FrontEnd/src/views/common/common.ts | 14 - FrontEnd/src/views/common/dialog/ConfirmDialog.css | 0 FrontEnd/src/views/common/dialog/ConfirmDialog.tsx | 59 --- FrontEnd/src/views/common/dialog/Dialog.css | 60 --- FrontEnd/src/views/common/dialog/Dialog.tsx | 63 --- .../src/views/common/dialog/DialogContainer.css | 20 - .../src/views/common/dialog/DialogContainer.tsx | 95 ----- .../src/views/common/dialog/FullPageDialog.css | 44 -- .../src/views/common/dialog/FullPageDialog.tsx | 53 --- .../src/views/common/dialog/OperationDialog.css | 8 - .../src/views/common/dialog/OperationDialog.tsx | 228 ---------- FrontEnd/src/views/common/dialog/index.ts | 64 --- FrontEnd/src/views/common/hooks.ts | 14 - FrontEnd/src/views/common/index.css | 100 ----- FrontEnd/src/views/common/input/InputGroup.css | 54 --- FrontEnd/src/views/common/input/InputGroup.tsx | 461 -------------------- FrontEnd/src/views/common/list/ListContainer.css | 4 - FrontEnd/src/views/common/list/ListContainer.tsx | 23 - .../src/views/common/list/ListItemContainer.css | 3 - .../src/views/common/list/ListItemContainer.tsx | 23 - FrontEnd/src/views/common/list/index.ts | 4 - FrontEnd/src/views/common/menu/Menu.css | 36 -- FrontEnd/src/views/common/menu/Menu.tsx | 62 --- FrontEnd/src/views/common/menu/PopupMenu.css | 7 - FrontEnd/src/views/common/menu/PopupMenu.tsx | 73 ---- FrontEnd/src/views/common/tab/TabPages.tsx | 71 ---- FrontEnd/src/views/common/tab/Tabs.css | 33 -- FrontEnd/src/views/common/tab/Tabs.tsx | 62 --- FrontEnd/src/views/common/theme-color.css | 173 -------- FrontEnd/src/views/common/theme.css | 146 ------- FrontEnd/src/views/common/user/UserAvatar.tsx | 22 - FrontEnd/tsconfig.json | 3 - 164 files changed, 3771 insertions(+), 3762 deletions(-) create mode 100644 FrontEnd/src/components/AppBar.css create mode 100644 FrontEnd/src/components/AppBar.tsx create mode 100644 FrontEnd/src/components/BlobImage.tsx create mode 100644 FrontEnd/src/components/Card.css create mode 100644 FrontEnd/src/components/Card.tsx create mode 100644 FrontEnd/src/components/Icon.css create mode 100644 FrontEnd/src/components/Icon.tsx create mode 100644 FrontEnd/src/components/ImageCropper.css create mode 100644 FrontEnd/src/components/ImageCropper.tsx create mode 100644 FrontEnd/src/components/LoadFailReload.tsx create mode 100644 FrontEnd/src/components/LoadingPage.tsx create mode 100644 FrontEnd/src/components/Page.tsx create mode 100644 FrontEnd/src/components/SearchInput.css create mode 100644 FrontEnd/src/components/SearchInput.tsx create mode 100644 FrontEnd/src/components/Skeleton.css create mode 100644 FrontEnd/src/components/Skeleton.tsx create mode 100644 FrontEnd/src/components/Spinner.css create mode 100644 FrontEnd/src/components/Spinner.tsx create mode 100644 FrontEnd/src/components/TimelineLogo.tsx create mode 100644 FrontEnd/src/components/alert/AlertHost.tsx create mode 100644 FrontEnd/src/components/alert/alert.css create mode 100644 FrontEnd/src/components/breakpoints.ts create mode 100644 FrontEnd/src/components/button/Button.css create mode 100644 FrontEnd/src/components/button/Button.tsx create mode 100644 FrontEnd/src/components/button/ButtonRow.css create mode 100644 FrontEnd/src/components/button/ButtonRow.tsx create mode 100644 FrontEnd/src/components/button/ButtonRowV2.tsx create mode 100644 FrontEnd/src/components/button/FlatButton.css create mode 100644 FrontEnd/src/components/button/FlatButton.tsx create mode 100644 FrontEnd/src/components/button/IconButton.css create mode 100644 FrontEnd/src/components/button/IconButton.tsx create mode 100644 FrontEnd/src/components/button/LoadingButton.css create mode 100644 FrontEnd/src/components/button/LoadingButton.tsx create mode 100644 FrontEnd/src/components/button/index.tsx create mode 100644 FrontEnd/src/components/common.ts create mode 100644 FrontEnd/src/components/dialog/ConfirmDialog.css create mode 100644 FrontEnd/src/components/dialog/ConfirmDialog.tsx create mode 100644 FrontEnd/src/components/dialog/Dialog.css create mode 100644 FrontEnd/src/components/dialog/Dialog.tsx create mode 100644 FrontEnd/src/components/dialog/DialogContainer.css create mode 100644 FrontEnd/src/components/dialog/DialogContainer.tsx create mode 100644 FrontEnd/src/components/dialog/FullPageDialog.css create mode 100644 FrontEnd/src/components/dialog/FullPageDialog.tsx create mode 100644 FrontEnd/src/components/dialog/OperationDialog.css create mode 100644 FrontEnd/src/components/dialog/OperationDialog.tsx create mode 100644 FrontEnd/src/components/dialog/index.ts create mode 100644 FrontEnd/src/components/hooks.ts create mode 100644 FrontEnd/src/components/index.css create mode 100644 FrontEnd/src/components/input/InputGroup.css create mode 100644 FrontEnd/src/components/input/InputGroup.tsx create mode 100644 FrontEnd/src/components/input/index.ts create mode 100644 FrontEnd/src/components/list/ListContainer.css create mode 100644 FrontEnd/src/components/list/ListContainer.tsx create mode 100644 FrontEnd/src/components/list/ListItemContainer.css create mode 100644 FrontEnd/src/components/list/ListItemContainer.tsx create mode 100644 FrontEnd/src/components/list/index.ts create mode 100644 FrontEnd/src/components/menu/Menu.css create mode 100644 FrontEnd/src/components/menu/Menu.tsx create mode 100644 FrontEnd/src/components/menu/PopupMenu.css create mode 100644 FrontEnd/src/components/menu/PopupMenu.tsx create mode 100644 FrontEnd/src/components/tab/TabPages.tsx create mode 100644 FrontEnd/src/components/tab/Tabs.css create mode 100644 FrontEnd/src/components/tab/Tabs.tsx create mode 100644 FrontEnd/src/components/theme-color.css create mode 100644 FrontEnd/src/components/theme.css create mode 100644 FrontEnd/src/components/user/UserAvatar.tsx delete mode 100644 FrontEnd/src/views/common/AppBar.css delete mode 100644 FrontEnd/src/views/common/AppBar.tsx delete mode 100644 FrontEnd/src/views/common/BlobImage.tsx delete mode 100644 FrontEnd/src/views/common/Card.css delete mode 100644 FrontEnd/src/views/common/Card.tsx delete mode 100644 FrontEnd/src/views/common/Icon.css delete mode 100644 FrontEnd/src/views/common/Icon.tsx delete mode 100644 FrontEnd/src/views/common/ImageCropper.css delete mode 100644 FrontEnd/src/views/common/ImageCropper.tsx delete mode 100644 FrontEnd/src/views/common/LoadFailReload.tsx delete mode 100644 FrontEnd/src/views/common/LoadingPage.tsx delete mode 100644 FrontEnd/src/views/common/Page.tsx delete mode 100644 FrontEnd/src/views/common/SearchInput.css delete mode 100644 FrontEnd/src/views/common/SearchInput.tsx delete mode 100644 FrontEnd/src/views/common/Skeleton.css delete mode 100644 FrontEnd/src/views/common/Skeleton.tsx delete mode 100644 FrontEnd/src/views/common/Spinner.css delete mode 100644 FrontEnd/src/views/common/Spinner.tsx delete mode 100644 FrontEnd/src/views/common/TimelineLogo.tsx delete mode 100644 FrontEnd/src/views/common/alert/AlertHost.tsx delete mode 100644 FrontEnd/src/views/common/alert/alert.css delete mode 100644 FrontEnd/src/views/common/breakpoints.ts delete mode 100644 FrontEnd/src/views/common/button/Button.css delete mode 100644 FrontEnd/src/views/common/button/Button.tsx delete mode 100644 FrontEnd/src/views/common/button/ButtonRow.css delete mode 100644 FrontEnd/src/views/common/button/ButtonRow.tsx delete mode 100644 FrontEnd/src/views/common/button/ButtonRowV2.tsx delete mode 100644 FrontEnd/src/views/common/button/FlatButton.css delete mode 100644 FrontEnd/src/views/common/button/FlatButton.tsx delete mode 100644 FrontEnd/src/views/common/button/IconButton.css delete mode 100644 FrontEnd/src/views/common/button/IconButton.tsx delete mode 100644 FrontEnd/src/views/common/button/LoadingButton.css delete mode 100644 FrontEnd/src/views/common/button/LoadingButton.tsx delete mode 100644 FrontEnd/src/views/common/button/index.tsx delete mode 100644 FrontEnd/src/views/common/common.ts delete mode 100644 FrontEnd/src/views/common/dialog/ConfirmDialog.css delete mode 100644 FrontEnd/src/views/common/dialog/ConfirmDialog.tsx delete mode 100644 FrontEnd/src/views/common/dialog/Dialog.css delete mode 100644 FrontEnd/src/views/common/dialog/Dialog.tsx delete mode 100644 FrontEnd/src/views/common/dialog/DialogContainer.css delete mode 100644 FrontEnd/src/views/common/dialog/DialogContainer.tsx delete mode 100644 FrontEnd/src/views/common/dialog/FullPageDialog.css delete mode 100644 FrontEnd/src/views/common/dialog/FullPageDialog.tsx delete mode 100644 FrontEnd/src/views/common/dialog/OperationDialog.css delete mode 100644 FrontEnd/src/views/common/dialog/OperationDialog.tsx delete mode 100644 FrontEnd/src/views/common/dialog/index.ts delete mode 100644 FrontEnd/src/views/common/hooks.ts delete mode 100644 FrontEnd/src/views/common/index.css delete mode 100644 FrontEnd/src/views/common/input/InputGroup.css delete mode 100644 FrontEnd/src/views/common/input/InputGroup.tsx delete mode 100644 FrontEnd/src/views/common/list/ListContainer.css delete mode 100644 FrontEnd/src/views/common/list/ListContainer.tsx delete mode 100644 FrontEnd/src/views/common/list/ListItemContainer.css delete mode 100644 FrontEnd/src/views/common/list/ListItemContainer.tsx delete mode 100644 FrontEnd/src/views/common/list/index.ts delete mode 100644 FrontEnd/src/views/common/menu/Menu.css delete mode 100644 FrontEnd/src/views/common/menu/Menu.tsx delete mode 100644 FrontEnd/src/views/common/menu/PopupMenu.css delete mode 100644 FrontEnd/src/views/common/menu/PopupMenu.tsx delete mode 100644 FrontEnd/src/views/common/tab/TabPages.tsx delete mode 100644 FrontEnd/src/views/common/tab/Tabs.css delete mode 100644 FrontEnd/src/views/common/tab/Tabs.tsx delete mode 100644 FrontEnd/src/views/common/theme-color.css delete mode 100644 FrontEnd/src/views/common/theme.css delete mode 100644 FrontEnd/src/views/common/user/UserAvatar.tsx (limited to 'FrontEnd/src/pages/timeline/TimelinePostList.tsx') diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx index 6029daac..b5b076ec 100644 --- a/FrontEnd/src/App.tsx +++ b/FrontEnd/src/App.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import AppBar from "./views/common/AppBar"; +import AppBar from "./components/AppBar"; import NotFoundPage from "./pages/404"; import HomePage from "./pages/home"; import AboutPage from "./pages/about"; @@ -10,7 +10,7 @@ import LoginPage from "./pages/login"; import RegisterPage from "./pages/register"; import TimelinePage from "./pages/timeline"; import LoadingPage from "./pages/loading"; -import AlertHost from "./views/common/alert/AlertHost"; +import AlertHost from "./components/alert/AlertHost"; export default function App() { return ( diff --git a/FrontEnd/src/components/AppBar.css b/FrontEnd/src/components/AppBar.css new file mode 100644 index 00000000..a0d975b5 --- /dev/null +++ b/FrontEnd/src/components/AppBar.css @@ -0,0 +1,87 @@ +.app-bar { + height: 56px; + position: fixed; + z-index: 1030; + top: 0; + left: 0; + right: 0; + background-color: var(--cru-primary-color); +} + +.app-bar { + display: flex; +} + +.app-bar .app-bar-brand { + display: flex; + align-items: center; +} + +.app-bar .app-bar-brand-icon { + height: 2em; +} + +.app-bar .app-bar-user-area { + display: flex; + margin-left: auto; +} + +.app-bar a { + background-color: var(--cru-primary-color); + color: var(--cru-push-button-text-color); + text-decoration: none; + display: flex; + align-items: center; + padding: 0 1em; + transition: all 0.5s; +} + +.app-bar a:hover { + background-color: var(--cru-clickable-primary-hover-color); +} + +.app-bar a:focus { + background-color: var(--cru-clickable-primary-focus-color); +} + +.app-bar a:active { + background-color: var(--cru-clickable-primary-active-color); +} + +/* the current page */ +.app-bar a.active { + background-color: var(--cru-clickable-primary-focus-color); +} + +.app-bar .app-bar-avatar img { + width: 45px; + height: 45px; + background-color: white; + border-radius: 50%; +} + +.app-bar.desktop .app-bar-link-area { + display: flex; +} + +.app-bar.mobile .app-bar-link-area { + position: absolute; + z-index: -1; + top: 56px; + left: 0; + right: 0; + transition: transform 0.5s; +} + +.app-bar.mobile a { + height: 56px; +} + +.app-bar.mobile.collapse .app-bar-link-area { + transform: translateY(-100%); +} + +.app-bar .toggler { + font-size: 2em; + margin-right: 0.5em; +} \ No newline at end of file diff --git a/FrontEnd/src/components/AppBar.tsx b/FrontEnd/src/components/AppBar.tsx new file mode 100644 index 00000000..da3a946f --- /dev/null +++ b/FrontEnd/src/components/AppBar.tsx @@ -0,0 +1,98 @@ +import { useState } from "react"; +import classnames from "classnames"; +import { Link, NavLink } from "react-router-dom"; + +import { I18nText, useC, useMobile } from "./common"; +import { useUser } from "~src/services/user"; + +import TimelineLogo from "./TimelineLogo"; +import { IconButton } from "./button"; +import UserAvatar from "./user/UserAvatar"; + +import "./AppBar.css"; + +function AppBarNavLink({ + link, + className, + label, + onClick, + children, +}: { + link: string; + className?: string; + label?: I18nText; + onClick?: () => void; + children?: React.ReactNode; +}) { + if (label != null && children != null) { + throw new Error("AppBarNavLink: label and children cannot be both set"); + } + + const c = useC(); + + return ( + classnames(className, isActive && "active")} + onClick={onClick} + > + {children != null ? children : c(label)} + + ); +} + +export default function AppBar() { + const isMobile = useMobile(); + + const [isCollapse, setIsCollapse] = useState(true); + const collapse = isMobile ? () => setIsCollapse(true) : undefined; + const toggleCollapse = () => setIsCollapse(!isCollapse); + + const user = useUser(); + const hasAdministrationPermission = user && user.hasAdministrationPermission; + + return ( + + ); +} diff --git a/FrontEnd/src/components/BlobImage.tsx b/FrontEnd/src/components/BlobImage.tsx new file mode 100644 index 00000000..259c2210 --- /dev/null +++ b/FrontEnd/src/components/BlobImage.tsx @@ -0,0 +1,26 @@ +import { ComponentPropsWithoutRef, useState, useEffect } from "react"; + +type BlobImageProps = Omit, "src"> & { + imgRef?: React.Ref; + src?: Blob | string | null; +}; + +export default function BlobImage(props: BlobImageProps) { + const { imgRef, src, ...otherProps } = props; + + const [url, setUrl] = useState(undefined); + + useEffect(() => { + if (src instanceof Blob) { + const url = URL.createObjectURL(src); + setUrl(url); + return () => { + URL.revokeObjectURL(url); + }; + } else { + setUrl(src); + } + }, [src]); + + return ; +} diff --git a/FrontEnd/src/components/Card.css b/FrontEnd/src/components/Card.css new file mode 100644 index 00000000..6d655eb9 --- /dev/null +++ b/FrontEnd/src/components/Card.css @@ -0,0 +1,20 @@ +.cru-card { + border-radius: var(--cru-card-border-radius); + transition: all 0.3s; +} + +.cru-card-background-none { + background-color: transparent; +} + +.cru-card-background-solid { + background-color: var(--cru-background-color); +} + +.cru-card-background-grayscale { + background-color: var(--cru-container-background-color); +} + +.cru-card-border-color { + border: 2px solid var(--cru-card-border-color); +} diff --git a/FrontEnd/src/components/Card.tsx b/FrontEnd/src/components/Card.tsx new file mode 100644 index 00000000..a8f0d3cc --- /dev/null +++ b/FrontEnd/src/components/Card.tsx @@ -0,0 +1,38 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import { ThemeColor } from "./common"; +import "./Card.css"; + +interface CardProps extends ComponentPropsWithoutRef<"div"> { + containerRef?: Ref; + color?: ThemeColor; + border?: "color" | "none"; + background?: "color" | "solid" | "grayscale" | "none"; +} + +export default function Card({ + color, + background, + border, + className, + children, + containerRef, + ...otherProps +}: CardProps) { + return ( +
+ {children} +
+ ); +} diff --git a/FrontEnd/src/components/Icon.css b/FrontEnd/src/components/Icon.css new file mode 100644 index 00000000..fe980d7b --- /dev/null +++ b/FrontEnd/src/components/Icon.css @@ -0,0 +1,3 @@ +.cru-icon { + font-size: 1.4rem; +} diff --git a/FrontEnd/src/components/Icon.tsx b/FrontEnd/src/components/Icon.tsx new file mode 100644 index 00000000..2ac3a7ca --- /dev/null +++ b/FrontEnd/src/components/Icon.tsx @@ -0,0 +1,30 @@ +import { ComponentPropsWithoutRef } from "react"; +import classNames from "classnames"; + +import { ThemeColor } from "./common"; + +import "./Icon.css"; + +interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { + icon: string; + color?: ThemeColor | "on-surface"; + size?: string | number; +} + +export default function Icon(props: IconButtonProps) { + const { icon, color, size, style, className, ...otherProps } = props; + + const colorName = color === "on-surface" ? "surface-on" : color; + + return ( + + ); +} diff --git a/FrontEnd/src/components/ImageCropper.css b/FrontEnd/src/components/ImageCropper.css new file mode 100644 index 00000000..2c4d0a8c --- /dev/null +++ b/FrontEnd/src/components/ImageCropper.css @@ -0,0 +1,38 @@ +.image-cropper-container { + position: relative; + box-sizing: border-box; + user-select: none; +} + +.image-cropper-container img { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.image-cropper-mask-container { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.image-cropper-mask { + position: absolute; + box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8); + touch-action: none; +} + +.image-cropper-handler { + position: absolute; + width: 26px; + height: 26px; + border: black solid 2px; + border-radius: 50%; + background: white; + touch-action: none; +} diff --git a/FrontEnd/src/components/ImageCropper.tsx b/FrontEnd/src/components/ImageCropper.tsx new file mode 100644 index 00000000..f23994e2 --- /dev/null +++ b/FrontEnd/src/components/ImageCropper.tsx @@ -0,0 +1,312 @@ +import * as React from "react"; +import classnames from "classnames"; + +import { UiLogicError } from "~src/common"; + +import "./ImageCropper.css"; +import BlobImage from "./BlobImage"; + +export interface Clip { + left: number; + top: number; + width: number; +} + +interface NormailizedClip extends Clip { + height: number; +} + +interface ImageInfo { + width: number; + height: number; + landscape: boolean; + ratio: number; + maxClipWidth: number; + maxClipHeight: number; +} + +interface ImageCropperSavedState { + clip: NormailizedClip; + x: number; + y: number; + pointerId: number; +} + +export interface ImageCropperProps { + clip: Clip | null; + image: string | Blob; + onChange: (clip: Clip) => void; + imageElementCallback?: (element: HTMLImageElement | null) => void; + className?: string; +} + +const ImageCropper = (props: ImageCropperProps): React.ReactElement => { + const { clip, image, onChange, imageElementCallback, className } = props; + + const [oldState, setOldState] = React.useState( + null, + ); + const [imageInfo, setImageInfo] = React.useState(null); + + const normalizeClip = (c: Clip | null | undefined): NormailizedClip => { + if (c == null) { + return { left: 0, top: 0, width: 0, height: 0 }; + } + + return { + left: c.left || 0, + top: c.top || 0, + width: c.width || 0, + height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0, + }; + }; + + const c = normalizeClip(clip); + + const imgElementRef = React.useRef(null); + + const onImageRef = React.useCallback( + (e: HTMLImageElement | null) => { + imgElementRef.current = e; + if (imageElementCallback != null && e == null) { + imageElementCallback(null); + } + }, + [imageElementCallback], + ); + + const onImageLoad = React.useCallback( + (e: React.SyntheticEvent) => { + const img = e.currentTarget; + const landscape = img.naturalWidth >= img.naturalHeight; + + const info = { + width: img.naturalWidth, + height: img.naturalHeight, + landscape, + ratio: img.naturalHeight / img.naturalWidth, + maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1, + maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight, + }; + setImageInfo(info); + onChange({ left: 0, top: 0, width: info.maxClipWidth }); + if (imageElementCallback != null) { + imageElementCallback(img); + } + }, + [onChange, imageElementCallback], + ); + + const onPointerDown = React.useCallback( + (e: React.PointerEvent) => { + if (oldState != null) return; + e.currentTarget.setPointerCapture(e.pointerId); + setOldState({ + x: e.clientX, + y: e.clientY, + clip: c, + pointerId: e.pointerId, + }); + }, + [oldState, c], + ); + + const onPointerUp = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null || oldState.pointerId !== e.pointerId) return; + e.currentTarget.releasePointerCapture(e.pointerId); + setOldState(null); + }, + [oldState], + ); + + const onPointerMove = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null) return; + + const oldClip = oldState.clip; + + const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; + + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError("Image element is null."); + + const moveRatio = { + x: movement.x / imgElement.width, + y: movement.y / imgElement.height, + }; + + const newRatio = { + x: oldClip.left + moveRatio.x, + y: oldClip.top + moveRatio.y, + }; + if (newRatio.x < 0) { + newRatio.x = 0; + } else if (newRatio.x > 1 - oldClip.width) { + newRatio.x = 1 - oldClip.width; + } + if (newRatio.y < 0) { + newRatio.y = 0; + } else if (newRatio.y > 1 - oldClip.height) { + newRatio.y = 1 - oldClip.height; + } + + onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width }); + }, + [oldState, onChange], + ); + + const onHandlerPointerMove = React.useCallback( + (e: React.PointerEvent) => { + if (oldState == null) return; + + const oldClip = oldState.clip; + + const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; + + const ratio = imageInfo == null ? 1 : imageInfo.ratio; + + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError("Image element is null."); + + const moveRatio = { + x: movement.x / imgElement.width, + y: movement.x / imgElement.width / ratio, + }; + + const newRatio = { + x: oldClip.width + moveRatio.x, + y: oldClip.height + moveRatio.y, + }; + + const maxRatio = { + x: Math.min(1 - oldClip.left, newRatio.x), + y: Math.min(1 - oldClip.top, newRatio.y), + }; + + const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio); + + let newWidth; + if (newRatio.x < 0) { + newWidth = 0; + } else if (newRatio.x > maxWidthRatio) { + newWidth = maxWidthRatio; + } else { + newWidth = newRatio.x; + } + + onChange({ left: oldClip.left, top: oldClip.top, width: newWidth }); + }, + [imageInfo, oldState, onChange], + ); + + const toPercentage = (n: number): string => `${n}%`; + + // fuck!!! I just can't find a better way to implement this in pure css + const containerStyle: React.CSSProperties = (() => { + if (imageInfo == null) { + return { width: "100%", paddingTop: "100%", height: 0 }; + } else { + if (imageInfo.ratio > 1) { + return { + width: toPercentage(100 / imageInfo.ratio), + paddingTop: "100%", + height: 0, + }; + } else { + return { + width: "100%", + paddingTop: toPercentage(100 * imageInfo.ratio), + height: 0, + }; + } + } + })(); + + return ( +
+ +
+
+
+
+
+ ); +}; + +export default ImageCropper; + +export function applyClipToImage( + image: HTMLImageElement, + clip: Clip, + mimeType: string, +): Promise { + return new Promise((resolve, reject) => { + const naturalSize = { + width: image.naturalWidth, + height: image.naturalHeight, + }; + const clipArea = { + x: naturalSize.width * clip.left, + y: naturalSize.height * clip.top, + length: naturalSize.width * clip.width, + }; + + const canvas = document.createElement("canvas"); + canvas.width = clipArea.length; + canvas.height = clipArea.length; + const context = canvas.getContext("2d"); + + if (context == null) throw new Error("Failed to create context."); + + context.drawImage( + image, + clipArea.x, + clipArea.y, + clipArea.length, + clipArea.length, + 0, + 0, + clipArea.length, + clipArea.length, + ); + + canvas.toBlob((blob) => { + if (blob == null) { + reject(new Error("canvas.toBlob returns null")); + } else { + resolve(blob); + } + }, mimeType); + }); +} diff --git a/FrontEnd/src/components/LoadFailReload.tsx b/FrontEnd/src/components/LoadFailReload.tsx new file mode 100644 index 00000000..81ba1f67 --- /dev/null +++ b/FrontEnd/src/components/LoadFailReload.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import { Trans } from "react-i18next"; + +export interface LoadFailReloadProps { + className?: string; + style?: React.CSSProperties; + onReload: () => void; +} + +const LoadFailReload: React.FC = ({ + onReload, + className, + style, +}) => { + return ( + + 0 + { + onReload(); + e.preventDefault(); + }} + > + 1 + + 2 + + ); +}; + +export default LoadFailReload; diff --git a/FrontEnd/src/components/LoadingPage.tsx b/FrontEnd/src/components/LoadingPage.tsx new file mode 100644 index 00000000..35ee1aa8 --- /dev/null +++ b/FrontEnd/src/components/LoadingPage.tsx @@ -0,0 +1,13 @@ +import * as React from "react"; + +import Spinner from "./Spinner"; + +const LoadingPage: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default LoadingPage; diff --git a/FrontEnd/src/components/Page.tsx b/FrontEnd/src/components/Page.tsx new file mode 100644 index 00000000..86fdb2f5 --- /dev/null +++ b/FrontEnd/src/components/Page.tsx @@ -0,0 +1,15 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +interface PageProps extends ComponentPropsWithoutRef<"div"> { + noTopPadding?: boolean; + pageRef?: Ref; +} + +export default function Page({ noTopPadding, pageRef, className, children }: PageProps) { + return ( +
+ {children} +
+ ); +} diff --git a/FrontEnd/src/components/SearchInput.css b/FrontEnd/src/components/SearchInput.css new file mode 100644 index 00000000..f0503016 --- /dev/null +++ b/FrontEnd/src/components/SearchInput.css @@ -0,0 +1,8 @@ +.cru-search-input { + display: flex; + flex-wrap: wrap; +} + +.cru-search-input-input { + width: 100%; +} diff --git a/FrontEnd/src/components/SearchInput.tsx b/FrontEnd/src/components/SearchInput.tsx new file mode 100644 index 00000000..e3216b86 --- /dev/null +++ b/FrontEnd/src/components/SearchInput.tsx @@ -0,0 +1,50 @@ +import classNames from "classnames"; + +import { useC, Text } from "./common"; +import LoadingButton from "./button/LoadingButton"; + +import "./SearchInput.css"; + +interface SearchInputProps { + value: string; + onChange: (value: string) => void; + onButtonClick: () => void; + loading?: boolean; + className?: string; + buttonText?: Text; +} + +export default function SearchInput({ + value, + onChange, + onButtonClick, + loading, + className, + buttonText, +}: SearchInputProps) { + const c = useC(); + + return ( +
+ { + const { value } = event.currentTarget; + onChange(value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + onButtonClick(); + event.preventDefault(); + } + }} + /> + + + {c(buttonText ?? "search")} + +
+ ); +} diff --git a/FrontEnd/src/components/Skeleton.css b/FrontEnd/src/components/Skeleton.css new file mode 100644 index 00000000..a571eead --- /dev/null +++ b/FrontEnd/src/components/Skeleton.css @@ -0,0 +1,14 @@ +.cru-skeleton { + padding: 0 1em; +} + +.cru-skeleton-line { + height: 1em; + background-color: hsl(0, 0%, 90%); + margin: 0.7em 0; + border-radius: 0.2em; +} + +.cru-skeleton-line.last { + width: 50%; +} diff --git a/FrontEnd/src/components/Skeleton.tsx b/FrontEnd/src/components/Skeleton.tsx new file mode 100644 index 00000000..3b149db9 --- /dev/null +++ b/FrontEnd/src/components/Skeleton.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import classnames from "classnames"; +import range from "lodash/range"; + +import "./Skeleton.css"; + +export interface SkeletonProps { + lineNumber?: number; + className?: string; + style?: React.CSSProperties; +} + +const Skeleton: React.FC = (props) => { + const { lineNumber: lineNumberProps, className, style } = props; + const lineNumber = lineNumberProps ?? 3; + + return ( +
+ {range(lineNumber).map((i) => ( +
+ ))} +
+ ); +}; + +export default Skeleton; diff --git a/FrontEnd/src/components/Spinner.css b/FrontEnd/src/components/Spinner.css new file mode 100644 index 00000000..a1de68d2 --- /dev/null +++ b/FrontEnd/src/components/Spinner.css @@ -0,0 +1,13 @@ +@keyframes cru-spinner-animation { + from { + transform: scale(0,0); + } +} + +.cru-spinner { + display: inline-block; + animation: cru-spinner-animation 0.5s infinite alternate; + background-color: currentColor; + border-radius: 50%; + transform-origin: center; +} diff --git a/FrontEnd/src/components/Spinner.tsx b/FrontEnd/src/components/Spinner.tsx new file mode 100644 index 00000000..ec0c2c35 --- /dev/null +++ b/FrontEnd/src/components/Spinner.tsx @@ -0,0 +1,36 @@ +import { CSSProperties } from "react"; +import classnames from "classnames"; + +import { ThemeColor } from "./common"; + +import "./Spinner.css"; + +export interface SpinnerProps { + size?: "sm" | "md" | "lg" | number | string; + color?: ThemeColor; + className?: string; + style?: CSSProperties; +} + +export default function Spinner(props: SpinnerProps) { + const { size, color, className, style } = props; + const calculatedSize = + size === "sm" + ? "18px" + : size === "md" + ? "30px" + : size === "lg" + ? "42px" + : typeof size === "number" + ? size + : size == null + ? "20px" + : size; + + return ( + + ); +} diff --git a/FrontEnd/src/components/TimelineLogo.tsx b/FrontEnd/src/components/TimelineLogo.tsx new file mode 100644 index 00000000..e06ed0f5 --- /dev/null +++ b/FrontEnd/src/components/TimelineLogo.tsx @@ -0,0 +1,27 @@ +import { SVGAttributes } from "react"; +import * as React from "react"; + +export interface TimelineLogoProps extends SVGAttributes { + color?: string; +} + +const TimelineLogo: React.FC = (props) => { + const { color, ...forwardProps } = props; + const coercedColor = color ?? "currentcolor"; + return ( + + + + + + ); +}; + +export default TimelineLogo; diff --git a/FrontEnd/src/components/alert/AlertHost.tsx b/FrontEnd/src/components/alert/AlertHost.tsx new file mode 100644 index 00000000..b234ac03 --- /dev/null +++ b/FrontEnd/src/components/alert/AlertHost.tsx @@ -0,0 +1,113 @@ +import * as React from "react"; +import without from "lodash/without"; +import { useTranslation } from "react-i18next"; +import classNames from "classnames"; + +import { alertService, AlertInfoEx, AlertInfo } from "~src/services/alert"; +import { convertI18nText } from "~src/common"; + +import IconButton from "../button/IconButton"; + +import "./alert.css"; + +interface AutoCloseAlertProps { + alert: AlertInfo; + close: () => void; +} + +export const AutoCloseAlert: React.FC = (props) => { + const { alert, close } = props; + const { dismissTime } = alert; + + const { t } = useTranslation(); + + const timerTag = React.useRef(null); + const closeHandler = React.useRef<(() => void) | null>(null); + + React.useEffect(() => { + closeHandler.current = close; + }, [close]); + + React.useEffect(() => { + const tag = + dismissTime === "never" + ? null + : typeof dismissTime === "number" + ? window.setTimeout(() => closeHandler.current?.(), dismissTime) + : window.setTimeout(() => closeHandler.current?.(), 5000); + timerTag.current = tag; + return () => { + if (tag != null) { + window.clearTimeout(tag); + } + }; + }, [dismissTime]); + + const cancelTimer = (): void => { + const { current: tag } = timerTag; + if (tag != null) { + window.clearTimeout(tag); + } + }; + + return ( +
+
+ {(() => { + const { message, customMessage } = alert; + if (customMessage != null) { + return customMessage; + } else { + return convertI18nText(message, t); + } + })()} +
+
+ +
+
+ ); +}; + +const AlertHost: React.FC = () => { + const [alerts, setAlerts] = React.useState([]); + + React.useEffect(() => { + const consume = (alert: AlertInfoEx): void => { + setAlerts((old) => [...old, alert]); + }; + + alertService.registerConsumer(consume); + return () => { + alertService.unregisterConsumer(consume); + }; + }, []); + + return ( +
+ {alerts.map((alert) => { + return ( + { + setAlerts((old) => without(old, alert)); + }} + /> + ); + })} +
+ ); +}; + +export default AlertHost; diff --git a/FrontEnd/src/components/alert/alert.css b/FrontEnd/src/components/alert/alert.css new file mode 100644 index 00000000..54c2b87f --- /dev/null +++ b/FrontEnd/src/components/alert/alert.css @@ -0,0 +1,33 @@ +.alert-container { + position: fixed; + z-index: 1040; +} + +.cru-alert { + border-radius: 5px; + border: var(--cru-key-color) 1px solid; + color: var(--cru-key-t-color); + background-color: var(--cru-key-b1-color); + + display: flex; + overflow: hidden; +} + +.cru-alert-content { + padding: 0.5em 2em; +} + +.cru-alert-close-button-container { + flex-shrink: 0; + margin-left: auto; + width: 2em; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--cru-key-t-color); +} + +.cru-alert-close-button { + color: var(--cru-key-color); +} diff --git a/FrontEnd/src/components/breakpoints.ts b/FrontEnd/src/components/breakpoints.ts new file mode 100644 index 00000000..fb281610 --- /dev/null +++ b/FrontEnd/src/components/breakpoints.ts @@ -0,0 +1,3 @@ +export const breakpoints = { + sm: 576, +} as const; diff --git a/FrontEnd/src/components/button/Button.css b/FrontEnd/src/components/button/Button.css new file mode 100644 index 00000000..1da70f0e --- /dev/null +++ b/FrontEnd/src/components/button/Button.css @@ -0,0 +1,64 @@ +.cru-button { + font-size: 1rem; + padding: 0.4em 0.8em; + transition: all 0.3s; + border-radius: 0.2em; + border: 1px solid; + cursor: pointer; +} + +.cru-button:not(.outline) { + color: var(--cru-push-button-text-color); + background-color: var(--cru-clickable-normal-color); + border-color: var(--cru-clickable-normal-color); +} + +.cru-button:not(.outline):hover { + background-color: var(--cru-clickable-hover-color); + border-color: var(--cru-clickable-hover-color); +} + +.cru-button:not(.outline):focus { + background-color: var(--cru-clickable-focus-color); + border-color: var(--cru-clickable-focus-color); +} + +.cru-button:not(.outline):active { + background-color: var(--cru-clickable-active-color); + border-color: var(--cru-clickable-active-color); +} + +.cru-button:not(.outline):disabled { + color: var(--cru-push-button-disabled-text-color); + background-color: var(--cru-push-button-disabled-color); + border-color: var(--cru-push-button-disabled-color); + cursor: auto; +} + + +.cru-button.outline { + color: var(--cru-clickable-normal-color); + border-color: var(--cru-clickable-normal-color); + background-color: transparent; +} + +.cru-button.outline:hover { + color: var(--cru-clickable-hover-color); + border-color: var(--cru-clickable-hover-color); +} + +.cru-button.outline:focus { + color: var(--cru-clickable-focus-color); + border-color: var(--cru-clickable-focus-color); +} + +.cru-button.outline:active { + color: var(--cru-clickable-active-color); + border-color: var(--cru-clickable-active-color); +} + +.cru-button.outline:disabled { + color: var(--cru-clickable-disabled-color); + border-color: var(--cru-clickable-disabled-color); + cursor: auto; +} diff --git a/FrontEnd/src/components/button/Button.tsx b/FrontEnd/src/components/button/Button.tsx new file mode 100644 index 00000000..6c38e130 --- /dev/null +++ b/FrontEnd/src/components/button/Button.tsx @@ -0,0 +1,46 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import { Text, useC, ThemeColor } from "../common"; + +import "./Button.css"; + +interface ButtonProps extends ComponentPropsWithoutRef<"button"> { + color?: ThemeColor; + text?: Text; + outline?: boolean; + buttonRef?: Ref | null; +} + +export default function Button(props: ButtonProps) { + const { + buttonRef, + color, + text, + outline, + className, + children, + ...otherProps + } = props; + + if (text != null && children != null) { + console.warn("You can't set both text and children props."); + } + + const c = useC(); + + return ( + + ); +} diff --git a/FrontEnd/src/components/button/ButtonRow.css b/FrontEnd/src/components/button/ButtonRow.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/components/button/ButtonRow.tsx b/FrontEnd/src/components/button/ButtonRow.tsx new file mode 100644 index 00000000..eea60cc4 --- /dev/null +++ b/FrontEnd/src/components/button/ButtonRow.tsx @@ -0,0 +1,62 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; + +import "./ButtonRow.css"; + +type ButtonRowButton = ( + | { + type: "normal"; + props: ComponentPropsWithoutRef; + } + | { + type: "flat"; + props: ComponentPropsWithoutRef; + } + | { + type: "icon"; + props: ComponentPropsWithoutRef; + } + | { type: "loading"; props: ComponentPropsWithoutRef } +) & { key: string | number }; + +interface ButtonRowProps { + className?: string; + containerRef?: Ref; + buttons: ButtonRowButton[]; + buttonsClassName?: string; +} + +export default function ButtonRow({ + className, + containerRef, + buttons, + buttonsClassName, +}: ButtonRowProps) { + return ( +
+ {buttons.map((button) => { + const { type, key, props } = button; + const newClassName = classNames(props.className, buttonsClassName); + switch (type) { + case "normal": + return
+ ); +} diff --git a/FrontEnd/src/components/button/ButtonRowV2.tsx b/FrontEnd/src/components/button/ButtonRowV2.tsx new file mode 100644 index 00000000..3467ad52 --- /dev/null +++ b/FrontEnd/src/components/button/ButtonRowV2.tsx @@ -0,0 +1,143 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; + +import "./ButtonRow.css"; +import { Text, ThemeColor } from "../common"; + +interface ButtonRowV2ButtonBase { + key: string | number; + action?: "primary" | "secondary"; + color?: ThemeColor; + disabled?: boolean; + onClick?: () => void; +} + +interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase { + type?: undefined | null; + text: Text; + outline?: boolean; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase { + type: "normal"; + text: Text; + outline?: boolean; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase { + type: "flat"; + text: Text; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase { + type: "icon"; + icon: string; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase { + type: "loading"; + text: Text; + loading?: boolean; + props?: ComponentPropsWithoutRef; +} + +type ButtonRowV2Button = + | ButtonRowV2ButtonWithNoType + | ButtonRowV2NormalButton + | ButtonRowV2FlatButton + | ButtonRowV2IconButton + | ButtonRowV2LoadingButton; + +interface ButtonRowV2Props { + className?: string; + containerRef?: Ref; + buttons: ButtonRowV2Button[]; + buttonsClassName?: string; +} + +export default function ButtonRowV2({ + className, + containerRef, + buttons, + buttonsClassName, +}: ButtonRowV2Props) { + return ( +
+ {buttons.map((button) => { + const { key, action, color, disabled, onClick } = button; + + const realAction = action ?? "primary"; + const realColor = + color ?? (realAction === "primary" ? "primary" : "secondary"); + + const commonProps = { key, color: realColor, disabled, onClick }; + const newClassName = classNames( + button.props?.className, + buttonsClassName, + ); + + switch (button.type) { + case null: + case undefined: + case "normal": { + const { text, outline, props } = button; + return ( +
+ ); +} diff --git a/FrontEnd/src/components/button/FlatButton.css b/FrontEnd/src/components/button/FlatButton.css new file mode 100644 index 00000000..2050946c --- /dev/null +++ b/FrontEnd/src/components/button/FlatButton.css @@ -0,0 +1,27 @@ +.cru-flat-button { + font-size: 1rem; + padding: 0.4em 0.8em; + transition: all 0.5s; + border-radius: 0.2em; + background-color: var(--cru-clickable-grayscale-normal-color); + border: 1px none; + color: var(--cru-clickable-normal-color); + cursor: pointer; +} + +.cru-flat-button:hover { + background-color: var(--cru-clickable-grayscale-hover-color); +} + +.cru-flat-button:focus { + background-color: var(--cru-clickable-grayscale-focus-color); +} + +.cru-flat-button:active { + background-color: var(--cru-clickable-grayscale-active-color); +} + +.cru-flat-button:disabled { + color: var(--cru-clickable-disabled-color); + cursor: auto; +} \ No newline at end of file diff --git a/FrontEnd/src/components/button/FlatButton.tsx b/FrontEnd/src/components/button/FlatButton.tsx new file mode 100644 index 00000000..9f074dd6 --- /dev/null +++ b/FrontEnd/src/components/button/FlatButton.tsx @@ -0,0 +1,36 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import { Text, useC, ThemeColor } from "../common"; + +import "./FlatButton.css"; + +interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> { + color?: ThemeColor; + text?: Text; + buttonRef?: Ref | null; +} + +export default function FlatButton(props: FlatButtonProps) { + const { color, text, className, children, buttonRef, ...otherProps } = props; + + if (text != null && children != null) { + console.warn("You can't set both text and children props."); + } + + const c = useC(); + + return ( + + ); +} diff --git a/FrontEnd/src/components/button/IconButton.css b/FrontEnd/src/components/button/IconButton.css new file mode 100644 index 00000000..a3747201 --- /dev/null +++ b/FrontEnd/src/components/button/IconButton.css @@ -0,0 +1,30 @@ +.cru-icon-button { + color: var(--cru-clickable-normal-color); + font-size: 1.4rem; + background: none; + border: none; + transition: all 0.5s; + cursor: pointer; + user-select: none; +} + +.cru-icon-button:hover { + color: var(--cru-clickable-hover-color); +} + +.cru-icon-button:focus { + color: var(--cru-clickable-focus-color); +} + +.cru-icon-button:active { + color: var(--cru-clickable-active-color); +} + +.cru-flat-button:disabled { + color: var(--cru-clickable-disabled-color); + cursor: auto; +} + +.cru-icon-button.large { + font-size: 1.6rem; +} diff --git a/FrontEnd/src/components/button/IconButton.tsx b/FrontEnd/src/components/button/IconButton.tsx new file mode 100644 index 00000000..95c58887 --- /dev/null +++ b/FrontEnd/src/components/button/IconButton.tsx @@ -0,0 +1,30 @@ +import { ComponentPropsWithoutRef } from "react"; +import classNames from "classnames"; + +import { ThemeColor } from "../common"; + +import "./IconButton.css"; + +interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { + icon: string; + color?: ThemeColor | "grayscale"; + large?: boolean; + disabled?: boolean; // TODO: Not implemented +} + +export default function IconButton(props: IconButtonProps) { + const { icon, color, className, large, ...otherProps } = props; + + return ( + + ); +} diff --git a/FrontEnd/src/components/button/index.tsx b/FrontEnd/src/components/button/index.tsx new file mode 100644 index 00000000..b5aa5470 --- /dev/null +++ b/FrontEnd/src/components/button/index.tsx @@ -0,0 +1,15 @@ +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; +import ButtonRow from "./ButtonRow"; +import ButtonRowV2 from "./ButtonRowV2"; + +export { + Button, + FlatButton, + IconButton, + LoadingButton, + ButtonRow, + ButtonRowV2, +}; diff --git a/FrontEnd/src/components/common.ts b/FrontEnd/src/components/common.ts new file mode 100644 index 00000000..e6f7319f --- /dev/null +++ b/FrontEnd/src/components/common.ts @@ -0,0 +1,14 @@ +export type { Text, I18nText } from "~src/common"; +export { UiLogicError, c, convertI18nText, useC } from "~src/common"; + +export const themeColors = [ + "primary", + "secondary", + "danger", + "create", +] as const; + +export type ThemeColor = (typeof themeColors)[number]; + +export { breakpoints } from "./breakpoints"; +export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.css b/FrontEnd/src/components/dialog/ConfirmDialog.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.tsx b/FrontEnd/src/components/dialog/ConfirmDialog.tsx new file mode 100644 index 00000000..26939c9b --- /dev/null +++ b/FrontEnd/src/components/dialog/ConfirmDialog.tsx @@ -0,0 +1,59 @@ +import { useC, Text, ThemeColor } from "../common"; + +import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; + +export default function ConfirmDialog({ + open, + onClose, + onConfirm, + title, + body, + color, + bodyColor, +}: { + open: boolean; + onClose: () => void; + onConfirm: () => void; + title: Text; + body: Text; + color?: ThemeColor; + bodyColor?: ThemeColor; +}) { + const c = useC(); + + return ( + + { + onConfirm(); + onClose(); + }, + }, + }, + ]} + > +
{c(body)}
+
+
+ ); +} diff --git a/FrontEnd/src/components/dialog/Dialog.css b/FrontEnd/src/components/dialog/Dialog.css new file mode 100644 index 00000000..e4c61440 --- /dev/null +++ b/FrontEnd/src/components/dialog/Dialog.css @@ -0,0 +1,60 @@ +.cru-dialog-overlay { + position: fixed; + z-index: 1040; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + overflow: auto; +} + +.cru-dialog-background { + position: absolute; + z-index: -1; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: var(--cru-surface-dim-color); + opacity: 0.8; +} + +.cru-dialog-container { + max-width: 100%; + min-width: 30vw; + + margin: 2em auto; + + border: var(--cru-key-container-color) 1px solid; + border-radius: 5px; + padding: 1.5em; + background-color: var(--cru-surface-color); +} + +@media (min-width: 576px) { + .cru-dialog-container { + max-width: 800px; + } +} + +.cru-dialog-enter .cru-dialog-container { + transform: scale(0, 0); + opacity: 0; + transform-origin: center; +} + +.cru-dialog-enter-active .cru-dialog-container { + transform: scale(1, 1); + opacity: 1; + transition: transform 0.3s, opacity 0.3s; + transform-origin: center; +} + +.cru-dialog-exit-active .cru-dialog-container { + transition: transform 0.3s, opacity 0.3s; + transform: scale(0, 0); + opacity: 0; + transform-origin: center; +} \ No newline at end of file diff --git a/FrontEnd/src/components/dialog/Dialog.tsx b/FrontEnd/src/components/dialog/Dialog.tsx new file mode 100644 index 00000000..2ff7bea8 --- /dev/null +++ b/FrontEnd/src/components/dialog/Dialog.tsx @@ -0,0 +1,63 @@ +import { ReactNode, useRef } from "react"; +import ReactDOM from "react-dom"; +import { CSSTransition } from "react-transition-group"; +import classNames from "classnames"; + +import { ThemeColor } from "../common"; + +import "./Dialog.css"; + +const optionalPortalElement = document.getElementById("portal"); +if (optionalPortalElement == null) { + throw new Error("Portal element not found"); +} +const portalElement = optionalPortalElement; + +interface DialogProps { + open: boolean; + onClose: () => void; + color?: ThemeColor; + children?: ReactNode; + disableCloseOnClickOnOverlay?: boolean; +} + +export default function Dialog({ + open, + onClose, + color, + children, + disableCloseOnClickOnOverlay, +}: DialogProps) { + color = color ?? "primary"; + + const nodeRef = useRef(null); + + return ReactDOM.createPortal( + +
+
{ + onClose(); + } + } + /> +
{children}
+
+ , + portalElement, + ); +} diff --git a/FrontEnd/src/components/dialog/DialogContainer.css b/FrontEnd/src/components/dialog/DialogContainer.css new file mode 100644 index 00000000..fbb18e0d --- /dev/null +++ b/FrontEnd/src/components/dialog/DialogContainer.css @@ -0,0 +1,20 @@ +.cru-dialog-container-title { + font-size: 1.2em; + font-weight: bold; + color: var(--cru-key-color); + margin-bottom: 0.5em; +} + + +.cru-dialog-container-hr { + margin: 1em 0; +} + +.cru-dialog-container-button-row { + display: flex; + justify-content: flex-end; +} + +.cru-dialog-container-button { + margin-left: 1em; +} \ No newline at end of file diff --git a/FrontEnd/src/components/dialog/DialogContainer.tsx b/FrontEnd/src/components/dialog/DialogContainer.tsx new file mode 100644 index 00000000..afee2669 --- /dev/null +++ b/FrontEnd/src/components/dialog/DialogContainer.tsx @@ -0,0 +1,95 @@ +import { ComponentProps, Ref, ReactNode } from "react"; +import classNames from "classnames"; + +import { ThemeColor, Text, useC } from "../common"; +import { ButtonRow, ButtonRowV2 } from "../button"; + +import "./DialogContainer.css"; + +interface DialogContainerBaseProps { + className?: string; + title: Text; + titleColor?: ThemeColor; + titleClassName?: string; + titleRef?: Ref; + bodyContainerClassName?: string; + bodyContainerRef?: Ref; + buttonsClassName?: string; + buttonsContainerRef?: ComponentProps["containerRef"]; + children: ReactNode; +} + +interface DialogContainerWithButtonsProps extends DialogContainerBaseProps { + buttons: ComponentProps["buttons"]; +} + +interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps { + buttonsV2: ComponentProps["buttons"]; +} + +type DialogContainerProps = + | DialogContainerWithButtonsProps + | DialogContainerWithButtonsV2Props; + +export default function DialogContainer(props: DialogContainerProps) { + const { + className, + title, + titleColor, + titleClassName, + titleRef, + bodyContainerClassName, + bodyContainerRef, + buttonsClassName, + buttonsContainerRef, + children, + } = props; + + const c = useC(); + + return ( +
+
+ {c(title)} +
+
+
+ {children} +
+
+ {"buttons" in props ? ( + + ) : ( + + )} +
+ ); +} diff --git a/FrontEnd/src/components/dialog/FullPageDialog.css b/FrontEnd/src/components/dialog/FullPageDialog.css new file mode 100644 index 00000000..2f1fc636 --- /dev/null +++ b/FrontEnd/src/components/dialog/FullPageDialog.css @@ -0,0 +1,44 @@ +.cru-full-page { + position: fixed; + z-index: 1030; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: white; + padding-top: 56px; +} + +.cru-full-page-top-bar { + height: 56px; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; + background-color: var(--cru-primary-color); + display: flex; + align-items: center; +} + +.cru-full-page-content-container { + overflow: scroll; +} + +.cru-full-page-back-button { + color: var(--cru-primary-t-color); +} + +.cru-full-page-enter { + transform: translate(100%, 0); +} + +.cru-full-page-enter-active { + transform: none; + transition: transform 0.3s; +} + +.cru-full-page-exit-active { + transition: transform 0.3s; + transform: translate(100%, 0); +} diff --git a/FrontEnd/src/components/dialog/FullPageDialog.tsx b/FrontEnd/src/components/dialog/FullPageDialog.tsx new file mode 100644 index 00000000..6368fc0a --- /dev/null +++ b/FrontEnd/src/components/dialog/FullPageDialog.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { createPortal } from "react-dom"; +import classnames from "classnames"; +import { CSSTransition } from "react-transition-group"; + +import "./FullPageDialog.css"; +import IconButton from "../button/IconButton"; + +export interface FullPageDialogProps { + show: boolean; + onBack: () => void; + contentContainerClassName?: string; + children: React.ReactNode; +} + +const FullPageDialog: React.FC = ({ + show, + onBack, + children, + contentContainerClassName, +}) => { + return createPortal( + +
+
+ +
+
+ {children} +
+
+
, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")! + ); +}; + +export default FullPageDialog; diff --git a/FrontEnd/src/components/dialog/OperationDialog.css b/FrontEnd/src/components/dialog/OperationDialog.css new file mode 100644 index 00000000..f4b7237e --- /dev/null +++ b/FrontEnd/src/components/dialog/OperationDialog.css @@ -0,0 +1,8 @@ +.cru-operation-dialog-prompt { + color: var(--cru-surface-on-color); +} + +.cru-operation-dialog-input-group { + display: block; + margin: 0.5em 0; +} diff --git a/FrontEnd/src/components/dialog/OperationDialog.tsx b/FrontEnd/src/components/dialog/OperationDialog.tsx new file mode 100644 index 00000000..e5db7f4f --- /dev/null +++ b/FrontEnd/src/components/dialog/OperationDialog.tsx @@ -0,0 +1,230 @@ +import { useState, ReactNode, ComponentProps } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import { + useInputs, + InputGroup, + Initializer as InputInitializer, + InputValueDict, + InputErrorDict, + InputConfirmValueDict, +} from "../input"; +import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; +import { ButtonRow } from "../button"; + +import "./OperationDialog.css"; + +export type { InputInitializer, InputValueDict, InputErrorDict }; + +interface OperationDialogPromptProps { + message?: Text; + customMessage?: Text; + customMessageNode?: ReactNode; + className?: string; +} + +function OperationDialogPrompt(props: OperationDialogPromptProps) { + const { message, customMessage, customMessageNode, className } = props; + + const c = useC(); + + return ( +
+ {message &&

{c(message)}

} + {customMessageNode ?? (customMessage != null ? c(customMessage) : null)} +
+ ); +} + +export interface OperationDialogProps { + open: boolean; + onClose: () => void; + + color?: ThemeColor; + inputColor?: ThemeColor; + title: Text; + inputPrompt?: Text; + inputPromptNode?: ReactNode; + successPrompt?: (data: TData) => Text; + successPromptNode?: (data: TData) => ReactNode; + failurePrompt?: (error: unknown) => Text; + failurePromptNode?: (error: unknown) => ReactNode; + + inputs: InputInitializer; + + onProcess: (inputs: InputConfirmValueDict) => Promise; + onSuccessAndClose?: (data: TData) => void; +} + +function OperationDialog(props: OperationDialogProps) { + const { + open, + onClose, + color, + inputColor, + title, + inputPrompt, + inputPromptNode, + successPrompt, + successPromptNode, + failurePrompt, + failurePromptNode, + inputs, + onProcess, + onSuccessAndClose, + } = props; + + if (process.env.NODE_ENV === "development") { + if (inputPrompt && inputPromptNode) { + console.log("InputPrompt and inputPromptNode are both set."); + } + if (successPrompt && successPromptNode) { + console.log("SuccessPrompt and successPromptNode are both set."); + } + if (failurePrompt && failurePromptNode) { + console.log("FailurePrompt and failurePromptNode are both set."); + } + } + + type Step = + | { type: "input" } + | { type: "process" } + | { + type: "success"; + data: TData; + } + | { + type: "failure"; + data: unknown; + }; + + const [step, setStep] = useState({ type: "input" }); + + const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } = + useInputs({ + init: inputs, + }); + + function close() { + if (step.type !== "process") { + onClose(); + if (step.type === "success" && onSuccessAndClose) { + onSuccessAndClose?.(step.data); + } + } else { + console.log("Attempt to close modal dialog when processing."); + } + } + + function onConfirm() { + const result = confirm(); + if (result.type === "ok") { + setStep({ type: "process" }); + setAllDisabled(true); + onProcess(result.values).then( + (d) => { + setStep({ + type: "success", + data: d, + }); + }, + (e: unknown) => { + setStep({ + type: "failure", + data: e, + }); + }, + ); + } + } + + let body: ReactNode; + let buttons: ComponentProps["buttons"]; + + if (step.type === "input" || step.type === "process") { + const isProcessing = step.type === "process"; + + body = ( +
+ + +
+ ); + buttons = [ + { + key: "cancel", + type: "normal", + props: { + text: "operationDialog.cancel", + color: "secondary", + outline: true, + onClick: close, + disabled: isProcessing, + }, + }, + { + key: "confirm", + type: "loading", + props: { + text: "operationDialog.confirm", + color, + loading: isProcessing, + disabled: hasErrorAndDirty, + onClick: onConfirm, + }, + }, + ]; + } else { + const result = step; + + const promptProps: OperationDialogPromptProps = + result.type === "success" + ? { + message: "operationDialog.success", + customMessage: successPrompt?.(result.data), + customMessageNode: successPromptNode?.(result.data), + } + : { + message: "operationDialog.error", + customMessage: failurePrompt?.(result.data), + customMessageNode: failurePromptNode?.(result.data), + }; + body = ( +
+ +
+ ); + + buttons = [ + { + key: "ok", + type: "normal", + props: { + text: "operationDialog.ok", + color: "primary", + onClick: close, + }, + }, + ]; + } + + return ( + + + {body} + + + ); +} + +export default OperationDialog; diff --git a/FrontEnd/src/components/dialog/index.ts b/FrontEnd/src/components/dialog/index.ts new file mode 100644 index 00000000..59f15791 --- /dev/null +++ b/FrontEnd/src/components/dialog/index.ts @@ -0,0 +1,64 @@ +import { useState } from "react"; + +export { default as Dialog } from "./Dialog"; +export { default as FullPageDialog } from "./FullPageDialog"; +export { default as OperationDialog } from "./OperationDialog"; +export { default as ConfirmDialog } from "./ConfirmDialog"; + +type DialogMap = { + [K in D]: V; +}; + +type DialogKeyMap = DialogMap; + +type DialogPropsMap = DialogMap< + D, + { key: number | string; open: boolean; onClose: () => void } +>; + +export function useDialog( + dialogs: D[], + options?: { + initDialog?: D | null; + onClose?: { + [K in D]?: () => void; + }; + }, +): { + dialog: D | null; + switchDialog: (newDialog: D | null) => void; + dialogPropsMap: DialogPropsMap; + createDialogSwitch: (newDialog: D | null) => () => void; +} { + const [dialog, setDialog] = useState(options?.initDialog ?? null); + + const [dialogKeys, setDialogKeys] = useState>( + () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap, + ); + + const switchDialog = (newDialog: D | null) => { + if (dialog !== null) { + setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 }); + } + setDialog(newDialog); + }; + + return { + dialog, + switchDialog, + dialogPropsMap: Object.fromEntries( + dialogs.map((d) => [ + d, + { + key: `${d}-${dialogKeys[d]}`, + open: dialog === d, + onClose: () => { + switchDialog(null); + options?.onClose?.[d]?.(); + }, + }, + ]), + ) as DialogPropsMap, + createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog), + }; +} diff --git a/FrontEnd/src/components/hooks.ts b/FrontEnd/src/components/hooks.ts new file mode 100644 index 00000000..523a4538 --- /dev/null +++ b/FrontEnd/src/components/hooks.ts @@ -0,0 +1,14 @@ +// TODO: Migrate hooks + +export { + useIsSmallScreen, + useClickOutside, + useScrollToBottom, +} from "~src/utilities/hooks"; + +import { useMediaQuery } from "react-responsive"; +import { breakpoints } from "./breakpoints"; + +export function useMobile(): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }); +} diff --git a/FrontEnd/src/components/index.css b/FrontEnd/src/components/index.css new file mode 100644 index 00000000..a8f5e9a5 --- /dev/null +++ b/FrontEnd/src/components/index.css @@ -0,0 +1,100 @@ +@import "./theme.css"; + +* { + box-sizing: border-box; + margin-inline: 0; + margin-block: 0; +} + +body { + font-family: var(--cru-default-font-family); + background: var(--cru-body-background-color); + color: var(--cru-text-primary-color); + line-height: 1.2; +} + +.cru-page { + padding: var(--cru-page-padding); +} + +.cru-page-no-top-padding { + padding-top: 0; +} + +.cru-text-center { + text-align: center; +} + +.cru-text-end { + text-align: end; +} + +.cru-float-left { + float: left; +} + +.cru-float-right { + float: right; +} + +.cru-align-text-bottom { + vertical-align: text-bottom; +} + +.cru-align-middle { + vertical-align: middle; +} + +.cru-clearfix::after { + clear: both; +} + +.cru-fill-parent { + width: 100%; + height: 100%; +} + +.cru-avatar { + width: 60px; + height: 60px; +} + +.cru-avatar.large { + width: 100px; + height: 100px; +} + +.cru-avatar.small { + width: 40px; + height: 40px; +} + +.cru-round { + border-radius: 50%; +} + +.cru-tab-pages-action-area { + display: flex; + align-items: center; +} + +.alert-container { + position: fixed; + z-index: 1070; +} + +@media (min-width: 576px) { + .alert-container { + bottom: 0; + right: 0; + } +} + +@media (max-width: 575.98px) { + .alert-container { + bottom: 0; + right: 0; + left: 0; + text-align: center; + } +} \ No newline at end of file diff --git a/FrontEnd/src/components/input/InputGroup.css b/FrontEnd/src/components/input/InputGroup.css new file mode 100644 index 00000000..7e905b1e --- /dev/null +++ b/FrontEnd/src/components/input/InputGroup.css @@ -0,0 +1,54 @@ +.cru-input-group { + display: block; +} + +.cru-input-container { + margin: 0.4em 0; +} + +.cru-input-label { + display: block; + color: var(--cru-clickable-normal-color); + font-size: 0.9em; + margin-bottom: 0.3em; +} + +.cru-input-label-inline { + margin-inline-start: 0.5em; +} + +.cru-input-type-text input { + appearance: none; + display: block; + border: 1px solid; + /* color: var(--cru-surface-on-color); */ + /* background-color: var(--cru-surface-color); */ + margin: 0; + font-size: 1em; + padding: 0.2em; +} + +.cru-input-type-text input:hover { + border-color: var(--cru-clickable-hover-color); +} + +.cru-input-type-text input:focus { + border-color: var(--cru-clickable-focus-color); +} + +.cru-input-type-text input:disabled { + border-color: var(--cru-clickable-disabled-color); +} + +.cru-input-error { + display: block; + font-size: 0.8em; + color: var(--cru-danger-color); + margin-top: 0.4em; +} + +.cru-input-helper { + display: block; + font-size: 0.8em; + color: var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/components/input/InputGroup.tsx b/FrontEnd/src/components/input/InputGroup.tsx new file mode 100644 index 00000000..4f487344 --- /dev/null +++ b/FrontEnd/src/components/input/InputGroup.tsx @@ -0,0 +1,463 @@ +/** + * Some notes for InputGroup: + * This is one of the most complicated components in this project. + * Probably because the feature is complex and involved user inputs. + * + * I hope it contains following features: + * - Input features + * - Supports a wide range of input types. + * - Validator to validate user inputs. + * - Can set initial values. + * - Dirty, aka, has user touched this input. + * - Developer friendly + * - Easy to use APIs. + * - Type check as much as possible. + * - UI + * - Configurable appearance. + * - Can display helper and error messages. + * - Easy to extend, like new input types. + * + * So here is some design decisions: + * Inputs are identified by its _key_. + * `InputGroup` component takes care of only UI and no logic. + * `useInputs` hook takes care of logic and generate props for `InputGroup`. + */ + +import { useState, Ref, useId } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import "./InputGroup.css"; + +export interface InputBase { + key: string; + label: Text; + helper?: Text; + disabled?: boolean; + error?: Text; +} + +export interface TextInput extends InputBase { + type: "text"; + value: string; + password?: boolean; +} + +export interface BoolInput extends InputBase { + type: "bool"; + value: boolean; +} + +export interface SelectInputOption { + value: string; + label: Text; + icon?: string; +} + +export interface SelectInput extends InputBase { + type: "select"; + value: string; + options: SelectInputOption[]; +} + +export type Input = TextInput | BoolInput | SelectInput; + +export type InputValue = Input["value"]; + +export type InputValueDict = Record; +export type InputErrorDict = Record; +export type InputDisabledDict = Record; +export type InputDirtyDict = Record; +// use never so you don't have to cast everywhere +export type InputConfirmValueDict = Record; + +export type GeneralInputErrorDict = + | { + [key: string]: Text | null | undefined; + } + | null + | undefined; + +type MakeInputInfo = Omit; + +export type InputInfo = { + [I in Input as I["type"]]: MakeInputInfo; +}[Input["type"]]; + +export type Validator = ( + values: InputValueDict, + inputs: InputInfo[], +) => GeneralInputErrorDict; + +export type InputScheme = { + inputs: InputInfo[]; + validator?: Validator; +}; + +export type InputData = { + values: InputValueDict; + errors: InputErrorDict; + disabled: InputDisabledDict; + dirties: InputDirtyDict; +}; + +export type State = { + scheme: InputScheme; + data: InputData; +}; + +export type DataInitialization = { + values?: InputValueDict; + errors?: GeneralInputErrorDict; + disabled?: InputDisabledDict; + dirties?: InputDirtyDict; +}; + +export type Initialization = { + scheme: InputScheme; + dataInit?: DataInitialization; +}; + +export type GeneralInitialization = Initialization | InputScheme | InputInfo[]; + +export type Initializer = GeneralInitialization | (() => GeneralInitialization); + +export interface InputGroupProps { + color?: ThemeColor; + containerClassName?: string; + containerRef?: Ref; + + inputs: Input[]; + onChange: (index: number, value: Input["value"]) => void; +} + +function cleanObject(o: Record): Record> { + const result = { ...o }; + for (const key of Object.keys(result)) { + if (result[key] == null) { + delete result[key]; + } + } + return result as never; +} + +export type ConfirmResult = + | { + type: "ok"; + values: InputConfirmValueDict; + } + | { + type: "error"; + errors: InputErrorDict; + }; + +function validate( + validator: Validator | null | undefined, + values: InputValueDict, + inputs: InputInfo[], +): InputErrorDict { + return cleanObject(validator?.(values, inputs) ?? {}); +} + +export function useInputs(options: { init: Initializer }): { + inputGroupProps: InputGroupProps; + hasError: boolean; + hasErrorAndDirty: boolean; + confirm: () => ConfirmResult; + setAllDisabled: (disabled: boolean) => void; +} { + function initializeValue( + input: InputInfo, + value?: InputValue | null, + ): InputValue { + if (input.type === "text") { + return value ?? ""; + } else if (input.type === "bool") { + return value ?? false; + } else if (input.type === "select") { + return value ?? input.options[0].value; + } + throw new Error("Unknown input type"); + } + + function initialize(generalInitialization: GeneralInitialization): State { + const initialization: Initialization = Array.isArray(generalInitialization) + ? { scheme: { inputs: generalInitialization } } + : "scheme" in generalInitialization + ? generalInitialization + : { scheme: generalInitialization }; + + const { scheme, dataInit } = initialization; + const { inputs, validator } = scheme; + const keys = inputs.map((input) => input.key); + + if (process.env.NODE_ENV === "development") { + const checkKeys = (dict: Record | undefined) => { + if (dict != null) { + for (const key of Object.keys(dict)) { + if (!keys.includes(key)) { + console.warn(""); + } + } + } + }; + + checkKeys(dataInit?.values); + checkKeys(dataInit?.errors ?? {}); + checkKeys(dataInit?.disabled); + checkKeys(dataInit?.dirties); + } + + function clean( + dict: Record | null | undefined, + ): Record> { + return dict != null ? cleanObject(dict) : {}; + } + + const values: InputValueDict = {}; + const disabled: InputDisabledDict = clean(dataInit?.disabled); + const dirties: InputDirtyDict = clean(dataInit?.dirties); + const isErrorSet = dataInit?.errors != null; + let errors: InputErrorDict = clean(dataInit?.errors); + + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + const { key } = input; + + values[key] = initializeValue(input, dataInit?.values?.[key]); + } + + if (isErrorSet) { + if (process.env.NODE_ENV === "development") { + console.log( + "You explicitly set errors (not undefined) in initializer, so validator won't run.", + ); + } + } else { + errors = validate(validator, values, inputs); + } + + return { + scheme, + data: { + values, + errors, + disabled, + dirties, + }, + }; + } + + const { init } = options; + const initializer = typeof init === "function" ? init : () => init; + + const [state, setState] = useState(() => initialize(initializer())); + + const { scheme, data } = state; + const { validator } = scheme; + + function createAllBooleanDict(value: boolean): Record { + const result: InputDirtyDict = {}; + for (const key of scheme.inputs.map((input) => input.key)) { + result[key] = value; + } + return result; + } + + const createAllDirties = () => createAllBooleanDict(true); + + const componentInputs: Input[] = []; + + for (let i = 0; i < scheme.inputs.length; i++) { + const input = scheme.inputs[i]; + const value = data.values[input.key]; + const error = data.errors[input.key]; + const disabled = data.disabled[input.key] ?? false; + const dirty = data.dirties[input.key] ?? false; + const componentInput: Input = { + ...input, + value: value as never, + disabled, + error: dirty ? error : undefined, + }; + componentInputs.push(componentInput); + } + + const hasError = Object.keys(data.errors).length > 0; + const hasDirty = Object.keys(data.dirties).some((key) => data.dirties[key]); + + return { + inputGroupProps: { + inputs: componentInputs, + onChange: (index, value) => { + const input = scheme.inputs[index]; + const { key } = input; + const newValues = { ...data.values, [key]: value }; + const newDirties = { ...data.dirties, [key]: true }; + const newErrors = validate(validator, newValues, scheme.inputs); + setState({ + scheme, + data: { + ...data, + values: newValues, + errors: newErrors, + dirties: newDirties, + }, + }); + }, + }, + hasError, + hasErrorAndDirty: hasError && hasDirty, + confirm() { + const newDirties = createAllDirties(); + const newErrors = validate(validator, data.values, scheme.inputs); + + setState({ + scheme, + data: { + ...data, + dirties: newDirties, + errors: newErrors, + }, + }); + + if (Object.keys(newErrors).length !== 0) { + return { + type: "error", + errors: newErrors, + }; + } else { + return { + type: "ok", + values: data.values as InputConfirmValueDict, + }; + } + }, + setAllDisabled(disabled: boolean) { + setState({ + scheme, + data: { + ...data, + disabled: createAllBooleanDict(disabled), + }, + }); + }, + }; +} + +export function InputGroup({ + color, + inputs, + onChange, + containerRef, + containerClassName, +}: InputGroupProps) { + const c = useC(); + + const id = useId(); + + return ( +
+ {inputs.map((item, index) => { + const { key, type, value, label, error, helper, disabled } = item; + + const getContainerClassName = ( + ...additionalClassNames: classNames.ArgumentArray + ) => + classNames( + `cru-input-container cru-input-type-${type}`, + error && "error", + ...additionalClassNames, + ); + + const changeValue = (value: InputValue) => { + onChange(index, value); + }; + + const inputId = `${id}-${key}`; + + if (type === "text") { + const { password } = item; + return ( +
+ {label && ( + + )} + { + const v = event.target.value; + changeValue(v); + }} + disabled={disabled} + /> + {error &&
{c(error)}
} + {helper &&
{c(helper)}
} +
+ ); + } else if (type === "bool") { + return ( +
+ { + const v = event.currentTarget.checked; + changeValue(v); + }} + disabled={disabled} + /> + + {error &&
{c(error)}
} + {helper &&
{c(helper)}
} +
+ ); + } else if (type === "select") { + return ( +
+ + +
+ ); + } + })} +
+ ); +} diff --git a/FrontEnd/src/components/input/index.ts b/FrontEnd/src/components/input/index.ts new file mode 100644 index 00000000..ca183089 --- /dev/null +++ b/FrontEnd/src/components/input/index.ts @@ -0,0 +1,11 @@ +export { useInputs, InputGroup } from "./InputGroup"; + +export type { + InputValueDict, + InputErrorDict, + InputDirtyDict, + InputDisabledDict, + InputConfirmValueDict, + Validator, + Initializer, +} from "./InputGroup"; diff --git a/FrontEnd/src/components/list/ListContainer.css b/FrontEnd/src/components/list/ListContainer.css new file mode 100644 index 00000000..53781834 --- /dev/null +++ b/FrontEnd/src/components/list/ListContainer.css @@ -0,0 +1,4 @@ +.cru-list-container { + border: 1px solid var(--cru-clickable-primary-normal-color); + border-radius: 5px; +} diff --git a/FrontEnd/src/components/list/ListContainer.tsx b/FrontEnd/src/components/list/ListContainer.tsx new file mode 100644 index 00000000..aa00d12c --- /dev/null +++ b/FrontEnd/src/components/list/ListContainer.tsx @@ -0,0 +1,23 @@ +import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; +import classNames from "classnames"; + +import "./ListContainer.css" + +function _ListContainer( + { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, + ref: Ref, +) { + return ( +
+ {children} +
+ ); +} + +const ListContainer = forwardRef(_ListContainer); + +export default ListContainer; diff --git a/FrontEnd/src/components/list/ListItemContainer.css b/FrontEnd/src/components/list/ListItemContainer.css new file mode 100644 index 00000000..8d7afa9f --- /dev/null +++ b/FrontEnd/src/components/list/ListItemContainer.css @@ -0,0 +1,3 @@ +.cru-list-item-container { + border: 1px solid var(--cru-clickable-primary-normal-color); +} diff --git a/FrontEnd/src/components/list/ListItemContainer.tsx b/FrontEnd/src/components/list/ListItemContainer.tsx new file mode 100644 index 00000000..315cbd6e --- /dev/null +++ b/FrontEnd/src/components/list/ListItemContainer.tsx @@ -0,0 +1,23 @@ +import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; +import classNames from "classnames"; + +import "./ListItemContainer.css"; + +function _ListItemContainer( + { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, + ref: Ref, +) { + return ( +
+ {children} +
+ ); +} + +const ListItemContainer = forwardRef(_ListItemContainer); + +export default ListItemContainer; diff --git a/FrontEnd/src/components/list/index.ts b/FrontEnd/src/components/list/index.ts new file mode 100644 index 00000000..e183f7da --- /dev/null +++ b/FrontEnd/src/components/list/index.ts @@ -0,0 +1,4 @@ +import ListContainer from "./ListContainer"; +import ListItemContainer from "./ListItemContainer"; + +export { ListContainer, ListItemContainer }; diff --git a/FrontEnd/src/components/menu/Menu.css b/FrontEnd/src/components/menu/Menu.css new file mode 100644 index 00000000..75734533 --- /dev/null +++ b/FrontEnd/src/components/menu/Menu.css @@ -0,0 +1,36 @@ +.cru-menu { + min-width: 200px; +} + +.cru-menu-item { + display: block; + font-size: 1em; + width: 100%; + padding: 0.5em 1.5em; + transition: all 0.5s; + color: var(--cru-clickable-normal-color); + background-color: var(--cru-clickable-grayscale-normal-color); + border: none; + cursor: pointer; +} + +.cru-menu-item:hover { + background-color: var(--cru-clickable-grayscale-hover-color); +} + +.cru-menu-item:focus { + background-color: var(--cru-clickable-grayscale-focus-color); +} + +.cru-menu-item:active { + background-color: var(--cru-clickable-grayscale-active-color); +} + +.cru-menu-item-icon { + margin-right: 1em; +} + +.cru-menu-divider { + border-width: 0; + border-top: 1px solid var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/components/menu/Menu.tsx b/FrontEnd/src/components/menu/Menu.tsx new file mode 100644 index 00000000..e8099c76 --- /dev/null +++ b/FrontEnd/src/components/menu/Menu.tsx @@ -0,0 +1,62 @@ +import { CSSProperties } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import "./Menu.css"; +import Icon from "../Icon"; + +export type MenuItem = + | { + type: "divider"; + } + | { + type: "button"; + text: Text; + icon?: string; + color?: ThemeColor; + onClick: () => void; + }; + +export type MenuItems = MenuItem[]; + +export type MenuProps = { + items: MenuItems; + onItemClicked?: () => void; + className?: string; + style?: CSSProperties; +}; + +export default function Menu({ + items, + onItemClicked, + className, + style, +}: MenuProps) { + const c = useC(); + + return ( +
+ {items.map((item, index) => { + if (item.type === "divider") { + return
; + } else { + const { text, color, icon, onClick } = item; + return ( + + ); + } + })} +
+ ); +} diff --git a/FrontEnd/src/components/menu/PopupMenu.css b/FrontEnd/src/components/menu/PopupMenu.css new file mode 100644 index 00000000..149e0699 --- /dev/null +++ b/FrontEnd/src/components/menu/PopupMenu.css @@ -0,0 +1,7 @@ +.cru-popup-menu-menu-container { + z-index: 1040; + border-radius: 3px; + border: var(--cru-clickable-normal-color) 1.5px solid; + background-color: var(--cru-background-color); + overflow: hidden; +} diff --git a/FrontEnd/src/components/menu/PopupMenu.tsx b/FrontEnd/src/components/menu/PopupMenu.tsx new file mode 100644 index 00000000..23a67f79 --- /dev/null +++ b/FrontEnd/src/components/menu/PopupMenu.tsx @@ -0,0 +1,73 @@ +import { useState, CSSProperties, ReactNode } from "react"; +import classNames from "classnames"; +import { createPortal } from "react-dom"; +import { usePopper } from "react-popper"; + +import { useClickOutside } from "~src/utilities/hooks"; + +import Menu, { MenuItems } from "./Menu"; + +import { ThemeColor } from "../common"; + +import "./PopupMenu.css"; + +export interface PopupMenuProps { + color?: ThemeColor; + items: MenuItems; + children?: ReactNode; + containerClassName?: string; + containerStyle?: CSSProperties; +} + +export default function PopupMenu({ + color, + items, + children, + containerClassName, + containerStyle, +}: PopupMenuProps) { + const [show, setShow] = useState(false); + + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null, + ); + const { styles, attributes } = usePopper(referenceElement, popperElement); + + useClickOutside(popperElement, () => setShow(false), true); + + return ( +
setShow(true)} + > + {children} + {show && + createPortal( +
+ { + setShow(false); + }} + /> +
, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")!, + )} +
+ ); +} diff --git a/FrontEnd/src/components/tab/TabPages.tsx b/FrontEnd/src/components/tab/TabPages.tsx new file mode 100644 index 00000000..6a5f4469 --- /dev/null +++ b/FrontEnd/src/components/tab/TabPages.tsx @@ -0,0 +1,71 @@ +import * as React from "react"; + +import { I18nText, UiLogicError } from "~src/common"; + +import Tabs from "./Tabs"; + +export interface TabPage { + name: string; + text: I18nText; + page: React.ReactNode; +} + +export interface TabPagesProps { + pages: TabPage[]; + actions?: React.ReactNode; + dense?: boolean; + className?: string; + style?: React.CSSProperties; + navClassName?: string; + navStyle?: React.CSSProperties; + pageContainerClassName?: string; + pageContainerStyle?: React.CSSProperties; +} + +const TabPages: React.FC = ({ + pages, + actions, + dense, + className, + style, + navClassName, + navStyle, + pageContainerClassName, + pageContainerStyle, +}) => { + if (pages.length === 0) { + throw new UiLogicError("Page list can't be empty."); + } + + const [tab, setTab] = React.useState(pages[0].name); + + const currentPage = pages.find((p) => p.name === tab); + + if (currentPage == null) { + throw new UiLogicError("Current tab value is bad."); + } + + return ( +
+ ({ + name: page.name, + text: page.text, + onClick: () => { + setTab(page.name); + }, + }))} + dense={dense} + activeTabName={tab} + className={navClassName} + style={navStyle} + actions={actions} + /> +
+ {currentPage.page} +
+
+ ); +}; + +export default TabPages; diff --git a/FrontEnd/src/components/tab/Tabs.css b/FrontEnd/src/components/tab/Tabs.css new file mode 100644 index 00000000..395d16a7 --- /dev/null +++ b/FrontEnd/src/components/tab/Tabs.css @@ -0,0 +1,33 @@ +.cru-nav { + border-bottom: var(--cru-primary-color) 1px solid; + display: flex; +} + +.cru-nav-item { + color: var(--cru-primary-color); + border: var(--cru-background-2-color) 0.5px solid; + border-bottom: none; + padding: 0.5em 1.5em; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + transition: all 0.5s; + cursor: pointer; +} + +.cru-nav.dense .cru-nav-item { + padding: 0.2em 1em; +} + +.cru-nav-item:hover { + background-color: var(--cru-background-1-color); +} + +.cru-nav-item.active { + color: var(--cru-primary-t-color); + background-color: var(--cru-primary-color); + border-color: var(--cru-primary-color); +} + +.cru-nav-action-area { + margin-left: auto; +} diff --git a/FrontEnd/src/components/tab/Tabs.tsx b/FrontEnd/src/components/tab/Tabs.tsx new file mode 100644 index 00000000..dc8d9c01 --- /dev/null +++ b/FrontEnd/src/components/tab/Tabs.tsx @@ -0,0 +1,62 @@ +import * as React from "react"; +import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import classnames from "classnames"; + +import { convertI18nText, I18nText } from "~src/common"; + +import "./Tabs.css"; + +export interface Tab { + name: string; + text: I18nText; + link?: string; + onClick?: () => void; +} + +export interface TabsProps { + activeTabName?: string; + actions?: React.ReactNode; + dense?: boolean; + tabs: Tab[]; + className?: string; + style?: React.CSSProperties; +} + +export default function Tabs(props: TabsProps): React.ReactElement | null { + const { tabs, activeTabName, className, style, dense, actions } = props; + + const { t } = useTranslation(); + + return ( +
+ {tabs.map((tab) => { + const active = activeTabName === tab.name; + const className = classnames("cru-nav-item", active && "active"); + + if (tab.link != null) { + return ( + + {convertI18nText(tab.text, t)} + + ); + } else { + return ( + + {convertI18nText(tab.text, t)} + + ); + } + })} +
{actions}
+
+ ); +} diff --git a/FrontEnd/src/components/theme-color.css b/FrontEnd/src/components/theme-color.css new file mode 100644 index 00000000..24a7e267 --- /dev/null +++ b/FrontEnd/src/components/theme-color.css @@ -0,0 +1,173 @@ +/* Generated by theme-generator.ts */ + +:root { + --cru-primary-color: hsl(210 100% 40%); + --cru-primary-1-color: hsl(210 100% 37%); + --cru-primary-2-color: hsl(210 100% 34%); + --cru-primary-on-color: hsl(210 100% 100%); + --cru-primary-container-color: hsl(210 100% 90%); + --cru-primary-container-1-color: hsl(210 100% 80%); + --cru-primary-container-2-color: hsl(210 100% 70%); + --cru-primary-on-container-color: hsl(210 100% 10%); + --cru-secondary-color: hsl(40 100% 40%); + --cru-secondary-1-color: hsl(40 100% 37%); + --cru-secondary-2-color: hsl(40 100% 34%); + --cru-secondary-on-color: hsl(40 100% 100%); + --cru-secondary-container-color: hsl(40 100% 90%); + --cru-secondary-container-1-color: hsl(40 100% 80%); + --cru-secondary-container-2-color: hsl(40 100% 70%); + --cru-secondary-on-container-color: hsl(40 100% 10%); + --cru-tertiary-color: hsl(160 100% 40%); + --cru-tertiary-1-color: hsl(160 100% 37%); + --cru-tertiary-2-color: hsl(160 100% 34%); + --cru-tertiary-on-color: hsl(160 100% 100%); + --cru-tertiary-container-color: hsl(160 100% 90%); + --cru-tertiary-container-1-color: hsl(160 100% 80%); + --cru-tertiary-container-2-color: hsl(160 100% 70%); + --cru-tertiary-on-container-color: hsl(160 100% 10%); + --cru-danger-color: hsl(0 100% 40%); + --cru-danger-1-color: hsl(0 100% 37%); + --cru-danger-2-color: hsl(0 100% 34%); + --cru-danger-on-color: hsl(0 100% 100%); + --cru-danger-container-color: hsl(0 100% 90%); + --cru-danger-container-1-color: hsl(0 100% 80%); + --cru-danger-container-2-color: hsl(0 100% 70%); + --cru-danger-on-container-color: hsl(0 100% 10%); + --cru-success-color: hsl(120 60% 40%); + --cru-success-1-color: hsl(120 60% 37%); + --cru-success-2-color: hsl(120 60% 34%); + --cru-success-on-color: hsl(120 60% 100%); + --cru-success-container-color: hsl(120 60% 90%); + --cru-success-container-1-color: hsl(120 60% 80%); + --cru-success-container-2-color: hsl(120 60% 70%); + --cru-success-on-container-color: hsl(120 60% 10%); + --cru-surface-dim-color: hsl(0 0% 87%); + --cru-surface-color: hsl(0 0% 98%); + --cru-surface-1-color: hsl(0 0% 90%); + --cru-surface-2-color: hsl(0 0% 82%); + --cru-surface-bright-color: hsl(0 0% 98%); + --cru-surface-container-lowest-color: hsl(0 0% 100%); + --cru-surface-container-low-color: hsl(0 0% 96%); + --cru-surface-container-color: hsl(0 0% 94%); + --cru-surface-container-high-color: hsl(0 0% 92%); + --cru-surface-container-highest-color: hsl(0 0% 90%); + --cru-surface-on-color: hsl(0 0% 10%); + --cru-surface-on-variant-color: hsl(0 0% 30%); + --cru-surface-outline-color: hsl(0 0% 50%); + --cru-surface-outline-variant-color: hsl(0 0% 80%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-primary-color: hsl(210 100% 80%); + --cru-primary-1-color: hsl(210 100% 75%); + --cru-primary-2-color: hsl(210 100% 68%); + --cru-primary-on-color: hsl(210 100% 20%); + --cru-primary-container-color: hsl(210 100% 30%); + --cru-primary-container-1-color: hsl(210 100% 25%); + --cru-primary-container-2-color: hsl(210 100% 20%); + --cru-primary-on-container-color: hsl(210 100% 90%); + --cru-secondary-color: hsl(40 100% 80%); + --cru-secondary-1-color: hsl(40 100% 75%); + --cru-secondary-2-color: hsl(40 100% 68%); + --cru-secondary-on-color: hsl(40 100% 20%); + --cru-secondary-container-color: hsl(40 100% 30%); + --cru-secondary-container-1-color: hsl(40 100% 25%); + --cru-secondary-container-2-color: hsl(40 100% 20%); + --cru-secondary-on-container-color: hsl(40 100% 90%); + --cru-tertiary-color: hsl(160 100% 80%); + --cru-tertiary-1-color: hsl(160 100% 75%); + --cru-tertiary-2-color: hsl(160 100% 68%); + --cru-tertiary-on-color: hsl(160 100% 20%); + --cru-tertiary-container-color: hsl(160 100% 30%); + --cru-tertiary-container-1-color: hsl(160 100% 25%); + --cru-tertiary-container-2-color: hsl(160 100% 20%); + --cru-tertiary-on-container-color: hsl(160 100% 90%); + --cru-danger-color: hsl(0 100% 80%); + --cru-danger-1-color: hsl(0 100% 75%); + --cru-danger-2-color: hsl(0 100% 68%); + --cru-danger-on-color: hsl(0 100% 20%); + --cru-danger-container-color: hsl(0 100% 30%); + --cru-danger-container-1-color: hsl(0 100% 25%); + --cru-danger-container-2-color: hsl(0 100% 20%); + --cru-danger-on-container-color: hsl(0 100% 90%); + --cru-success-color: hsl(120 60% 80%); + --cru-success-1-color: hsl(120 60% 75%); + --cru-success-2-color: hsl(120 60% 68%); + --cru-success-on-color: hsl(120 60% 20%); + --cru-success-container-color: hsl(120 60% 30%); + --cru-success-container-1-color: hsl(120 60% 25%); + --cru-success-container-2-color: hsl(120 60% 20%); + --cru-success-on-container-color: hsl(120 60% 90%); + --cru-surface-dim-color: hsl(0 0% 6%); + --cru-surface-color: hsl(0 0% 6%); + --cru-surface-1-color: hsl(0 0% 25%); + --cru-surface-2-color: hsl(0 0% 40%); + --cru-surface-bright-color: hsl(0 0% 24%); + --cru-surface-container-lowest-color: hsl(0 0% 4%); + --cru-surface-container-low-color: hsl(0 0% 10%); + --cru-surface-container-color: hsl(0 0% 12%); + --cru-surface-container-high-color: hsl(0 0% 17%); + --cru-surface-container-highest-color: hsl(0 0% 22%); + --cru-surface-on-color: hsl(0 0% 90%); + --cru-surface-on-variant-color: hsl(0 0% 80%); + --cru-surface-outline-color: hsl(0 0% 60%); + --cru-surface-outline-variant-color: hsl(0 0% 30%); + } +} + +.cru-primary { + --cru-key-color: var(--cru-primary-color); + --cru-key-1-color: var(--cru-primary-1-color); + --cru-key-2-color: var(--cru-primary-2-color); + --cru-key-on-color: var(--cru-primary-on-color); + --cru-key-container-color: var(--cru-primary-container-color); + --cru-key-container-1-color: var(--cru-primary-container-1-color); + --cru-key-container-2-color: var(--cru-primary-container-2-color); + --cru-key-on-container-color: var(--cru-primary-on-container-color); +} + +.cru-secondary { + --cru-key-color: var(--cru-secondary-color); + --cru-key-1-color: var(--cru-secondary-1-color); + --cru-key-2-color: var(--cru-secondary-2-color); + --cru-key-on-color: var(--cru-secondary-on-color); + --cru-key-container-color: var(--cru-secondary-container-color); + --cru-key-container-1-color: var(--cru-secondary-container-1-color); + --cru-key-container-2-color: var(--cru-secondary-container-2-color); + --cru-key-on-container-color: var(--cru-secondary-on-container-color); +} + +.cru-tertiary { + --cru-key-color: var(--cru-tertiary-color); + --cru-key-1-color: var(--cru-tertiary-1-color); + --cru-key-2-color: var(--cru-tertiary-2-color); + --cru-key-on-color: var(--cru-tertiary-on-color); + --cru-key-container-color: var(--cru-tertiary-container-color); + --cru-key-container-1-color: var(--cru-tertiary-container-1-color); + --cru-key-container-2-color: var(--cru-tertiary-container-2-color); + --cru-key-on-container-color: var(--cru-tertiary-on-container-color); +} + +.cru-danger { + --cru-key-color: var(--cru-danger-color); + --cru-key-1-color: var(--cru-danger-1-color); + --cru-key-2-color: var(--cru-danger-2-color); + --cru-key-on-color: var(--cru-danger-on-color); + --cru-key-container-color: var(--cru-danger-container-color); + --cru-key-container-1-color: var(--cru-danger-container-1-color); + --cru-key-container-2-color: var(--cru-danger-container-2-color); + --cru-key-on-container-color: var(--cru-danger-on-container-color); +} + +.cru-success { + --cru-key-color: var(--cru-success-color); + --cru-key-1-color: var(--cru-success-1-color); + --cru-key-2-color: var(--cru-success-2-color); + --cru-key-on-color: var(--cru-success-on-color); + --cru-key-container-color: var(--cru-success-container-color); + --cru-key-container-1-color: var(--cru-success-container-1-color); + --cru-key-container-2-color: var(--cru-success-container-2-color); + --cru-key-on-container-color: var(--cru-success-on-container-color); +} + diff --git a/FrontEnd/src/components/theme.css b/FrontEnd/src/components/theme.css new file mode 100644 index 00000000..6ceb369f --- /dev/null +++ b/FrontEnd/src/components/theme.css @@ -0,0 +1,146 @@ +@import "./theme-color.css"; + +:root { + --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --cru-page-padding: 1em 2em; + + --cru-border-radius: 4px; + --cru-card-border-radius: 4px; +} + +/* theme colors */ +:root { + --cru-primary-color: hsl(210, 100%, 50%); + --cru-secondary-color: hsl(30, 100%, 50%); + --cru-create-color: hsl(120, 100%, 25%); + --cru-danger-color: hsl(0, 100%, 50%); +} + +/* common colors */ +:root { + --cru-background-color: hsl(0, 0%, 100%); + --cru-container-background-color: hsl(0, 0%, 97%); + --cru-text-primary-color: hsl(0, 0%, 0%); + --cru-text-secondary-color: hsl(0, 0%, 38%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-background-color: hsl(0, 0%, 0%); + --cru-container-background-color: hsl(0, 0%, 2%); + --cru-text-primary-color: hsl(0, 0%, 100%); + --cru-text-secondary-color: hsl(0, 0%, 85%); + } +} + +:root { + --cru-body-background-color: var(--cru-background-color); +} + +/* clickable color */ +:root { + --cru-clickable-primary-normal-color: var(--cru-primary-color); + --cru-clickable-primary-hover-color: hsl(210, 100%, 60%); + --cru-clickable-primary-focus-color: hsl(210, 100%, 60%); + --cru-clickable-primary-active-color: hsl(210, 100%, 70%); + --cru-clickable-secondary-normal-color: var(--cru-secondary-color); + --cru-clickable-secondary-hover-color: hsl(30, 100%, 60%); + --cru-clickable-secondary-focus-color: hsl(30, 100%, 60%); + --cru-clickable-secondary-active-color: hsl(30, 100%, 70%); + --cru-clickable-create-normal-color: var(--cru-create-color); + --cru-clickable-create-hover-color: hsl(120, 100%, 35%); + --cru-clickable-create-focus-color: hsl(120, 100%, 35%); + --cru-clickable-create-active-color: hsl(120, 100%, 35%); + --cru-clickable-danger-normal-color: var(--cru-danger-color); + --cru-clickable-danger-hover-color: hsl(0, 100%, 60%); + --cru-clickable-danger-focus-color: hsl(0, 100%, 60%); + --cru-clickable-danger-active-color: hsl(0, 100%, 70%); + --cru-clickable-grayscale-normal-color: hsl(0, 0%, 100%); + --cru-clickable-grayscale-hover-color: hsl(0, 0%, 92%); + --cru-clickable-grayscale-focus-color: hsl(0, 0%, 92%); + --cru-clickable-grayscale-active-color: hsl(0, 0%, 88%); + --cru-clickable-disabled-color: hsl(0, 0%, 50%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-clickable-grayscale-normal-color: hsl(0, 0%, 0%); + --cru-clickable-grayscale-hover-color: hsl(0, 0%, 10%); + --cru-clickable-grayscale-focus-color: hsl(0, 0%, 10%); + --cru-clickable-grayscale-active-color: hsl(0, 0%, 20%); + } +} + +.cru-clickable-primary { + --cru-clickable-normal-color: var(--cru-clickable-primary-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-primary-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-primary-focus-color); + --cru-clickable-active-color: var(--cru-clickable-primary-active-color); +} + +.cru-clickable-secondary { + --cru-clickable-normal-color: var(--cru-clickable-secondary-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-secondary-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-secondary-focus-color); + --cru-clickable-active-color: var(--cru-clickable-secondary-active-color); +} + +.cru-clickable-create { + --cru-clickable-normal-color: var(--cru-clickable-create-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-create-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-create-focus-color); + --cru-clickable-active-color: var(--cru-clickable-create-active-color); +} + +.cru-clickable-danger { + --cru-clickable-normal-color: var(--cru-clickable-danger-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-danger-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-danger-focus-color); + --cru-clickable-active-color: var(--cru-clickable-danger-active-color); +} + +.cru-clickable-grayscale { + --cru-clickable-normal-color: var(--cru-clickable-grayscale-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-grayscale-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-grayscale-focus-color); + --cru-clickable-active-color: var(--cru-clickable-grayscale-active-color); +} + +/* button colors */ +:root { + /* push button colors */ + --cru-push-button-text-color: #ffffff; + --cru-push-button-disabled-text-color: hsl(0, 0%, 80%); +} + +/* Card colors */ +:root { + --cru-card-background-primary-color: hsl(210, 100%, 50%); + --cru-card-border-primary-color: hsl(210, 100%, 50%); + --cru-card-background-secondary-color: hsl(30, 100%, 50%); + --cru-card-border-secondary-color: hsl(30, 100%, 50%); + --cru-card-background-create-color: hsl(120, 100%, 25%); + --cru-card-border-create-color: hsl(120, 100%, 25%); + --cru-card-background-danger-color: hsl(0, 100%, 50%); + --cru-card-border-danger-color: hsl(0, 100%, 50%); +} + +.cru-card-primary { + --cru-card-background-color: var(--cru-card-background-primary-color); + --cru-card-border-color: var(--cru-card-border-primary-color) +} + +.cru-card-secondary { + --cru-card-background-color: var(--cru-card-background-secondary-color); + --cru-card-border-color: var(--cru-card-border-secondary-color) +} + +.cru-card-create { + --cru-card-background-color: var(--cru-card-background-create-color); + --cru-card-border-color: var(--cru-card-border-create-color) +} + +.cru-card-danger { + --cru-card-background-color: var(--cru-card-background-danger-color); + --cru-card-border-color: var(--cru-card-border-danger-color) +} \ No newline at end of file diff --git a/FrontEnd/src/components/user/UserAvatar.tsx b/FrontEnd/src/components/user/UserAvatar.tsx new file mode 100644 index 00000000..8671f2d8 --- /dev/null +++ b/FrontEnd/src/components/user/UserAvatar.tsx @@ -0,0 +1,22 @@ +import { Ref, ComponentPropsWithoutRef } from "react"; + +import { getHttpUserClient } from "~src/http/user"; + +export interface UserAvatarProps extends ComponentPropsWithoutRef<"img"> { + username: string; + imgRef?: Ref | null; +} + +export default function UserAvatar({ + username, + imgRef, + ...otherProps +}: UserAvatarProps) { + return ( + + ); +} diff --git a/FrontEnd/src/http/bookmark.ts b/FrontEnd/src/http/bookmark.ts index 40e121cc..311f9a0f 100644 --- a/FrontEnd/src/http/bookmark.ts +++ b/FrontEnd/src/http/bookmark.ts @@ -1,4 +1,4 @@ -import { withQuery } from "@/utilities/url"; +import { withQuery } from "~src/utilities/url"; import { axios, apiBaseUrl, extractResponseData, Page } from "./common"; diff --git a/FrontEnd/src/http/timeline.ts b/FrontEnd/src/http/timeline.ts index 401ae116..255c786e 100644 --- a/FrontEnd/src/http/timeline.ts +++ b/FrontEnd/src/http/timeline.ts @@ -1,4 +1,4 @@ -import { withQuery } from "@/utilities/url"; +import { withQuery } from "~src/utilities/url"; import { axios, diff --git a/FrontEnd/src/migrating/center/CenterBoards.tsx b/FrontEnd/src/migrating/center/CenterBoards.tsx index a8be2c29..f1c3fc6a 100644 --- a/FrontEnd/src/migrating/center/CenterBoards.tsx +++ b/FrontEnd/src/migrating/center/CenterBoards.tsx @@ -1,13 +1,13 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; -import { highlightTimelineUsername } from "@/common"; +import { highlightTimelineUsername } from "~src/common"; -import { pushAlert } from "@/services/alert"; -import { useUserLoggedIn } from "@/services/user"; +import { pushAlert } from "~src/services/alert"; +import { useUserLoggedIn } from "~src/services/user"; -import { getHttpTimelineClient } from "@/http/timeline"; -import { getHttpBookmarkClient } from "@/http/bookmark"; +import { getHttpTimelineClient } from "~src/http/timeline"; +import { getHttpBookmarkClient } from "~src/http/bookmark"; import TimelineBoard from "./TimelineBoard"; diff --git a/FrontEnd/src/migrating/center/TimelineBoard.tsx b/FrontEnd/src/migrating/center/TimelineBoard.tsx index b3ccdf8c..8f4401bc 100644 --- a/FrontEnd/src/migrating/center/TimelineBoard.tsx +++ b/FrontEnd/src/migrating/center/TimelineBoard.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import classnames from "classnames"; import { Link } from "react-router-dom"; -import { TimelineBookmark } from "@/http/bookmark"; +import { TimelineBookmark } from "~src/http/bookmark"; import TimelineLogo from "../common/TimelineLogo"; import LoadFailReload from "../common/LoadFailReload"; diff --git a/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx b/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx index 63742936..340a08fe 100644 --- a/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx +++ b/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx @@ -1,11 +1,11 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; -import { validateTimelineName } from "@/services/timeline"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { validateTimelineName } from "~src/services/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; import OperationDialog from "../common/dialog/OperationDialog"; -import { useUserLoggedIn } from "@/services/user"; +import { useUserLoggedIn } from "~src/services/user"; interface TimelineCreateDialogProps { open: boolean; diff --git a/FrontEnd/src/migrating/center/index.tsx b/FrontEnd/src/migrating/center/index.tsx index 77af2c20..11502517 100644 --- a/FrontEnd/src/migrating/center/index.tsx +++ b/FrontEnd/src/migrating/center/index.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; -import { useUserLoggedIn } from "@/services/user"; +import { useUserLoggedIn } from "~src/services/user"; import SearchInput from "../common/SearchInput"; import Button from "../common/button/Button"; diff --git a/FrontEnd/src/pages/about/index.tsx b/FrontEnd/src/pages/about/index.tsx index acec1735..bce64322 100644 --- a/FrontEnd/src/pages/about/index.tsx +++ b/FrontEnd/src/pages/about/index.tsx @@ -1,7 +1,7 @@ import "./index.css"; -import { useC } from "@/common"; -import Page from "@/views/common/Page"; +import { useC } from "~src/common"; +import Page from "~src/components/Page"; interface Credit { name: string; diff --git a/FrontEnd/src/pages/loading/index.tsx b/FrontEnd/src/pages/loading/index.tsx index e4c8edab..29d27adc 100644 --- a/FrontEnd/src/pages/loading/index.tsx +++ b/FrontEnd/src/pages/loading/index.tsx @@ -1,4 +1,4 @@ -import Spinner from "@/views/common/Spinner"; +import Spinner from "~src/components/Spinner"; import "./index.css"; diff --git a/FrontEnd/src/pages/login/index.tsx b/FrontEnd/src/pages/login/index.tsx index a09e32c3..582ebd0f 100644 --- a/FrontEnd/src/pages/login/index.tsx +++ b/FrontEnd/src/pages/login/index.tsx @@ -2,16 +2,16 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; -import { useUser, userService } from "@/services/user"; +import { useUser, userService } from "~src/services/user"; -import { useC } from "@/views/common/common"; -import LoadingButton from "@/views/common/button/LoadingButton"; +import { useC } from "~src/components/common"; +import LoadingButton from "~src/components/button/LoadingButton"; import { InputErrorDict, InputGroup, useInputs, -} from "@/views/common/input/InputGroup"; -import Page from "@/views/common/Page"; +} from "~src/components/input/InputGroup"; +import Page from "~src/components/Page"; import "./index.css"; diff --git a/FrontEnd/src/pages/register/index.tsx b/FrontEnd/src/pages/register/index.tsx index bc474adb..9e478612 100644 --- a/FrontEnd/src/pages/register/index.tsx +++ b/FrontEnd/src/pages/register/index.tsx @@ -2,16 +2,16 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { HttpBadRequestError } from "@/http/common"; -import { getHttpTokenClient } from "@/http/token"; -import { userService, useUser } from "@/services/user"; +import { HttpBadRequestError } from "~src/http/common"; +import { getHttpTokenClient } from "~src/http/token"; +import { userService, useUser } from "~src/services/user"; -import { LoadingButton } from "@/views/common/button"; +import { LoadingButton } from "~src/components/button"; import { useInputs, InputErrorDict, InputGroup, -} from "@/views/common/input/InputGroup"; +} from "~src/components/input/InputGroup"; import "./index.css"; diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx index f35fc5a7..c34bcf4f 100644 --- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -1,19 +1,19 @@ import { useState, ChangeEvent, ComponentPropsWithoutRef } from "react"; -import { useC, Text, UiLogicError } from "@/common"; +import { useC, Text, UiLogicError } from "~src/common"; -import { useUser } from "@/services/user"; +import { useUser } from "~src/services/user"; -import { getHttpUserClient } from "@/http/user"; +import { getHttpUserClient } from "~src/http/user"; import ImageCropper, { Clip, applyClipToImage, -} from "@/views/common/ImageCropper"; -import BlobImage from "@/views/common/BlobImage"; -import ButtonRowV2 from "@/views/common/button/ButtonRowV2"; -import Dialog from "@/views/common/dialog/Dialog"; -import DialogContainer from "@/views/common/dialog/DialogContainer"; +} from "~src/components/ImageCropper"; +import BlobImage from "~src/components/BlobImage"; +import ButtonRowV2 from "~src/components/button/ButtonRowV2"; +import Dialog from "~src/components/dialog/Dialog"; +import DialogContainer from "~src/components/dialog/DialogContainer"; import "./ChangeAvatarDialog.css"; diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx index 4d318543..843659ef 100644 --- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx @@ -1,7 +1,7 @@ -import { getHttpUserClient } from "@/http/user"; -import { useUserLoggedIn } from "@/services/user"; +import { getHttpUserClient } from "~src/http/user"; +import { useUserLoggedIn } from "~src/services/user"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; export interface ChangeNicknameDialogProps { open: boolean; @@ -26,7 +26,7 @@ export default function ChangeNicknameDialog(props: ChangeNicknameDialogProps) { ]} onProcess={({ newNickname }) => { return getHttpUserClient().patch(user.username, { - nickname: newNickname as string, + nickname: newNickname, }); }} onClose={onClose} diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx index 87a970a5..bfcea92d 100644 --- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { userService } from "@/services/user"; +import { userService } from "~src/services/user"; import OperationDialog, { InputErrorDict, -} from "@/views/common/dialog/OperationDialog"; +} from "~src/components/dialog/OperationDialog"; interface ChangePasswordDialogProps { open: boolean; @@ -65,10 +65,7 @@ export function ChangePasswordDialog(props: ChangePasswordDialogProps) { }, }} onProcess={async ({ oldPassword, newPassword }) => { - await userService.changePassword( - oldPassword as string, - newPassword as string, - ); + await userService.changePassword(oldPassword, newPassword); setRedirect(true); }} onSuccessAndClose={() => { diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 50967a3c..67416a08 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -8,21 +8,21 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import classNames from "classnames"; -import { useC, Text } from "@/common"; -import { useUser, userService } from "@/services/user"; -import { getHttpUserClient } from "@/http/user"; +import { useC, Text } from "~src/common"; +import { useUser, userService } from "~src/services/user"; +import { getHttpUserClient } from "~src/http/user"; -import { useDialog } from "@/views/common/dialog"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; -import Card from "@/views/common/Card"; -import Spinner from "@/views/common/Spinner"; -import Page from "@/views/common/Page"; +import { useDialog } from "~src/components/dialog"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; +import Card from "~src/components/Card"; +import Spinner from "~src/components/Spinner"; +import Page from "~src/components/Page"; import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; import "./index.css"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; interface SettingSectionProps extends Omit, "title"> { diff --git a/FrontEnd/src/pages/timeline/CollapseButton.tsx b/FrontEnd/src/pages/timeline/CollapseButton.tsx index 14fc6bee..1c4fa2ba 100644 --- a/FrontEnd/src/pages/timeline/CollapseButton.tsx +++ b/FrontEnd/src/pages/timeline/CollapseButton.tsx @@ -1,6 +1,6 @@ import { CSSProperties } from "react"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; export default function CollapseButton({ collapse, diff --git a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx index 9c497108..43e81d67 100644 --- a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx +++ b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx @@ -2,15 +2,15 @@ import * as React from "react"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import TimelinePostBuilder from "@/services/TimelinePostBuilder"; +import TimelinePostBuilder from "~src/services/TimelinePostBuilder"; -import FlatButton from "@/views/common/button/FlatButton"; -import TabPages from "@/views/common/tab/TabPages"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; -import Spinner from "@/views/common/Spinner"; -import IconButton from "@/views/common/button/IconButton"; +import FlatButton from "~src/components/button/FlatButton"; +import TabPages from "~src/components/tab/TabPages"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; +import Spinner from "~src/components/Spinner"; +import IconButton from "~src/components/button/IconButton"; import "./MarkdownPostEdit.css"; diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index 73e621c1..f266ec9d 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -1,20 +1,20 @@ import { useState, useEffect } from "react"; import classnames from "classnames"; -import { useScrollToBottom } from "@/utilities/hooks"; +import { useScrollToBottom } from "~src/utilities/hooks"; import { HubConnectionState } from "@microsoft/signalr"; import { HttpForbiddenError, HttpNetworkError, HttpNotFoundError, -} from "@/http/common"; +} from "~src/http/common"; import { getHttpTimelineClient, HttpTimelineInfo, HttpTimelinePostInfo, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import { getTimelinePostUpdate$ } from "@/services/timeline"; +import { getTimelinePostUpdate$ } from "~src/services/timeline"; import TimelinePostList from "./TimelinePostList"; import TimelinePostEdit from "./TimelinePostCreateView"; diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx index 2987aa74..82d6d350 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx @@ -1,24 +1,24 @@ import { useState } from "react"; import { HubConnectionState } from "@microsoft/signalr"; -import { useUser } from "@/services/user"; -import { pushAlert } from "@/services/alert"; +import { useUser } from "~src/services/user"; +import { pushAlert } from "~src/services/alert"; -import { HttpTimelineInfo } from "@/http/timeline"; -import { getHttpBookmarkClient } from "@/http/bookmark"; +import { HttpTimelineInfo } from "~src/http/timeline"; +import { getHttpBookmarkClient } from "~src/http/bookmark"; -import { useMobile } from "@/views/common/common"; -import { Dialog, useDialog } from "@/views/common/dialog"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import PopupMenu from "@/views/common/menu/PopupMenu"; -import FullPageDialog from "@/views/common/dialog/FullPageDialog"; -import Card from "@/views/common/Card"; +import { useMobile } from "~src/components/common"; +import { Dialog, useDialog } from "~src/components/dialog"; +import UserAvatar from "~src/components/user/UserAvatar"; +import PopupMenu from "~src/components/menu/PopupMenu"; +import FullPageDialog from "~src/components/dialog/FullPageDialog"; +import Card from "~src/components/Card"; import TimelineDeleteDialog from "./TimelineDeleteDialog"; import ConnectionStatusBadge from "./ConnectionStatusBadge"; import CollapseButton from "./CollapseButton"; import TimelineMember from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import "./TimelineCard.css"; diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx index 7d7b9527..7b7b8e8c 100644 --- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx @@ -2,9 +2,9 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; interface TimelineDeleteDialog { timeline: HttpTimelineInfo; diff --git a/FrontEnd/src/pages/timeline/TimelineMember.tsx b/FrontEnd/src/pages/timeline/TimelineMember.tsx index 4c1600f5..a25fe6a9 100644 --- a/FrontEnd/src/pages/timeline/TimelineMember.tsx +++ b/FrontEnd/src/pages/timeline/TimelineMember.tsx @@ -1,16 +1,16 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { convertI18nText, I18nText } from "@/common"; +import { convertI18nText, I18nText } from "~src/common"; -import { HttpUser } from "@/http/user"; -import { getHttpSearchClient } from "@/http/search"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { HttpUser } from "~src/http/user"; +import { getHttpSearchClient } from "~src/http/search"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; -import SearchInput from "@/views/common/SearchInput"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import Button from "@/views/common/button/Button"; -import { ListContainer, ListItemContainer } from "@/views/common/list"; +import SearchInput from "~src/components/SearchInput"; +import UserAvatar from "~src/components/user/UserAvatar"; +import Button from "~src/components/button/Button"; +import { ListContainer, ListItemContainer } from "~src/components/list"; import "./TimelineMember.css"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx index 23dd141f..d3fd3215 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx @@ -1,7 +1,7 @@ import { ReactNode } from "react"; import classNames from "classnames"; -import Card from "@/views/common/Card"; +import Card from "~src/components/Card"; import "./TimelinePostCard.css"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx index ad5465c1..6c0d7387 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx @@ -2,15 +2,15 @@ import * as React from "react"; import classnames from "classnames"; import { marked } from "marked"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import { HttpNetworkError } from "@/http/common"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { HttpNetworkError } from "~src/http/common"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import { useUser } from "@/services/user"; +import { useUser } from "~src/services/user"; -import Skeleton from "@/views/common/Skeleton"; -import LoadFailReload from "@/views/common/LoadFailReload"; +import Skeleton from "~src/components/Skeleton"; +import LoadFailReload from "~src/components/LoadFailReload"; const TextView: React.FC = (props) => { const { post, className, style } = props; diff --git a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx index 572a9119..3c41228a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx @@ -1,26 +1,26 @@ import { useState, useEffect, ChangeEventHandler } from "react"; import { useTranslation } from "react-i18next"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; import { getHttpTimelineClient, HttpTimelineInfo, HttpTimelinePostInfo, HttpTimelinePostPostRequestData, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; -import base64 from "@/utilities/base64"; +import base64 from "~src/utilities/base64"; -import BlobImage from "@/views/common/BlobImage"; -import LoadingButton from "@/views/common/button/LoadingButton"; -import PopupMenu from "@/views/common/menu/PopupMenu"; +import BlobImage from "~src/components/BlobImage"; +import LoadingButton from "~src/components/button/LoadingButton"; +import PopupMenu from "~src/components/menu/PopupMenu"; import MarkdownPostEdit from "./MarkdownPostEdit"; import TimelinePostCard from "./TimelinePostCard"; import TimelinePostContainer from "./TimelinePostContainer"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import "./TimelinePostCreateView.css"; import classNames from "classnames"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx index a3501b33..7912260a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostList.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -1,6 +1,6 @@ import { useMemo, Fragment } from "react"; -import { HttpTimelinePostInfo } from "@/http/timeline"; +import { HttpTimelinePostInfo } from "~src/http/timeline"; import TimelinePostView from "./TimelinePostView"; import TimelineDateLabel from "./TimelineDateLabel"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx index afae5033..2a8c5947 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx @@ -1,17 +1,17 @@ import { useState } from "react"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; -import { useClickOutside } from "@/utilities/hooks"; +import { useClickOutside } from "~src/utilities/hooks"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import { useDialog } from "@/views/common/dialog"; -import FlatButton from "@/views/common/button/FlatButton"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; +import UserAvatar from "~src/components/user/UserAvatar"; +import { useDialog } from "~src/components/dialog"; +import FlatButton from "~src/components/button/FlatButton"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; import TimelinePostContentView from "./TimelinePostContentView"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import TimelinePostContainer from "./TimelinePostContainer"; import TimelinePostCard from "./TimelinePostCard"; diff --git a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx index b57135bb..afd83a5f 100644 --- a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx @@ -6,9 +6,9 @@ import { HttpTimelinePatchRequest, kTimelineVisibilities, TimelineVisibility, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; export interface TimelinePropertyChangeDialogProps { open: boolean; @@ -68,13 +68,13 @@ const TimelinePropertyChangeDialog: React.FC< onProcess={({ title, visibility, description }) => { const req: HttpTimelinePatchRequest = {}; if (title !== timeline.title) { - req.title = title as string; + req.title = title; } if (visibility !== timeline.visibility) { - req.visibility = visibility as TimelineVisibility; + req.visibility = visibility; } if (description !== timeline.description) { - req.description = description as string; + req.description = description; } return getHttpTimelineClient() .patchTimeline(timeline.owner.username, timeline.nameV2, req) diff --git a/FrontEnd/src/pages/timeline/index.tsx b/FrontEnd/src/pages/timeline/index.tsx index 51cc37f0..6cd1ded0 100644 --- a/FrontEnd/src/pages/timeline/index.tsx +++ b/FrontEnd/src/pages/timeline/index.tsx @@ -1,6 +1,6 @@ import { useParams } from "react-router-dom"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; import Timeline from "./Timeline"; diff --git a/FrontEnd/src/services/TimelinePostBuilder.ts b/FrontEnd/src/services/TimelinePostBuilder.ts index 0440e136..919d4f55 100644 --- a/FrontEnd/src/services/TimelinePostBuilder.ts +++ b/FrontEnd/src/services/TimelinePostBuilder.ts @@ -1,10 +1,10 @@ import { marked } from "marked"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import base64 from "@/utilities/base64"; +import base64 from "~src/utilities/base64"; -import { HttpTimelinePostPostRequest } from "@/http/timeline"; +import { HttpTimelinePostPostRequest } from "~src/http/timeline"; class TimelinePostMarkedRenderer extends marked.Renderer { constructor(private _images: { file: File; url: string }[]) { diff --git a/FrontEnd/src/services/alert.ts b/FrontEnd/src/services/alert.ts index 2f66dccc..241deab1 100644 --- a/FrontEnd/src/services/alert.ts +++ b/FrontEnd/src/services/alert.ts @@ -1,7 +1,7 @@ import pull from "lodash/pull"; -import { I18nText } from "@/common"; -import { ThemeColor } from "@/views/common/common"; +import { I18nText } from "~src/common"; +import { ThemeColor } from "~src/views/common/common"; export interface AlertInfo { type?: ThemeColor; diff --git a/FrontEnd/src/services/timeline.ts b/FrontEnd/src/services/timeline.ts index 342f803a..41a7bff0 100644 --- a/FrontEnd/src/services/timeline.ts +++ b/FrontEnd/src/services/timeline.ts @@ -13,8 +13,8 @@ import { HubConnectionState, } from "@microsoft/signalr"; -import { TimelineVisibility } from "@/http/timeline"; -import { token$ } from "@/http/common"; +import { TimelineVisibility } from "~src/http/timeline"; +import { token$ } from "~src/http/common"; // cSpell:ignore onreconnected onreconnecting diff --git a/FrontEnd/src/services/user.ts b/FrontEnd/src/services/user.ts index c89ca893..ddba4dab 100644 --- a/FrontEnd/src/services/user.ts +++ b/FrontEnd/src/services/user.ts @@ -2,11 +2,11 @@ import { useState, useEffect } from "react"; import { BehaviorSubject, Observable } from "rxjs"; import { AxiosError } from "axios"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import { setHttpToken, axios, HttpBadRequestError } from "@/http/common"; -import { getHttpTokenClient } from "@/http/token"; -import { getHttpUserClient, HttpUser, UserPermission } from "@/http/user"; +import { setHttpToken, axios, HttpBadRequestError } from "~src/http/common"; +import { getHttpTokenClient } from "~src/http/token"; +import { getHttpUserClient, HttpUser, UserPermission } from "~src/http/user"; import { pushAlert } from "./alert"; diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css deleted file mode 100644 index a0d975b5..00000000 --- a/FrontEnd/src/views/common/AppBar.css +++ /dev/null @@ -1,87 +0,0 @@ -.app-bar { - height: 56px; - position: fixed; - z-index: 1030; - top: 0; - left: 0; - right: 0; - background-color: var(--cru-primary-color); -} - -.app-bar { - display: flex; -} - -.app-bar .app-bar-brand { - display: flex; - align-items: center; -} - -.app-bar .app-bar-brand-icon { - height: 2em; -} - -.app-bar .app-bar-user-area { - display: flex; - margin-left: auto; -} - -.app-bar a { - background-color: var(--cru-primary-color); - color: var(--cru-push-button-text-color); - text-decoration: none; - display: flex; - align-items: center; - padding: 0 1em; - transition: all 0.5s; -} - -.app-bar a:hover { - background-color: var(--cru-clickable-primary-hover-color); -} - -.app-bar a:focus { - background-color: var(--cru-clickable-primary-focus-color); -} - -.app-bar a:active { - background-color: var(--cru-clickable-primary-active-color); -} - -/* the current page */ -.app-bar a.active { - background-color: var(--cru-clickable-primary-focus-color); -} - -.app-bar .app-bar-avatar img { - width: 45px; - height: 45px; - background-color: white; - border-radius: 50%; -} - -.app-bar.desktop .app-bar-link-area { - display: flex; -} - -.app-bar.mobile .app-bar-link-area { - position: absolute; - z-index: -1; - top: 56px; - left: 0; - right: 0; - transition: transform 0.5s; -} - -.app-bar.mobile a { - height: 56px; -} - -.app-bar.mobile.collapse .app-bar-link-area { - transform: translateY(-100%); -} - -.app-bar .toggler { - font-size: 2em; - margin-right: 0.5em; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx deleted file mode 100644 index b9ea825b..00000000 --- a/FrontEnd/src/views/common/AppBar.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useState } from "react"; -import classnames from "classnames"; -import { Link, NavLink } from "react-router-dom"; - -import { I18nText, useC, useMobile } from "./common"; -import { useUser } from "@/services/user"; - -import TimelineLogo from "./TimelineLogo"; -import { IconButton } from "./button"; -import UserAvatar from "./user/UserAvatar"; - -import "./AppBar.css"; - -function AppBarNavLink({ - link, - className, - label, - onClick, - children, -}: { - link: string; - className?: string; - label?: I18nText; - onClick?: () => void; - children?: React.ReactNode; -}) { - if (label != null && children != null) { - throw new Error("AppBarNavLink: label and children cannot be both set"); - } - - const c = useC(); - - return ( - classnames(className, isActive && "active")} - onClick={onClick} - > - {children != null ? children : c(label)} - - ); -} - -export default function AppBar() { - const isMobile = useMobile(); - - const [isCollapse, setIsCollapse] = useState(true); - const collapse = isMobile ? () => setIsCollapse(true) : undefined; - const toggleCollapse = () => setIsCollapse(!isCollapse); - - const user = useUser(); - const hasAdministrationPermission = user && user.hasAdministrationPermission; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/BlobImage.tsx b/FrontEnd/src/views/common/BlobImage.tsx deleted file mode 100644 index 259c2210..00000000 --- a/FrontEnd/src/views/common/BlobImage.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ComponentPropsWithoutRef, useState, useEffect } from "react"; - -type BlobImageProps = Omit, "src"> & { - imgRef?: React.Ref; - src?: Blob | string | null; -}; - -export default function BlobImage(props: BlobImageProps) { - const { imgRef, src, ...otherProps } = props; - - const [url, setUrl] = useState(undefined); - - useEffect(() => { - if (src instanceof Blob) { - const url = URL.createObjectURL(src); - setUrl(url); - return () => { - URL.revokeObjectURL(url); - }; - } else { - setUrl(src); - } - }, [src]); - - return ; -} diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css deleted file mode 100644 index 6d655eb9..00000000 --- a/FrontEnd/src/views/common/Card.css +++ /dev/null @@ -1,20 +0,0 @@ -.cru-card { - border-radius: var(--cru-card-border-radius); - transition: all 0.3s; -} - -.cru-card-background-none { - background-color: transparent; -} - -.cru-card-background-solid { - background-color: var(--cru-background-color); -} - -.cru-card-background-grayscale { - background-color: var(--cru-container-background-color); -} - -.cru-card-border-color { - border: 2px solid var(--cru-card-border-color); -} diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx deleted file mode 100644 index a8f0d3cc..00000000 --- a/FrontEnd/src/views/common/Card.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "./common"; -import "./Card.css"; - -interface CardProps extends ComponentPropsWithoutRef<"div"> { - containerRef?: Ref; - color?: ThemeColor; - border?: "color" | "none"; - background?: "color" | "solid" | "grayscale" | "none"; -} - -export default function Card({ - color, - background, - border, - className, - children, - containerRef, - ...otherProps -}: CardProps) { - return ( -
- {children} -
- ); -} diff --git a/FrontEnd/src/views/common/Icon.css b/FrontEnd/src/views/common/Icon.css deleted file mode 100644 index fe980d7b..00000000 --- a/FrontEnd/src/views/common/Icon.css +++ /dev/null @@ -1,3 +0,0 @@ -.cru-icon { - font-size: 1.4rem; -} diff --git a/FrontEnd/src/views/common/Icon.tsx b/FrontEnd/src/views/common/Icon.tsx deleted file mode 100644 index 2ac3a7ca..00000000 --- a/FrontEnd/src/views/common/Icon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ComponentPropsWithoutRef } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "./common"; - -import "./Icon.css"; - -interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { - icon: string; - color?: ThemeColor | "on-surface"; - size?: string | number; -} - -export default function Icon(props: IconButtonProps) { - const { icon, color, size, style, className, ...otherProps } = props; - - const colorName = color === "on-surface" ? "surface-on" : color; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/ImageCropper.css b/FrontEnd/src/views/common/ImageCropper.css deleted file mode 100644 index 2c4d0a8c..00000000 --- a/FrontEnd/src/views/common/ImageCropper.css +++ /dev/null @@ -1,38 +0,0 @@ -.image-cropper-container { - position: relative; - box-sizing: border-box; - user-select: none; -} - -.image-cropper-container img { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; -} - -.image-cropper-mask-container { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - overflow: hidden; -} - -.image-cropper-mask { - position: absolute; - box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8); - touch-action: none; -} - -.image-cropper-handler { - position: absolute; - width: 26px; - height: 26px; - border: black solid 2px; - border-radius: 50%; - background: white; - touch-action: none; -} diff --git a/FrontEnd/src/views/common/ImageCropper.tsx b/FrontEnd/src/views/common/ImageCropper.tsx deleted file mode 100644 index fcab74b0..00000000 --- a/FrontEnd/src/views/common/ImageCropper.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; - -import { UiLogicError } from "@/common"; - -import "./ImageCropper.css"; -import BlobImage from "./BlobImage"; - -export interface Clip { - left: number; - top: number; - width: number; -} - -interface NormailizedClip extends Clip { - height: number; -} - -interface ImageInfo { - width: number; - height: number; - landscape: boolean; - ratio: number; - maxClipWidth: number; - maxClipHeight: number; -} - -interface ImageCropperSavedState { - clip: NormailizedClip; - x: number; - y: number; - pointerId: number; -} - -export interface ImageCropperProps { - clip: Clip | null; - image: string | Blob; - onChange: (clip: Clip) => void; - imageElementCallback?: (element: HTMLImageElement | null) => void; - className?: string; -} - -const ImageCropper = (props: ImageCropperProps): React.ReactElement => { - const { clip, image, onChange, imageElementCallback, className } = props; - - const [oldState, setOldState] = React.useState( - null, - ); - const [imageInfo, setImageInfo] = React.useState(null); - - const normalizeClip = (c: Clip | null | undefined): NormailizedClip => { - if (c == null) { - return { left: 0, top: 0, width: 0, height: 0 }; - } - - return { - left: c.left || 0, - top: c.top || 0, - width: c.width || 0, - height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0, - }; - }; - - const c = normalizeClip(clip); - - const imgElementRef = React.useRef(null); - - const onImageRef = React.useCallback( - (e: HTMLImageElement | null) => { - imgElementRef.current = e; - if (imageElementCallback != null && e == null) { - imageElementCallback(null); - } - }, - [imageElementCallback], - ); - - const onImageLoad = React.useCallback( - (e: React.SyntheticEvent) => { - const img = e.currentTarget; - const landscape = img.naturalWidth >= img.naturalHeight; - - const info = { - width: img.naturalWidth, - height: img.naturalHeight, - landscape, - ratio: img.naturalHeight / img.naturalWidth, - maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1, - maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight, - }; - setImageInfo(info); - onChange({ left: 0, top: 0, width: info.maxClipWidth }); - if (imageElementCallback != null) { - imageElementCallback(img); - } - }, - [onChange, imageElementCallback], - ); - - const onPointerDown = React.useCallback( - (e: React.PointerEvent) => { - if (oldState != null) return; - e.currentTarget.setPointerCapture(e.pointerId); - setOldState({ - x: e.clientX, - y: e.clientY, - clip: c, - pointerId: e.pointerId, - }); - }, - [oldState, c], - ); - - const onPointerUp = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null || oldState.pointerId !== e.pointerId) return; - e.currentTarget.releasePointerCapture(e.pointerId); - setOldState(null); - }, - [oldState], - ); - - const onPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.y / imgElement.height, - }; - - const newRatio = { - x: oldClip.left + moveRatio.x, - y: oldClip.top + moveRatio.y, - }; - if (newRatio.x < 0) { - newRatio.x = 0; - } else if (newRatio.x > 1 - oldClip.width) { - newRatio.x = 1 - oldClip.width; - } - if (newRatio.y < 0) { - newRatio.y = 0; - } else if (newRatio.y > 1 - oldClip.height) { - newRatio.y = 1 - oldClip.height; - } - - onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width }); - }, - [oldState, onChange], - ); - - const onHandlerPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const ratio = imageInfo == null ? 1 : imageInfo.ratio; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.x / imgElement.width / ratio, - }; - - const newRatio = { - x: oldClip.width + moveRatio.x, - y: oldClip.height + moveRatio.y, - }; - - const maxRatio = { - x: Math.min(1 - oldClip.left, newRatio.x), - y: Math.min(1 - oldClip.top, newRatio.y), - }; - - const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio); - - let newWidth; - if (newRatio.x < 0) { - newWidth = 0; - } else if (newRatio.x > maxWidthRatio) { - newWidth = maxWidthRatio; - } else { - newWidth = newRatio.x; - } - - onChange({ left: oldClip.left, top: oldClip.top, width: newWidth }); - }, - [imageInfo, oldState, onChange], - ); - - const toPercentage = (n: number): string => `${n}%`; - - // fuck!!! I just can't find a better way to implement this in pure css - const containerStyle: React.CSSProperties = (() => { - if (imageInfo == null) { - return { width: "100%", paddingTop: "100%", height: 0 }; - } else { - if (imageInfo.ratio > 1) { - return { - width: toPercentage(100 / imageInfo.ratio), - paddingTop: "100%", - height: 0, - }; - } else { - return { - width: "100%", - paddingTop: toPercentage(100 * imageInfo.ratio), - height: 0, - }; - } - } - })(); - - return ( -
- -
-
-
-
-
- ); -}; - -export default ImageCropper; - -export function applyClipToImage( - image: HTMLImageElement, - clip: Clip, - mimeType: string, -): Promise { - return new Promise((resolve, reject) => { - const naturalSize = { - width: image.naturalWidth, - height: image.naturalHeight, - }; - const clipArea = { - x: naturalSize.width * clip.left, - y: naturalSize.height * clip.top, - length: naturalSize.width * clip.width, - }; - - const canvas = document.createElement("canvas"); - canvas.width = clipArea.length; - canvas.height = clipArea.length; - const context = canvas.getContext("2d"); - - if (context == null) throw new Error("Failed to create context."); - - context.drawImage( - image, - clipArea.x, - clipArea.y, - clipArea.length, - clipArea.length, - 0, - 0, - clipArea.length, - clipArea.length, - ); - - canvas.toBlob((blob) => { - if (blob == null) { - reject(new Error("canvas.toBlob returns null")); - } else { - resolve(blob); - } - }, mimeType); - }); -} diff --git a/FrontEnd/src/views/common/LoadFailReload.tsx b/FrontEnd/src/views/common/LoadFailReload.tsx deleted file mode 100644 index 81ba1f67..00000000 --- a/FrontEnd/src/views/common/LoadFailReload.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from "react"; -import { Trans } from "react-i18next"; - -export interface LoadFailReloadProps { - className?: string; - style?: React.CSSProperties; - onReload: () => void; -} - -const LoadFailReload: React.FC = ({ - onReload, - className, - style, -}) => { - return ( - - 0 - { - onReload(); - e.preventDefault(); - }} - > - 1 - - 2 - - ); -}; - -export default LoadFailReload; diff --git a/FrontEnd/src/views/common/LoadingPage.tsx b/FrontEnd/src/views/common/LoadingPage.tsx deleted file mode 100644 index 35ee1aa8..00000000 --- a/FrontEnd/src/views/common/LoadingPage.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from "react"; - -import Spinner from "./Spinner"; - -const LoadingPage: React.FC = () => { - return ( -
- -
- ); -}; - -export default LoadingPage; diff --git a/FrontEnd/src/views/common/Page.tsx b/FrontEnd/src/views/common/Page.tsx deleted file mode 100644 index 86fdb2f5..00000000 --- a/FrontEnd/src/views/common/Page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -interface PageProps extends ComponentPropsWithoutRef<"div"> { - noTopPadding?: boolean; - pageRef?: Ref; -} - -export default function Page({ noTopPadding, pageRef, className, children }: PageProps) { - return ( -
- {children} -
- ); -} diff --git a/FrontEnd/src/views/common/SearchInput.css b/FrontEnd/src/views/common/SearchInput.css deleted file mode 100644 index f0503016..00000000 --- a/FrontEnd/src/views/common/SearchInput.css +++ /dev/null @@ -1,8 +0,0 @@ -.cru-search-input { - display: flex; - flex-wrap: wrap; -} - -.cru-search-input-input { - width: 100%; -} diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx deleted file mode 100644 index e3216b86..00000000 --- a/FrontEnd/src/views/common/SearchInput.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import classNames from "classnames"; - -import { useC, Text } from "./common"; -import LoadingButton from "./button/LoadingButton"; - -import "./SearchInput.css"; - -interface SearchInputProps { - value: string; - onChange: (value: string) => void; - onButtonClick: () => void; - loading?: boolean; - className?: string; - buttonText?: Text; -} - -export default function SearchInput({ - value, - onChange, - onButtonClick, - loading, - className, - buttonText, -}: SearchInputProps) { - const c = useC(); - - return ( -
- { - const { value } = event.currentTarget; - onChange(value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - onButtonClick(); - event.preventDefault(); - } - }} - /> - - - {c(buttonText ?? "search")} - -
- ); -} diff --git a/FrontEnd/src/views/common/Skeleton.css b/FrontEnd/src/views/common/Skeleton.css deleted file mode 100644 index a571eead..00000000 --- a/FrontEnd/src/views/common/Skeleton.css +++ /dev/null @@ -1,14 +0,0 @@ -.cru-skeleton { - padding: 0 1em; -} - -.cru-skeleton-line { - height: 1em; - background-color: hsl(0, 0%, 90%); - margin: 0.7em 0; - border-radius: 0.2em; -} - -.cru-skeleton-line.last { - width: 50%; -} diff --git a/FrontEnd/src/views/common/Skeleton.tsx b/FrontEnd/src/views/common/Skeleton.tsx deleted file mode 100644 index 3b149db9..00000000 --- a/FrontEnd/src/views/common/Skeleton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; -import range from "lodash/range"; - -import "./Skeleton.css"; - -export interface SkeletonProps { - lineNumber?: number; - className?: string; - style?: React.CSSProperties; -} - -const Skeleton: React.FC = (props) => { - const { lineNumber: lineNumberProps, className, style } = props; - const lineNumber = lineNumberProps ?? 3; - - return ( -
- {range(lineNumber).map((i) => ( -
- ))} -
- ); -}; - -export default Skeleton; diff --git a/FrontEnd/src/views/common/Spinner.css b/FrontEnd/src/views/common/Spinner.css deleted file mode 100644 index a1de68d2..00000000 --- a/FrontEnd/src/views/common/Spinner.css +++ /dev/null @@ -1,13 +0,0 @@ -@keyframes cru-spinner-animation { - from { - transform: scale(0,0); - } -} - -.cru-spinner { - display: inline-block; - animation: cru-spinner-animation 0.5s infinite alternate; - background-color: currentColor; - border-radius: 50%; - transform-origin: center; -} diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx deleted file mode 100644 index ec0c2c35..00000000 --- a/FrontEnd/src/views/common/Spinner.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { CSSProperties } from "react"; -import classnames from "classnames"; - -import { ThemeColor } from "./common"; - -import "./Spinner.css"; - -export interface SpinnerProps { - size?: "sm" | "md" | "lg" | number | string; - color?: ThemeColor; - className?: string; - style?: CSSProperties; -} - -export default function Spinner(props: SpinnerProps) { - const { size, color, className, style } = props; - const calculatedSize = - size === "sm" - ? "18px" - : size === "md" - ? "30px" - : size === "lg" - ? "42px" - : typeof size === "number" - ? size - : size == null - ? "20px" - : size; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/TimelineLogo.tsx b/FrontEnd/src/views/common/TimelineLogo.tsx deleted file mode 100644 index e06ed0f5..00000000 --- a/FrontEnd/src/views/common/TimelineLogo.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { SVGAttributes } from "react"; -import * as React from "react"; - -export interface TimelineLogoProps extends SVGAttributes { - color?: string; -} - -const TimelineLogo: React.FC = (props) => { - const { color, ...forwardProps } = props; - const coercedColor = color ?? "currentcolor"; - return ( - - - - - - ); -}; - -export default TimelineLogo; diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx deleted file mode 100644 index 42074781..00000000 --- a/FrontEnd/src/views/common/alert/AlertHost.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as React from "react"; -import without from "lodash/without"; -import { useTranslation } from "react-i18next"; -import classNames from "classnames"; - -import { alertService, AlertInfoEx, AlertInfo } from "@/services/alert"; -import { convertI18nText } from "@/common"; - -import IconButton from "../button/IconButton"; - -import "./alert.css"; - -interface AutoCloseAlertProps { - alert: AlertInfo; - close: () => void; -} - -export const AutoCloseAlert: React.FC = (props) => { - const { alert, close } = props; - const { dismissTime } = alert; - - const { t } = useTranslation(); - - const timerTag = React.useRef(null); - const closeHandler = React.useRef<(() => void) | null>(null); - - React.useEffect(() => { - closeHandler.current = close; - }, [close]); - - React.useEffect(() => { - const tag = - dismissTime === "never" - ? null - : typeof dismissTime === "number" - ? window.setTimeout(() => closeHandler.current?.(), dismissTime) - : window.setTimeout(() => closeHandler.current?.(), 5000); - timerTag.current = tag; - return () => { - if (tag != null) { - window.clearTimeout(tag); - } - }; - }, [dismissTime]); - - const cancelTimer = (): void => { - const { current: tag } = timerTag; - if (tag != null) { - window.clearTimeout(tag); - } - }; - - return ( -
-
- {(() => { - const { message, customMessage } = alert; - if (customMessage != null) { - return customMessage; - } else { - return convertI18nText(message, t); - } - })()} -
-
- -
-
- ); -}; - -const AlertHost: React.FC = () => { - const [alerts, setAlerts] = React.useState([]); - - React.useEffect(() => { - const consume = (alert: AlertInfoEx): void => { - setAlerts((old) => [...old, alert]); - }; - - alertService.registerConsumer(consume); - return () => { - alertService.unregisterConsumer(consume); - }; - }, []); - - return ( -
- {alerts.map((alert) => { - return ( - { - setAlerts((old) => without(old, alert)); - }} - /> - ); - })} -
- ); -}; - -export default AlertHost; diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css deleted file mode 100644 index 54c2b87f..00000000 --- a/FrontEnd/src/views/common/alert/alert.css +++ /dev/null @@ -1,33 +0,0 @@ -.alert-container { - position: fixed; - z-index: 1040; -} - -.cru-alert { - border-radius: 5px; - border: var(--cru-key-color) 1px solid; - color: var(--cru-key-t-color); - background-color: var(--cru-key-b1-color); - - display: flex; - overflow: hidden; -} - -.cru-alert-content { - padding: 0.5em 2em; -} - -.cru-alert-close-button-container { - flex-shrink: 0; - margin-left: auto; - width: 2em; - text-align: center; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--cru-key-t-color); -} - -.cru-alert-close-button { - color: var(--cru-key-color); -} diff --git a/FrontEnd/src/views/common/breakpoints.ts b/FrontEnd/src/views/common/breakpoints.ts deleted file mode 100644 index fb281610..00000000 --- a/FrontEnd/src/views/common/breakpoints.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const breakpoints = { - sm: 576, -} as const; diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css deleted file mode 100644 index 1da70f0e..00000000 --- a/FrontEnd/src/views/common/button/Button.css +++ /dev/null @@ -1,64 +0,0 @@ -.cru-button { - font-size: 1rem; - padding: 0.4em 0.8em; - transition: all 0.3s; - border-radius: 0.2em; - border: 1px solid; - cursor: pointer; -} - -.cru-button:not(.outline) { - color: var(--cru-push-button-text-color); - background-color: var(--cru-clickable-normal-color); - border-color: var(--cru-clickable-normal-color); -} - -.cru-button:not(.outline):hover { - background-color: var(--cru-clickable-hover-color); - border-color: var(--cru-clickable-hover-color); -} - -.cru-button:not(.outline):focus { - background-color: var(--cru-clickable-focus-color); - border-color: var(--cru-clickable-focus-color); -} - -.cru-button:not(.outline):active { - background-color: var(--cru-clickable-active-color); - border-color: var(--cru-clickable-active-color); -} - -.cru-button:not(.outline):disabled { - color: var(--cru-push-button-disabled-text-color); - background-color: var(--cru-push-button-disabled-color); - border-color: var(--cru-push-button-disabled-color); - cursor: auto; -} - - -.cru-button.outline { - color: var(--cru-clickable-normal-color); - border-color: var(--cru-clickable-normal-color); - background-color: transparent; -} - -.cru-button.outline:hover { - color: var(--cru-clickable-hover-color); - border-color: var(--cru-clickable-hover-color); -} - -.cru-button.outline:focus { - color: var(--cru-clickable-focus-color); - border-color: var(--cru-clickable-focus-color); -} - -.cru-button.outline:active { - color: var(--cru-clickable-active-color); - border-color: var(--cru-clickable-active-color); -} - -.cru-button.outline:disabled { - color: var(--cru-clickable-disabled-color); - border-color: var(--cru-clickable-disabled-color); - cursor: auto; -} diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx deleted file mode 100644 index 6c38e130..00000000 --- a/FrontEnd/src/views/common/button/Button.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { Text, useC, ThemeColor } from "../common"; - -import "./Button.css"; - -interface ButtonProps extends ComponentPropsWithoutRef<"button"> { - color?: ThemeColor; - text?: Text; - outline?: boolean; - buttonRef?: Ref | null; -} - -export default function Button(props: ButtonProps) { - const { - buttonRef, - color, - text, - outline, - className, - children, - ...otherProps - } = props; - - if (text != null && children != null) { - console.warn("You can't set both text and children props."); - } - - const c = useC(); - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/ButtonRow.css b/FrontEnd/src/views/common/button/ButtonRow.css deleted file mode 100644 index e69de29b..00000000 diff --git a/FrontEnd/src/views/common/button/ButtonRow.tsx b/FrontEnd/src/views/common/button/ButtonRow.tsx deleted file mode 100644 index eea60cc4..00000000 --- a/FrontEnd/src/views/common/button/ButtonRow.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; - -import "./ButtonRow.css"; - -type ButtonRowButton = ( - | { - type: "normal"; - props: ComponentPropsWithoutRef; - } - | { - type: "flat"; - props: ComponentPropsWithoutRef; - } - | { - type: "icon"; - props: ComponentPropsWithoutRef; - } - | { type: "loading"; props: ComponentPropsWithoutRef } -) & { key: string | number }; - -interface ButtonRowProps { - className?: string; - containerRef?: Ref; - buttons: ButtonRowButton[]; - buttonsClassName?: string; -} - -export default function ButtonRow({ - className, - containerRef, - buttons, - buttonsClassName, -}: ButtonRowProps) { - return ( -
- {buttons.map((button) => { - const { type, key, props } = button; - const newClassName = classNames(props.className, buttonsClassName); - switch (type) { - case "normal": - return
- ); -} diff --git a/FrontEnd/src/views/common/button/ButtonRowV2.tsx b/FrontEnd/src/views/common/button/ButtonRowV2.tsx deleted file mode 100644 index 3467ad52..00000000 --- a/FrontEnd/src/views/common/button/ButtonRowV2.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; - -import "./ButtonRow.css"; -import { Text, ThemeColor } from "../common"; - -interface ButtonRowV2ButtonBase { - key: string | number; - action?: "primary" | "secondary"; - color?: ThemeColor; - disabled?: boolean; - onClick?: () => void; -} - -interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase { - type?: undefined | null; - text: Text; - outline?: boolean; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase { - type: "normal"; - text: Text; - outline?: boolean; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase { - type: "flat"; - text: Text; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase { - type: "icon"; - icon: string; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase { - type: "loading"; - text: Text; - loading?: boolean; - props?: ComponentPropsWithoutRef; -} - -type ButtonRowV2Button = - | ButtonRowV2ButtonWithNoType - | ButtonRowV2NormalButton - | ButtonRowV2FlatButton - | ButtonRowV2IconButton - | ButtonRowV2LoadingButton; - -interface ButtonRowV2Props { - className?: string; - containerRef?: Ref; - buttons: ButtonRowV2Button[]; - buttonsClassName?: string; -} - -export default function ButtonRowV2({ - className, - containerRef, - buttons, - buttonsClassName, -}: ButtonRowV2Props) { - return ( -
- {buttons.map((button) => { - const { key, action, color, disabled, onClick } = button; - - const realAction = action ?? "primary"; - const realColor = - color ?? (realAction === "primary" ? "primary" : "secondary"); - - const commonProps = { key, color: realColor, disabled, onClick }; - const newClassName = classNames( - button.props?.className, - buttonsClassName, - ); - - switch (button.type) { - case null: - case undefined: - case "normal": { - const { text, outline, props } = button; - return ( -
- ); -} diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css deleted file mode 100644 index 2050946c..00000000 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ /dev/null @@ -1,27 +0,0 @@ -.cru-flat-button { - font-size: 1rem; - padding: 0.4em 0.8em; - transition: all 0.5s; - border-radius: 0.2em; - background-color: var(--cru-clickable-grayscale-normal-color); - border: 1px none; - color: var(--cru-clickable-normal-color); - cursor: pointer; -} - -.cru-flat-button:hover { - background-color: var(--cru-clickable-grayscale-hover-color); -} - -.cru-flat-button:focus { - background-color: var(--cru-clickable-grayscale-focus-color); -} - -.cru-flat-button:active { - background-color: var(--cru-clickable-grayscale-active-color); -} - -.cru-flat-button:disabled { - color: var(--cru-clickable-disabled-color); - cursor: auto; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx deleted file mode 100644 index 9f074dd6..00000000 --- a/FrontEnd/src/views/common/button/FlatButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { Text, useC, ThemeColor } from "../common"; - -import "./FlatButton.css"; - -interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> { - color?: ThemeColor; - text?: Text; - buttonRef?: Ref | null; -} - -export default function FlatButton(props: FlatButtonProps) { - const { color, text, className, children, buttonRef, ...otherProps } = props; - - if (text != null && children != null) { - console.warn("You can't set both text and children props."); - } - - const c = useC(); - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/IconButton.css b/FrontEnd/src/views/common/button/IconButton.css deleted file mode 100644 index a3747201..00000000 --- a/FrontEnd/src/views/common/button/IconButton.css +++ /dev/null @@ -1,30 +0,0 @@ -.cru-icon-button { - color: var(--cru-clickable-normal-color); - font-size: 1.4rem; - background: none; - border: none; - transition: all 0.5s; - cursor: pointer; - user-select: none; -} - -.cru-icon-button:hover { - color: var(--cru-clickable-hover-color); -} - -.cru-icon-button:focus { - color: var(--cru-clickable-focus-color); -} - -.cru-icon-button:active { - color: var(--cru-clickable-active-color); -} - -.cru-flat-button:disabled { - color: var(--cru-clickable-disabled-color); - cursor: auto; -} - -.cru-icon-button.large { - font-size: 1.6rem; -} diff --git a/FrontEnd/src/views/common/button/IconButton.tsx b/FrontEnd/src/views/common/button/IconButton.tsx deleted file mode 100644 index 95c58887..00000000 --- a/FrontEnd/src/views/common/button/IconButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ComponentPropsWithoutRef } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "../common"; - -import "./IconButton.css"; - -interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { - icon: string; - color?: ThemeColor | "grayscale"; - large?: boolean; - disabled?: boolean; // TODO: Not implemented -} - -export default function IconButton(props: IconButtonProps) { - const { icon, color, className, large, ...otherProps } = props; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/index.tsx b/FrontEnd/src/views/common/button/index.tsx deleted file mode 100644 index b5aa5470..00000000 --- a/FrontEnd/src/views/common/button/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; -import ButtonRow from "./ButtonRow"; -import ButtonRowV2 from "./ButtonRowV2"; - -export { - Button, - FlatButton, - IconButton, - LoadingButton, - ButtonRow, - ButtonRowV2, -}; diff --git a/FrontEnd/src/views/common/common.ts b/FrontEnd/src/views/common/common.ts deleted file mode 100644 index 7af2643b..00000000 --- a/FrontEnd/src/views/common/common.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type { Text, I18nText } from "@/common"; -export { c, convertI18nText, useC } from "@/common"; - -export const themeColors = [ - "primary", - "secondary", - "danger", - "create", -] as const; - -export type ThemeColor = (typeof themeColors)[number]; - -export { breakpoints } from "./breakpoints"; -export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.css b/FrontEnd/src/views/common/dialog/ConfirmDialog.css deleted file mode 100644 index e69de29b..00000000 diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx deleted file mode 100644 index dbbd15c6..00000000 --- a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useC, Text, ThemeColor } from "@/views/common/common"; - -import Dialog from "./Dialog"; -import DialogContainer from "./DialogContainer"; - -export default function ConfirmDialog({ - open, - onClose, - onConfirm, - title, - body, - color, - bodyColor, -}: { - open: boolean; - onClose: () => void; - onConfirm: () => void; - title: Text; - body: Text; - color?: ThemeColor; - bodyColor?: ThemeColor; -}) { - const c = useC(); - - return ( - - { - onConfirm(); - onClose(); - }, - }, - }, - ]} - > -
{c(body)}
-
-
- ); -} diff --git a/FrontEnd/src/views/common/dialog/Dialog.css b/FrontEnd/src/views/common/dialog/Dialog.css deleted file mode 100644 index e4c61440..00000000 --- a/FrontEnd/src/views/common/dialog/Dialog.css +++ /dev/null @@ -1,60 +0,0 @@ -.cru-dialog-overlay { - position: fixed; - z-index: 1040; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - overflow: auto; -} - -.cru-dialog-background { - position: absolute; - z-index: -1; - left: 0; - right: 0; - top: 0; - bottom: 0; - background-color: var(--cru-surface-dim-color); - opacity: 0.8; -} - -.cru-dialog-container { - max-width: 100%; - min-width: 30vw; - - margin: 2em auto; - - border: var(--cru-key-container-color) 1px solid; - border-radius: 5px; - padding: 1.5em; - background-color: var(--cru-surface-color); -} - -@media (min-width: 576px) { - .cru-dialog-container { - max-width: 800px; - } -} - -.cru-dialog-enter .cru-dialog-container { - transform: scale(0, 0); - opacity: 0; - transform-origin: center; -} - -.cru-dialog-enter-active .cru-dialog-container { - transform: scale(1, 1); - opacity: 1; - transition: transform 0.3s, opacity 0.3s; - transform-origin: center; -} - -.cru-dialog-exit-active .cru-dialog-container { - transition: transform 0.3s, opacity 0.3s; - transform: scale(0, 0); - opacity: 0; - transform-origin: center; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/dialog/Dialog.tsx b/FrontEnd/src/views/common/dialog/Dialog.tsx deleted file mode 100644 index 2ff7bea8..00000000 --- a/FrontEnd/src/views/common/dialog/Dialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { ReactNode, useRef } from "react"; -import ReactDOM from "react-dom"; -import { CSSTransition } from "react-transition-group"; -import classNames from "classnames"; - -import { ThemeColor } from "../common"; - -import "./Dialog.css"; - -const optionalPortalElement = document.getElementById("portal"); -if (optionalPortalElement == null) { - throw new Error("Portal element not found"); -} -const portalElement = optionalPortalElement; - -interface DialogProps { - open: boolean; - onClose: () => void; - color?: ThemeColor; - children?: ReactNode; - disableCloseOnClickOnOverlay?: boolean; -} - -export default function Dialog({ - open, - onClose, - color, - children, - disableCloseOnClickOnOverlay, -}: DialogProps) { - color = color ?? "primary"; - - const nodeRef = useRef(null); - - return ReactDOM.createPortal( - -
-
{ - onClose(); - } - } - /> -
{children}
-
- , - portalElement, - ); -} diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.css b/FrontEnd/src/views/common/dialog/DialogContainer.css deleted file mode 100644 index fbb18e0d..00000000 --- a/FrontEnd/src/views/common/dialog/DialogContainer.css +++ /dev/null @@ -1,20 +0,0 @@ -.cru-dialog-container-title { - font-size: 1.2em; - font-weight: bold; - color: var(--cru-key-color); - margin-bottom: 0.5em; -} - - -.cru-dialog-container-hr { - margin: 1em 0; -} - -.cru-dialog-container-button-row { - display: flex; - justify-content: flex-end; -} - -.cru-dialog-container-button { - margin-left: 1em; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.tsx b/FrontEnd/src/views/common/dialog/DialogContainer.tsx deleted file mode 100644 index afee2669..00000000 --- a/FrontEnd/src/views/common/dialog/DialogContainer.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ComponentProps, Ref, ReactNode } from "react"; -import classNames from "classnames"; - -import { ThemeColor, Text, useC } from "../common"; -import { ButtonRow, ButtonRowV2 } from "../button"; - -import "./DialogContainer.css"; - -interface DialogContainerBaseProps { - className?: string; - title: Text; - titleColor?: ThemeColor; - titleClassName?: string; - titleRef?: Ref; - bodyContainerClassName?: string; - bodyContainerRef?: Ref; - buttonsClassName?: string; - buttonsContainerRef?: ComponentProps["containerRef"]; - children: ReactNode; -} - -interface DialogContainerWithButtonsProps extends DialogContainerBaseProps { - buttons: ComponentProps["buttons"]; -} - -interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps { - buttonsV2: ComponentProps["buttons"]; -} - -type DialogContainerProps = - | DialogContainerWithButtonsProps - | DialogContainerWithButtonsV2Props; - -export default function DialogContainer(props: DialogContainerProps) { - const { - className, - title, - titleColor, - titleClassName, - titleRef, - bodyContainerClassName, - bodyContainerRef, - buttonsClassName, - buttonsContainerRef, - children, - } = props; - - const c = useC(); - - return ( -
-
- {c(title)} -
-
-
- {children} -
-
- {"buttons" in props ? ( - - ) : ( - - )} -
- ); -} diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.css b/FrontEnd/src/views/common/dialog/FullPageDialog.css deleted file mode 100644 index 2f1fc636..00000000 --- a/FrontEnd/src/views/common/dialog/FullPageDialog.css +++ /dev/null @@ -1,44 +0,0 @@ -.cru-full-page { - position: fixed; - z-index: 1030; - left: 0; - top: 0; - right: 0; - bottom: 0; - background-color: white; - padding-top: 56px; -} - -.cru-full-page-top-bar { - height: 56px; - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: 1; - background-color: var(--cru-primary-color); - display: flex; - align-items: center; -} - -.cru-full-page-content-container { - overflow: scroll; -} - -.cru-full-page-back-button { - color: var(--cru-primary-t-color); -} - -.cru-full-page-enter { - transform: translate(100%, 0); -} - -.cru-full-page-enter-active { - transform: none; - transition: transform 0.3s; -} - -.cru-full-page-exit-active { - transition: transform 0.3s; - transform: translate(100%, 0); -} diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx b/FrontEnd/src/views/common/dialog/FullPageDialog.tsx deleted file mode 100644 index 6368fc0a..00000000 --- a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react"; -import { createPortal } from "react-dom"; -import classnames from "classnames"; -import { CSSTransition } from "react-transition-group"; - -import "./FullPageDialog.css"; -import IconButton from "../button/IconButton"; - -export interface FullPageDialogProps { - show: boolean; - onBack: () => void; - contentContainerClassName?: string; - children: React.ReactNode; -} - -const FullPageDialog: React.FC = ({ - show, - onBack, - children, - contentContainerClassName, -}) => { - return createPortal( - -
-
- -
-
- {children} -
-
-
, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")! - ); -}; - -export default FullPageDialog; diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.css b/FrontEnd/src/views/common/dialog/OperationDialog.css deleted file mode 100644 index f4b7237e..00000000 --- a/FrontEnd/src/views/common/dialog/OperationDialog.css +++ /dev/null @@ -1,8 +0,0 @@ -.cru-operation-dialog-prompt { - color: var(--cru-surface-on-color); -} - -.cru-operation-dialog-input-group { - display: block; - margin: 0.5em 0; -} diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx deleted file mode 100644 index 4335b2b0..00000000 --- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { useState, ReactNode, ComponentProps } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import { - useInputs, - InputGroup, - Initializer as InputInitializer, - InputValueDict, - InputErrorDict, -} from "../input/InputGroup"; -import Dialog from "./Dialog"; -import DialogContainer from "./DialogContainer"; - -import "./OperationDialog.css"; - -export type { InputInitializer, InputValueDict, InputErrorDict }; - -interface OperationDialogPromptProps { - message?: Text; - customMessage?: Text; - customMessageNode?: ReactNode; - className?: string; -} - -function OperationDialogPrompt(props: OperationDialogPromptProps) { - const { message, customMessage, customMessageNode, className } = props; - - const c = useC(); - - return ( -
- {message &&

{c(message)}

} - {customMessageNode ?? (customMessage != null ? c(customMessage) : null)} -
- ); -} - -export interface OperationDialogProps { - open: boolean; - onClose: () => void; - - color?: ThemeColor; - inputColor?: ThemeColor; - title: Text; - inputPrompt?: Text; - inputPromptNode?: ReactNode; - successPrompt?: (data: TData) => Text; - successPromptNode?: (data: TData) => ReactNode; - failurePrompt?: (error: unknown) => Text; - failurePromptNode?: (error: unknown) => ReactNode; - - inputs: InputInitializer; - - onProcess: (inputs: InputValueDict) => Promise; - onSuccessAndClose?: (data: TData) => void; -} - -function OperationDialog(props: OperationDialogProps) { - const { - open, - onClose, - color, - inputColor, - title, - inputPrompt, - inputPromptNode, - successPrompt, - successPromptNode, - failurePrompt, - failurePromptNode, - inputs, - onProcess, - onSuccessAndClose, - } = props; - - if (process.env.NODE_ENV === "development") { - if (inputPrompt && inputPromptNode) { - console.log("InputPrompt and inputPromptNode are both set."); - } - if (successPrompt && successPromptNode) { - console.log("SuccessPrompt and successPromptNode are both set."); - } - if (failurePrompt && failurePromptNode) { - console.log("FailurePrompt and failurePromptNode are both set."); - } - } - - type Step = - | { type: "input" } - | { type: "process" } - | { - type: "success"; - data: TData; - } - | { - type: "failure"; - data: unknown; - }; - - const [step, setStep] = useState({ type: "input" }); - - const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } = - useInputs({ - init: inputs, - }); - - function close() { - if (step.type !== "process") { - onClose(); - if (step.type === "success" && onSuccessAndClose) { - onSuccessAndClose?.(step.data); - } - } else { - console.log("Attempt to close modal dialog when processing."); - } - } - - function onConfirm() { - const result = confirm(); - if (result.type === "ok") { - setStep({ type: "process" }); - setAllDisabled(true); - onProcess(result.values).then( - (d) => { - setStep({ - type: "success", - data: d, - }); - }, - (e: unknown) => { - setStep({ - type: "failure", - data: e, - }); - }, - ); - } - } - - let body: ReactNode; - let buttons: ComponentProps["buttons"]; - - if (step.type === "input" || step.type === "process") { - const isProcessing = step.type === "process"; - - body = ( -
- - -
- ); - buttons = [ - { - key: "cancel", - type: "normal", - props: { - text: "operationDialog.cancel", - color: "secondary", - outline: true, - onClick: close, - disabled: isProcessing, - }, - }, - { - key: "confirm", - type: "loading", - props: { - text: "operationDialog.confirm", - color, - loading: isProcessing, - disabled: hasErrorAndDirty, - onClick: onConfirm, - }, - }, - ]; - } else { - const result = step; - - const promptProps: OperationDialogPromptProps = - result.type === "success" - ? { - message: "operationDialog.success", - customMessage: successPrompt?.(result.data), - customMessageNode: successPromptNode?.(result.data), - } - : { - message: "operationDialog.error", - customMessage: failurePrompt?.(result.data), - customMessageNode: failurePromptNode?.(result.data), - }; - body = ( -
- -
- ); - - buttons = [ - { - key: "ok", - type: "normal", - props: { - text: "operationDialog.ok", - color: "primary", - onClick: close, - }, - }, - ]; - } - - return ( - - - {body} - - - ); -} - -export default OperationDialog; diff --git a/FrontEnd/src/views/common/dialog/index.ts b/FrontEnd/src/views/common/dialog/index.ts deleted file mode 100644 index 59f15791..00000000 --- a/FrontEnd/src/views/common/dialog/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from "react"; - -export { default as Dialog } from "./Dialog"; -export { default as FullPageDialog } from "./FullPageDialog"; -export { default as OperationDialog } from "./OperationDialog"; -export { default as ConfirmDialog } from "./ConfirmDialog"; - -type DialogMap = { - [K in D]: V; -}; - -type DialogKeyMap = DialogMap; - -type DialogPropsMap = DialogMap< - D, - { key: number | string; open: boolean; onClose: () => void } ->; - -export function useDialog( - dialogs: D[], - options?: { - initDialog?: D | null; - onClose?: { - [K in D]?: () => void; - }; - }, -): { - dialog: D | null; - switchDialog: (newDialog: D | null) => void; - dialogPropsMap: DialogPropsMap; - createDialogSwitch: (newDialog: D | null) => () => void; -} { - const [dialog, setDialog] = useState(options?.initDialog ?? null); - - const [dialogKeys, setDialogKeys] = useState>( - () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap, - ); - - const switchDialog = (newDialog: D | null) => { - if (dialog !== null) { - setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 }); - } - setDialog(newDialog); - }; - - return { - dialog, - switchDialog, - dialogPropsMap: Object.fromEntries( - dialogs.map((d) => [ - d, - { - key: `${d}-${dialogKeys[d]}`, - open: dialog === d, - onClose: () => { - switchDialog(null); - options?.onClose?.[d]?.(); - }, - }, - ]), - ) as DialogPropsMap, - createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog), - }; -} diff --git a/FrontEnd/src/views/common/hooks.ts b/FrontEnd/src/views/common/hooks.ts deleted file mode 100644 index c1fa5774..00000000 --- a/FrontEnd/src/views/common/hooks.ts +++ /dev/null @@ -1,14 +0,0 @@ -// TODO: Migrate hooks - -export { - useIsSmallScreen, - useClickOutside, - useScrollToBottom, -} from "@/utilities/hooks"; - -import { useMediaQuery } from "react-responsive"; -import { breakpoints } from "./breakpoints"; - -export function useMobile(): boolean { - return useMediaQuery({ maxWidth: breakpoints.sm }); -} diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css deleted file mode 100644 index a8f5e9a5..00000000 --- a/FrontEnd/src/views/common/index.css +++ /dev/null @@ -1,100 +0,0 @@ -@import "./theme.css"; - -* { - box-sizing: border-box; - margin-inline: 0; - margin-block: 0; -} - -body { - font-family: var(--cru-default-font-family); - background: var(--cru-body-background-color); - color: var(--cru-text-primary-color); - line-height: 1.2; -} - -.cru-page { - padding: var(--cru-page-padding); -} - -.cru-page-no-top-padding { - padding-top: 0; -} - -.cru-text-center { - text-align: center; -} - -.cru-text-end { - text-align: end; -} - -.cru-float-left { - float: left; -} - -.cru-float-right { - float: right; -} - -.cru-align-text-bottom { - vertical-align: text-bottom; -} - -.cru-align-middle { - vertical-align: middle; -} - -.cru-clearfix::after { - clear: both; -} - -.cru-fill-parent { - width: 100%; - height: 100%; -} - -.cru-avatar { - width: 60px; - height: 60px; -} - -.cru-avatar.large { - width: 100px; - height: 100px; -} - -.cru-avatar.small { - width: 40px; - height: 40px; -} - -.cru-round { - border-radius: 50%; -} - -.cru-tab-pages-action-area { - display: flex; - align-items: center; -} - -.alert-container { - position: fixed; - z-index: 1070; -} - -@media (min-width: 576px) { - .alert-container { - bottom: 0; - right: 0; - } -} - -@media (max-width: 575.98px) { - .alert-container { - bottom: 0; - right: 0; - left: 0; - text-align: center; - } -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/input/InputGroup.css b/FrontEnd/src/views/common/input/InputGroup.css deleted file mode 100644 index 7e905b1e..00000000 --- a/FrontEnd/src/views/common/input/InputGroup.css +++ /dev/null @@ -1,54 +0,0 @@ -.cru-input-group { - display: block; -} - -.cru-input-container { - margin: 0.4em 0; -} - -.cru-input-label { - display: block; - color: var(--cru-clickable-normal-color); - font-size: 0.9em; - margin-bottom: 0.3em; -} - -.cru-input-label-inline { - margin-inline-start: 0.5em; -} - -.cru-input-type-text input { - appearance: none; - display: block; - border: 1px solid; - /* color: var(--cru-surface-on-color); */ - /* background-color: var(--cru-surface-color); */ - margin: 0; - font-size: 1em; - padding: 0.2em; -} - -.cru-input-type-text input:hover { - border-color: var(--cru-clickable-hover-color); -} - -.cru-input-type-text input:focus { - border-color: var(--cru-clickable-focus-color); -} - -.cru-input-type-text input:disabled { - border-color: var(--cru-clickable-disabled-color); -} - -.cru-input-error { - display: block; - font-size: 0.8em; - color: var(--cru-danger-color); - margin-top: 0.4em; -} - -.cru-input-helper { - display: block; - font-size: 0.8em; - color: var(--cru-primary-color); -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/input/InputGroup.tsx b/FrontEnd/src/views/common/input/InputGroup.tsx deleted file mode 100644 index d95bb29e..00000000 --- a/FrontEnd/src/views/common/input/InputGroup.tsx +++ /dev/null @@ -1,461 +0,0 @@ -/** - * Some notes for InputGroup: - * This is one of the most complicated components in this project. - * Probably because the feature is complex and involved user inputs. - * - * I hope it contains following features: - * - Input features - * - Supports a wide range of input types. - * - Validator to validate user inputs. - * - Can set initial values. - * - Dirty, aka, has user touched this input. - * - Developer friendly - * - Easy to use APIs. - * - Type check as much as possible. - * - UI - * - Configurable appearance. - * - Can display helper and error messages. - * - Easy to extend, like new input types. - * - * So here is some design decisions: - * Inputs are identified by its _key_. - * `InputGroup` component takes care of only UI and no logic. - * `useInputs` hook takes care of logic and generate props for `InputGroup`. - */ - -import { useState, Ref, useId } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import "./InputGroup.css"; - -export interface InputBase { - key: string; - label: Text; - helper?: Text; - disabled?: boolean; - error?: Text; -} - -export interface TextInput extends InputBase { - type: "text"; - value: string; - password?: boolean; -} - -export interface BoolInput extends InputBase { - type: "bool"; - value: boolean; -} - -export interface SelectInputOption { - value: string; - label: Text; - icon?: string; -} - -export interface SelectInput extends InputBase { - type: "select"; - value: string; - options: SelectInputOption[]; -} - -export type Input = TextInput | BoolInput | SelectInput; - -export type InputValue = Input["value"]; - -export type InputValueDict = Record; -export type InputErrorDict = Record; -export type InputDisabledDict = Record; -export type InputDirtyDict = Record; - -export type GeneralInputErrorDict = - | { - [key: string]: Text | null | undefined; - } - | null - | undefined; - -type MakeInputInfo = Omit; - -export type InputInfo = { - [I in Input as I["type"]]: MakeInputInfo; -}[Input["type"]]; - -export type Validator = ( - values: InputValueDict, - inputs: InputInfo[], -) => GeneralInputErrorDict; - -export type InputScheme = { - inputs: InputInfo[]; - validator?: Validator; -}; - -export type InputData = { - values: InputValueDict; - errors: InputErrorDict; - disabled: InputDisabledDict; - dirties: InputDirtyDict; -}; - -export type State = { - scheme: InputScheme; - data: InputData; -}; - -export type DataInitialization = { - values?: InputValueDict; - errors?: GeneralInputErrorDict; - disabled?: InputDisabledDict; - dirties?: InputDirtyDict; -}; - -export type Initialization = { - scheme: InputScheme; - dataInit?: DataInitialization; -}; - -export type GeneralInitialization = Initialization | InputScheme | InputInfo[]; - -export type Initializer = GeneralInitialization | (() => GeneralInitialization); - -export interface InputGroupProps { - color?: ThemeColor; - containerClassName?: string; - containerRef?: Ref; - - inputs: Input[]; - onChange: (index: number, value: Input["value"]) => void; -} - -function cleanObject(o: Record): Record> { - const result = { ...o }; - for (const key of Object.keys(result)) { - if (result[key] == null) { - delete result[key]; - } - } - return result as never; -} - -export type ConfirmResult = - | { - type: "ok"; - values: InputValueDict; - } - | { - type: "error"; - errors: InputErrorDict; - }; - -function validate( - validator: Validator | null | undefined, - values: InputValueDict, - inputs: InputInfo[], -): InputErrorDict { - return cleanObject(validator?.(values, inputs) ?? {}); -} - -export function useInputs(options: { init: Initializer }): { - inputGroupProps: InputGroupProps; - hasError: boolean; - hasErrorAndDirty: boolean; - confirm: () => ConfirmResult; - setAllDisabled: (disabled: boolean) => void; -} { - function initializeValue( - input: InputInfo, - value?: InputValue | null, - ): InputValue { - if (input.type === "text") { - return value ?? ""; - } else if (input.type === "bool") { - return value ?? false; - } else if (input.type === "select") { - return value ?? input.options[0].value; - } - throw new Error("Unknown input type"); - } - - function initialize(generalInitialization: GeneralInitialization): State { - const initialization: Initialization = Array.isArray(generalInitialization) - ? { scheme: { inputs: generalInitialization } } - : "scheme" in generalInitialization - ? generalInitialization - : { scheme: generalInitialization }; - - const { scheme, dataInit } = initialization; - const { inputs, validator } = scheme; - const keys = inputs.map((input) => input.key); - - if (process.env.NODE_ENV === "development") { - const checkKeys = (dict: Record | undefined) => { - if (dict != null) { - for (const key of Object.keys(dict)) { - if (!keys.includes(key)) { - console.warn(""); - } - } - } - }; - - checkKeys(dataInit?.values); - checkKeys(dataInit?.errors ?? {}); - checkKeys(dataInit?.disabled); - checkKeys(dataInit?.dirties); - } - - function clean( - dict: Record | null | undefined, - ): Record> { - return dict != null ? cleanObject(dict) : {}; - } - - const values: InputValueDict = {}; - const disabled: InputDisabledDict = clean(dataInit?.disabled); - const dirties: InputDirtyDict = clean(dataInit?.dirties); - const isErrorSet = dataInit?.errors != null; - let errors: InputErrorDict = clean(dataInit?.errors); - - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i]; - const { key } = input; - - values[key] = initializeValue(input, dataInit?.values?.[key]); - } - - if (isErrorSet) { - if (process.env.NODE_ENV === "development") { - console.log( - "You explicitly set errors (not undefined) in initializer, so validator won't run.", - ); - } - } else { - errors = validate(validator, values, inputs); - } - - return { - scheme, - data: { - values, - errors, - disabled, - dirties, - }, - }; - } - - const { init } = options; - const initializer = typeof init === "function" ? init : () => init; - - const [state, setState] = useState(() => initialize(initializer())); - - const { scheme, data } = state; - const { validator } = scheme; - - function createAllBooleanDict(value: boolean): Record { - const result: InputDirtyDict = {}; - for (const key of scheme.inputs.map((input) => input.key)) { - result[key] = value; - } - return result; - } - - const createAllDirties = () => createAllBooleanDict(true); - - const componentInputs: Input[] = []; - - for (let i = 0; i < scheme.inputs.length; i++) { - const input = scheme.inputs[i]; - const value = data.values[input.key]; - const error = data.errors[input.key]; - const disabled = data.disabled[input.key] ?? false; - const dirty = data.dirties[input.key] ?? false; - const componentInput: Input = { - ...input, - value: value as never, - disabled, - error: dirty ? error : undefined, - }; - componentInputs.push(componentInput); - } - - const hasError = Object.keys(data.errors).length > 0; - const hasDirty = Object.keys(data.dirties).some((key) => data.dirties[key]); - - return { - inputGroupProps: { - inputs: componentInputs, - onChange: (index, value) => { - const input = scheme.inputs[index]; - const { key } = input; - const newValues = { ...data.values, [key]: value }; - const newDirties = { ...data.dirties, [key]: true }; - const newErrors = validate(validator, newValues, scheme.inputs); - setState({ - scheme, - data: { - ...data, - values: newValues, - errors: newErrors, - dirties: newDirties, - }, - }); - }, - }, - hasError, - hasErrorAndDirty: hasError && hasDirty, - confirm() { - const newDirties = createAllDirties(); - const newErrors = validate(validator, data.values, scheme.inputs); - - setState({ - scheme, - data: { - ...data, - dirties: newDirties, - errors: newErrors, - }, - }); - - if (Object.keys(newErrors).length !== 0) { - return { - type: "error", - errors: newErrors, - }; - } else { - return { - type: "ok", - values: data.values, - }; - } - }, - setAllDisabled(disabled: boolean) { - setState({ - scheme, - data: { - ...data, - disabled: createAllBooleanDict(disabled), - }, - }); - }, - }; -} - -export function InputGroup({ - color, - inputs, - onChange, - containerRef, - containerClassName, -}: InputGroupProps) { - const c = useC(); - - const id = useId(); - - return ( -
- {inputs.map((item, index) => { - const { key, type, value, label, error, helper, disabled } = item; - - const getContainerClassName = ( - ...additionalClassNames: classNames.ArgumentArray - ) => - classNames( - `cru-input-container cru-input-type-${type}`, - error && "error", - ...additionalClassNames, - ); - - const changeValue = (value: InputValue) => { - onChange(index, value); - }; - - const inputId = `${id}-${key}`; - - if (type === "text") { - const { password } = item; - return ( -
- {label && ( - - )} - { - const v = event.target.value; - changeValue(v); - }} - disabled={disabled} - /> - {error &&
{c(error)}
} - {helper &&
{c(helper)}
} -
- ); - } else if (type === "bool") { - return ( -
- { - const v = event.currentTarget.checked; - changeValue(v); - }} - disabled={disabled} - /> - - {error &&
{c(error)}
} - {helper &&
{c(helper)}
} -
- ); - } else if (type === "select") { - return ( -
- - -
- ); - } - })} -
- ); -} diff --git a/FrontEnd/src/views/common/list/ListContainer.css b/FrontEnd/src/views/common/list/ListContainer.css deleted file mode 100644 index 53781834..00000000 --- a/FrontEnd/src/views/common/list/ListContainer.css +++ /dev/null @@ -1,4 +0,0 @@ -.cru-list-container { - border: 1px solid var(--cru-clickable-primary-normal-color); - border-radius: 5px; -} diff --git a/FrontEnd/src/views/common/list/ListContainer.tsx b/FrontEnd/src/views/common/list/ListContainer.tsx deleted file mode 100644 index aa00d12c..00000000 --- a/FrontEnd/src/views/common/list/ListContainer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; -import classNames from "classnames"; - -import "./ListContainer.css" - -function _ListContainer( - { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, - ref: Ref, -) { - return ( -
- {children} -
- ); -} - -const ListContainer = forwardRef(_ListContainer); - -export default ListContainer; diff --git a/FrontEnd/src/views/common/list/ListItemContainer.css b/FrontEnd/src/views/common/list/ListItemContainer.css deleted file mode 100644 index 8d7afa9f..00000000 --- a/FrontEnd/src/views/common/list/ListItemContainer.css +++ /dev/null @@ -1,3 +0,0 @@ -.cru-list-item-container { - border: 1px solid var(--cru-clickable-primary-normal-color); -} diff --git a/FrontEnd/src/views/common/list/ListItemContainer.tsx b/FrontEnd/src/views/common/list/ListItemContainer.tsx deleted file mode 100644 index 315cbd6e..00000000 --- a/FrontEnd/src/views/common/list/ListItemContainer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; -import classNames from "classnames"; - -import "./ListItemContainer.css"; - -function _ListItemContainer( - { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, - ref: Ref, -) { - return ( -
- {children} -
- ); -} - -const ListItemContainer = forwardRef(_ListItemContainer); - -export default ListItemContainer; diff --git a/FrontEnd/src/views/common/list/index.ts b/FrontEnd/src/views/common/list/index.ts deleted file mode 100644 index e183f7da..00000000 --- a/FrontEnd/src/views/common/list/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import ListContainer from "./ListContainer"; -import ListItemContainer from "./ListItemContainer"; - -export { ListContainer, ListItemContainer }; diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css deleted file mode 100644 index 75734533..00000000 --- a/FrontEnd/src/views/common/menu/Menu.css +++ /dev/null @@ -1,36 +0,0 @@ -.cru-menu { - min-width: 200px; -} - -.cru-menu-item { - display: block; - font-size: 1em; - width: 100%; - padding: 0.5em 1.5em; - transition: all 0.5s; - color: var(--cru-clickable-normal-color); - background-color: var(--cru-clickable-grayscale-normal-color); - border: none; - cursor: pointer; -} - -.cru-menu-item:hover { - background-color: var(--cru-clickable-grayscale-hover-color); -} - -.cru-menu-item:focus { - background-color: var(--cru-clickable-grayscale-focus-color); -} - -.cru-menu-item:active { - background-color: var(--cru-clickable-grayscale-active-color); -} - -.cru-menu-item-icon { - margin-right: 1em; -} - -.cru-menu-divider { - border-width: 0; - border-top: 1px solid var(--cru-primary-color); -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx deleted file mode 100644 index e8099c76..00000000 --- a/FrontEnd/src/views/common/menu/Menu.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { CSSProperties } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import "./Menu.css"; -import Icon from "../Icon"; - -export type MenuItem = - | { - type: "divider"; - } - | { - type: "button"; - text: Text; - icon?: string; - color?: ThemeColor; - onClick: () => void; - }; - -export type MenuItems = MenuItem[]; - -export type MenuProps = { - items: MenuItems; - onItemClicked?: () => void; - className?: string; - style?: CSSProperties; -}; - -export default function Menu({ - items, - onItemClicked, - className, - style, -}: MenuProps) { - const c = useC(); - - return ( -
- {items.map((item, index) => { - if (item.type === "divider") { - return
; - } else { - const { text, color, icon, onClick } = item; - return ( - - ); - } - })} -
- ); -} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.css b/FrontEnd/src/views/common/menu/PopupMenu.css deleted file mode 100644 index 149e0699..00000000 --- a/FrontEnd/src/views/common/menu/PopupMenu.css +++ /dev/null @@ -1,7 +0,0 @@ -.cru-popup-menu-menu-container { - z-index: 1040; - border-radius: 3px; - border: var(--cru-clickable-normal-color) 1.5px solid; - background-color: var(--cru-background-color); - overflow: hidden; -} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx deleted file mode 100644 index 5c8d5e98..00000000 --- a/FrontEnd/src/views/common/menu/PopupMenu.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useState, CSSProperties, ReactNode } from "react"; -import classNames from "classnames"; -import { createPortal } from "react-dom"; -import { usePopper } from "react-popper"; - -import { useClickOutside } from "@/utilities/hooks"; - -import Menu, { MenuItems } from "./Menu"; - -import { ThemeColor } from "../common"; - -import "./PopupMenu.css"; - -export interface PopupMenuProps { - color?: ThemeColor; - items: MenuItems; - children?: ReactNode; - containerClassName?: string; - containerStyle?: CSSProperties; -} - -export default function PopupMenu({ - color, - items, - children, - containerClassName, - containerStyle, -}: PopupMenuProps) { - const [show, setShow] = useState(false); - - const [referenceElement, setReferenceElement] = - useState(null); - const [popperElement, setPopperElement] = useState( - null, - ); - const { styles, attributes } = usePopper(referenceElement, popperElement); - - useClickOutside(popperElement, () => setShow(false), true); - - return ( -
setShow(true)} - > - {children} - {show && - createPortal( -
- { - setShow(false); - }} - /> -
, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")!, - )} -
- ); -} diff --git a/FrontEnd/src/views/common/tab/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx deleted file mode 100644 index cdb988e0..00000000 --- a/FrontEnd/src/views/common/tab/TabPages.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from "react"; - -import { I18nText, UiLogicError } from "@/common"; - -import Tabs from "./Tabs"; - -export interface TabPage { - name: string; - text: I18nText; - page: React.ReactNode; -} - -export interface TabPagesProps { - pages: TabPage[]; - actions?: React.ReactNode; - dense?: boolean; - className?: string; - style?: React.CSSProperties; - navClassName?: string; - navStyle?: React.CSSProperties; - pageContainerClassName?: string; - pageContainerStyle?: React.CSSProperties; -} - -const TabPages: React.FC = ({ - pages, - actions, - dense, - className, - style, - navClassName, - navStyle, - pageContainerClassName, - pageContainerStyle, -}) => { - if (pages.length === 0) { - throw new UiLogicError("Page list can't be empty."); - } - - const [tab, setTab] = React.useState(pages[0].name); - - const currentPage = pages.find((p) => p.name === tab); - - if (currentPage == null) { - throw new UiLogicError("Current tab value is bad."); - } - - return ( -
- ({ - name: page.name, - text: page.text, - onClick: () => { - setTab(page.name); - }, - }))} - dense={dense} - activeTabName={tab} - className={navClassName} - style={navStyle} - actions={actions} - /> -
- {currentPage.page} -
-
- ); -}; - -export default TabPages; diff --git a/FrontEnd/src/views/common/tab/Tabs.css b/FrontEnd/src/views/common/tab/Tabs.css deleted file mode 100644 index 395d16a7..00000000 --- a/FrontEnd/src/views/common/tab/Tabs.css +++ /dev/null @@ -1,33 +0,0 @@ -.cru-nav { - border-bottom: var(--cru-primary-color) 1px solid; - display: flex; -} - -.cru-nav-item { - color: var(--cru-primary-color); - border: var(--cru-background-2-color) 0.5px solid; - border-bottom: none; - padding: 0.5em 1.5em; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - transition: all 0.5s; - cursor: pointer; -} - -.cru-nav.dense .cru-nav-item { - padding: 0.2em 1em; -} - -.cru-nav-item:hover { - background-color: var(--cru-background-1-color); -} - -.cru-nav-item.active { - color: var(--cru-primary-t-color); - background-color: var(--cru-primary-color); - border-color: var(--cru-primary-color); -} - -.cru-nav-action-area { - margin-left: auto; -} diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx deleted file mode 100644 index 3e3ef6fa..00000000 --- a/FrontEnd/src/views/common/tab/Tabs.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import * as React from "react"; -import { Link } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import classnames from "classnames"; - -import { convertI18nText, I18nText } from "@/common"; - -import "./Tabs.css"; - -export interface Tab { - name: string; - text: I18nText; - link?: string; - onClick?: () => void; -} - -export interface TabsProps { - activeTabName?: string; - actions?: React.ReactNode; - dense?: boolean; - tabs: Tab[]; - className?: string; - style?: React.CSSProperties; -} - -export default function Tabs(props: TabsProps): React.ReactElement | null { - const { tabs, activeTabName, className, style, dense, actions } = props; - - const { t } = useTranslation(); - - return ( -
- {tabs.map((tab) => { - const active = activeTabName === tab.name; - const className = classnames("cru-nav-item", active && "active"); - - if (tab.link != null) { - return ( - - {convertI18nText(tab.text, t)} - - ); - } else { - return ( - - {convertI18nText(tab.text, t)} - - ); - } - })} -
{actions}
-
- ); -} diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css deleted file mode 100644 index 24a7e267..00000000 --- a/FrontEnd/src/views/common/theme-color.css +++ /dev/null @@ -1,173 +0,0 @@ -/* Generated by theme-generator.ts */ - -:root { - --cru-primary-color: hsl(210 100% 40%); - --cru-primary-1-color: hsl(210 100% 37%); - --cru-primary-2-color: hsl(210 100% 34%); - --cru-primary-on-color: hsl(210 100% 100%); - --cru-primary-container-color: hsl(210 100% 90%); - --cru-primary-container-1-color: hsl(210 100% 80%); - --cru-primary-container-2-color: hsl(210 100% 70%); - --cru-primary-on-container-color: hsl(210 100% 10%); - --cru-secondary-color: hsl(40 100% 40%); - --cru-secondary-1-color: hsl(40 100% 37%); - --cru-secondary-2-color: hsl(40 100% 34%); - --cru-secondary-on-color: hsl(40 100% 100%); - --cru-secondary-container-color: hsl(40 100% 90%); - --cru-secondary-container-1-color: hsl(40 100% 80%); - --cru-secondary-container-2-color: hsl(40 100% 70%); - --cru-secondary-on-container-color: hsl(40 100% 10%); - --cru-tertiary-color: hsl(160 100% 40%); - --cru-tertiary-1-color: hsl(160 100% 37%); - --cru-tertiary-2-color: hsl(160 100% 34%); - --cru-tertiary-on-color: hsl(160 100% 100%); - --cru-tertiary-container-color: hsl(160 100% 90%); - --cru-tertiary-container-1-color: hsl(160 100% 80%); - --cru-tertiary-container-2-color: hsl(160 100% 70%); - --cru-tertiary-on-container-color: hsl(160 100% 10%); - --cru-danger-color: hsl(0 100% 40%); - --cru-danger-1-color: hsl(0 100% 37%); - --cru-danger-2-color: hsl(0 100% 34%); - --cru-danger-on-color: hsl(0 100% 100%); - --cru-danger-container-color: hsl(0 100% 90%); - --cru-danger-container-1-color: hsl(0 100% 80%); - --cru-danger-container-2-color: hsl(0 100% 70%); - --cru-danger-on-container-color: hsl(0 100% 10%); - --cru-success-color: hsl(120 60% 40%); - --cru-success-1-color: hsl(120 60% 37%); - --cru-success-2-color: hsl(120 60% 34%); - --cru-success-on-color: hsl(120 60% 100%); - --cru-success-container-color: hsl(120 60% 90%); - --cru-success-container-1-color: hsl(120 60% 80%); - --cru-success-container-2-color: hsl(120 60% 70%); - --cru-success-on-container-color: hsl(120 60% 10%); - --cru-surface-dim-color: hsl(0 0% 87%); - --cru-surface-color: hsl(0 0% 98%); - --cru-surface-1-color: hsl(0 0% 90%); - --cru-surface-2-color: hsl(0 0% 82%); - --cru-surface-bright-color: hsl(0 0% 98%); - --cru-surface-container-lowest-color: hsl(0 0% 100%); - --cru-surface-container-low-color: hsl(0 0% 96%); - --cru-surface-container-color: hsl(0 0% 94%); - --cru-surface-container-high-color: hsl(0 0% 92%); - --cru-surface-container-highest-color: hsl(0 0% 90%); - --cru-surface-on-color: hsl(0 0% 10%); - --cru-surface-on-variant-color: hsl(0 0% 30%); - --cru-surface-outline-color: hsl(0 0% 50%); - --cru-surface-outline-variant-color: hsl(0 0% 80%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-primary-color: hsl(210 100% 80%); - --cru-primary-1-color: hsl(210 100% 75%); - --cru-primary-2-color: hsl(210 100% 68%); - --cru-primary-on-color: hsl(210 100% 20%); - --cru-primary-container-color: hsl(210 100% 30%); - --cru-primary-container-1-color: hsl(210 100% 25%); - --cru-primary-container-2-color: hsl(210 100% 20%); - --cru-primary-on-container-color: hsl(210 100% 90%); - --cru-secondary-color: hsl(40 100% 80%); - --cru-secondary-1-color: hsl(40 100% 75%); - --cru-secondary-2-color: hsl(40 100% 68%); - --cru-secondary-on-color: hsl(40 100% 20%); - --cru-secondary-container-color: hsl(40 100% 30%); - --cru-secondary-container-1-color: hsl(40 100% 25%); - --cru-secondary-container-2-color: hsl(40 100% 20%); - --cru-secondary-on-container-color: hsl(40 100% 90%); - --cru-tertiary-color: hsl(160 100% 80%); - --cru-tertiary-1-color: hsl(160 100% 75%); - --cru-tertiary-2-color: hsl(160 100% 68%); - --cru-tertiary-on-color: hsl(160 100% 20%); - --cru-tertiary-container-color: hsl(160 100% 30%); - --cru-tertiary-container-1-color: hsl(160 100% 25%); - --cru-tertiary-container-2-color: hsl(160 100% 20%); - --cru-tertiary-on-container-color: hsl(160 100% 90%); - --cru-danger-color: hsl(0 100% 80%); - --cru-danger-1-color: hsl(0 100% 75%); - --cru-danger-2-color: hsl(0 100% 68%); - --cru-danger-on-color: hsl(0 100% 20%); - --cru-danger-container-color: hsl(0 100% 30%); - --cru-danger-container-1-color: hsl(0 100% 25%); - --cru-danger-container-2-color: hsl(0 100% 20%); - --cru-danger-on-container-color: hsl(0 100% 90%); - --cru-success-color: hsl(120 60% 80%); - --cru-success-1-color: hsl(120 60% 75%); - --cru-success-2-color: hsl(120 60% 68%); - --cru-success-on-color: hsl(120 60% 20%); - --cru-success-container-color: hsl(120 60% 30%); - --cru-success-container-1-color: hsl(120 60% 25%); - --cru-success-container-2-color: hsl(120 60% 20%); - --cru-success-on-container-color: hsl(120 60% 90%); - --cru-surface-dim-color: hsl(0 0% 6%); - --cru-surface-color: hsl(0 0% 6%); - --cru-surface-1-color: hsl(0 0% 25%); - --cru-surface-2-color: hsl(0 0% 40%); - --cru-surface-bright-color: hsl(0 0% 24%); - --cru-surface-container-lowest-color: hsl(0 0% 4%); - --cru-surface-container-low-color: hsl(0 0% 10%); - --cru-surface-container-color: hsl(0 0% 12%); - --cru-surface-container-high-color: hsl(0 0% 17%); - --cru-surface-container-highest-color: hsl(0 0% 22%); - --cru-surface-on-color: hsl(0 0% 90%); - --cru-surface-on-variant-color: hsl(0 0% 80%); - --cru-surface-outline-color: hsl(0 0% 60%); - --cru-surface-outline-variant-color: hsl(0 0% 30%); - } -} - -.cru-primary { - --cru-key-color: var(--cru-primary-color); - --cru-key-1-color: var(--cru-primary-1-color); - --cru-key-2-color: var(--cru-primary-2-color); - --cru-key-on-color: var(--cru-primary-on-color); - --cru-key-container-color: var(--cru-primary-container-color); - --cru-key-container-1-color: var(--cru-primary-container-1-color); - --cru-key-container-2-color: var(--cru-primary-container-2-color); - --cru-key-on-container-color: var(--cru-primary-on-container-color); -} - -.cru-secondary { - --cru-key-color: var(--cru-secondary-color); - --cru-key-1-color: var(--cru-secondary-1-color); - --cru-key-2-color: var(--cru-secondary-2-color); - --cru-key-on-color: var(--cru-secondary-on-color); - --cru-key-container-color: var(--cru-secondary-container-color); - --cru-key-container-1-color: var(--cru-secondary-container-1-color); - --cru-key-container-2-color: var(--cru-secondary-container-2-color); - --cru-key-on-container-color: var(--cru-secondary-on-container-color); -} - -.cru-tertiary { - --cru-key-color: var(--cru-tertiary-color); - --cru-key-1-color: var(--cru-tertiary-1-color); - --cru-key-2-color: var(--cru-tertiary-2-color); - --cru-key-on-color: var(--cru-tertiary-on-color); - --cru-key-container-color: var(--cru-tertiary-container-color); - --cru-key-container-1-color: var(--cru-tertiary-container-1-color); - --cru-key-container-2-color: var(--cru-tertiary-container-2-color); - --cru-key-on-container-color: var(--cru-tertiary-on-container-color); -} - -.cru-danger { - --cru-key-color: var(--cru-danger-color); - --cru-key-1-color: var(--cru-danger-1-color); - --cru-key-2-color: var(--cru-danger-2-color); - --cru-key-on-color: var(--cru-danger-on-color); - --cru-key-container-color: var(--cru-danger-container-color); - --cru-key-container-1-color: var(--cru-danger-container-1-color); - --cru-key-container-2-color: var(--cru-danger-container-2-color); - --cru-key-on-container-color: var(--cru-danger-on-container-color); -} - -.cru-success { - --cru-key-color: var(--cru-success-color); - --cru-key-1-color: var(--cru-success-1-color); - --cru-key-2-color: var(--cru-success-2-color); - --cru-key-on-color: var(--cru-success-on-color); - --cru-key-container-color: var(--cru-success-container-color); - --cru-key-container-1-color: var(--cru-success-container-1-color); - --cru-key-container-2-color: var(--cru-success-container-2-color); - --cru-key-on-container-color: var(--cru-success-on-container-color); -} - diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css deleted file mode 100644 index 6ceb369f..00000000 --- a/FrontEnd/src/views/common/theme.css +++ /dev/null @@ -1,146 +0,0 @@ -@import "./theme-color.css"; - -:root { - --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - --cru-page-padding: 1em 2em; - - --cru-border-radius: 4px; - --cru-card-border-radius: 4px; -} - -/* theme colors */ -:root { - --cru-primary-color: hsl(210, 100%, 50%); - --cru-secondary-color: hsl(30, 100%, 50%); - --cru-create-color: hsl(120, 100%, 25%); - --cru-danger-color: hsl(0, 100%, 50%); -} - -/* common colors */ -:root { - --cru-background-color: hsl(0, 0%, 100%); - --cru-container-background-color: hsl(0, 0%, 97%); - --cru-text-primary-color: hsl(0, 0%, 0%); - --cru-text-secondary-color: hsl(0, 0%, 38%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-background-color: hsl(0, 0%, 0%); - --cru-container-background-color: hsl(0, 0%, 2%); - --cru-text-primary-color: hsl(0, 0%, 100%); - --cru-text-secondary-color: hsl(0, 0%, 85%); - } -} - -:root { - --cru-body-background-color: var(--cru-background-color); -} - -/* clickable color */ -:root { - --cru-clickable-primary-normal-color: var(--cru-primary-color); - --cru-clickable-primary-hover-color: hsl(210, 100%, 60%); - --cru-clickable-primary-focus-color: hsl(210, 100%, 60%); - --cru-clickable-primary-active-color: hsl(210, 100%, 70%); - --cru-clickable-secondary-normal-color: var(--cru-secondary-color); - --cru-clickable-secondary-hover-color: hsl(30, 100%, 60%); - --cru-clickable-secondary-focus-color: hsl(30, 100%, 60%); - --cru-clickable-secondary-active-color: hsl(30, 100%, 70%); - --cru-clickable-create-normal-color: var(--cru-create-color); - --cru-clickable-create-hover-color: hsl(120, 100%, 35%); - --cru-clickable-create-focus-color: hsl(120, 100%, 35%); - --cru-clickable-create-active-color: hsl(120, 100%, 35%); - --cru-clickable-danger-normal-color: var(--cru-danger-color); - --cru-clickable-danger-hover-color: hsl(0, 100%, 60%); - --cru-clickable-danger-focus-color: hsl(0, 100%, 60%); - --cru-clickable-danger-active-color: hsl(0, 100%, 70%); - --cru-clickable-grayscale-normal-color: hsl(0, 0%, 100%); - --cru-clickable-grayscale-hover-color: hsl(0, 0%, 92%); - --cru-clickable-grayscale-focus-color: hsl(0, 0%, 92%); - --cru-clickable-grayscale-active-color: hsl(0, 0%, 88%); - --cru-clickable-disabled-color: hsl(0, 0%, 50%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-clickable-grayscale-normal-color: hsl(0, 0%, 0%); - --cru-clickable-grayscale-hover-color: hsl(0, 0%, 10%); - --cru-clickable-grayscale-focus-color: hsl(0, 0%, 10%); - --cru-clickable-grayscale-active-color: hsl(0, 0%, 20%); - } -} - -.cru-clickable-primary { - --cru-clickable-normal-color: var(--cru-clickable-primary-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-primary-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-primary-focus-color); - --cru-clickable-active-color: var(--cru-clickable-primary-active-color); -} - -.cru-clickable-secondary { - --cru-clickable-normal-color: var(--cru-clickable-secondary-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-secondary-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-secondary-focus-color); - --cru-clickable-active-color: var(--cru-clickable-secondary-active-color); -} - -.cru-clickable-create { - --cru-clickable-normal-color: var(--cru-clickable-create-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-create-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-create-focus-color); - --cru-clickable-active-color: var(--cru-clickable-create-active-color); -} - -.cru-clickable-danger { - --cru-clickable-normal-color: var(--cru-clickable-danger-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-danger-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-danger-focus-color); - --cru-clickable-active-color: var(--cru-clickable-danger-active-color); -} - -.cru-clickable-grayscale { - --cru-clickable-normal-color: var(--cru-clickable-grayscale-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-grayscale-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-grayscale-focus-color); - --cru-clickable-active-color: var(--cru-clickable-grayscale-active-color); -} - -/* button colors */ -:root { - /* push button colors */ - --cru-push-button-text-color: #ffffff; - --cru-push-button-disabled-text-color: hsl(0, 0%, 80%); -} - -/* Card colors */ -:root { - --cru-card-background-primary-color: hsl(210, 100%, 50%); - --cru-card-border-primary-color: hsl(210, 100%, 50%); - --cru-card-background-secondary-color: hsl(30, 100%, 50%); - --cru-card-border-secondary-color: hsl(30, 100%, 50%); - --cru-card-background-create-color: hsl(120, 100%, 25%); - --cru-card-border-create-color: hsl(120, 100%, 25%); - --cru-card-background-danger-color: hsl(0, 100%, 50%); - --cru-card-border-danger-color: hsl(0, 100%, 50%); -} - -.cru-card-primary { - --cru-card-background-color: var(--cru-card-background-primary-color); - --cru-card-border-color: var(--cru-card-border-primary-color) -} - -.cru-card-secondary { - --cru-card-background-color: var(--cru-card-background-secondary-color); - --cru-card-border-color: var(--cru-card-border-secondary-color) -} - -.cru-card-create { - --cru-card-background-color: var(--cru-card-background-create-color); - --cru-card-border-color: var(--cru-card-border-create-color) -} - -.cru-card-danger { - --cru-card-background-color: var(--cru-card-background-danger-color); - --cru-card-border-color: var(--cru-card-border-danger-color) -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/user/UserAvatar.tsx b/FrontEnd/src/views/common/user/UserAvatar.tsx deleted file mode 100644 index aea7bd48..00000000 --- a/FrontEnd/src/views/common/user/UserAvatar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Ref, ComponentPropsWithoutRef } from "react"; - -import { getHttpUserClient } from "@/http/user"; - -export interface UserAvatarProps extends ComponentPropsWithoutRef<"img"> { - username: string; - imgRef?: Ref | null; -} - -export default function UserAvatar({ - username, - imgRef, - ...otherProps -}: UserAvatarProps) { - return ( - - ); -} diff --git a/FrontEnd/tsconfig.json b/FrontEnd/tsconfig.json index cd6aa25d..bf106d95 100644 --- a/FrontEnd/tsconfig.json +++ b/FrontEnd/tsconfig.json @@ -21,9 +21,6 @@ "~*": [ "./*" ], - "@/*": [ - "./src/*" - ] }, "noEmit": true }, -- cgit v1.2.3 From e06f1a33e678ac264f9290013f49237ea97e699b Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 20 Sep 2023 19:37:02 +0800 Subject: ... --- FrontEnd/package.json | 54 +- FrontEnd/pnpm-lock.yaml | 1600 +++++++++++++------- FrontEnd/src/components/dialog/ConfirmDialog.tsx | 25 +- FrontEnd/src/components/dialog/DialogContainer.css | 3 +- FrontEnd/src/pages/timeline/Timeline.css | 19 +- FrontEnd/src/pages/timeline/TimelineDateLabel.css | 6 +- FrontEnd/src/pages/timeline/TimelinePostList.tsx | 1 - 7 files changed, 1085 insertions(+), 623 deletions(-) (limited to 'FrontEnd/src/pages/timeline/TimelinePostList.tsx') diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 6bb8b6e4..4a94d319 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -12,25 +12,25 @@ "check:fix": "pnpm run type-check && pnpm run lint:fix" }, "dependencies": { - "@floating-ui/react-dom": "^2.0.1", - "@microsoft/signalr": "^7.0.7", - "axios": "^1.4.0", - "bootstrap": "^5.3.0", - "bootstrap-icons": "^1.10.5", + "@floating-ui/react-dom": "^2.0.2", + "@microsoft/signalr": "^7.0.11", + "axios": "^1.5.0", + "bootstrap": "^5.3.2", + "bootstrap-icons": "^1.11.1", "classnames": "^2.3.2", "color": "^4.2.3", - "core-js": "^3.31.1", - "i18next": "^23.2.8", + "core-js": "^3.32.2", + "i18next": "^23.5.1", "i18next-browser-languagedetector": "^7.1.0", "lodash": "^4.17.21", - "marked": "^5.1.1", + "marked": "^9.0.3", "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^13.0.1", + "react-i18next": "^13.2.2", "react-popper": "^2.3.0", "react-responsive": "^9.0.2", - "react-router-dom": "^6.14.1", + "react-router-dom": "^6.16.0", "react-transition-group": "^4.4.5", "rxjs": "^7.8.1", "xregexp": "^5.1.1" @@ -38,30 +38,30 @@ "devDependencies": { "@parcel/packager-raw-url": "2.9.3", "@parcel/transformer-webmanifest": "2.9.3", - "@tsconfig/node20": "^1.0.2", - "@types/color": "^3.0.3", - "@types/lodash": "^4.14.195", - "@types/marked": "^5.0.0", - "@types/node": "^20.4.1", + "@tsconfig/node20": "^20.1.2", + "@types/color": "^3.0.4", + "@types/lodash": "^4.14.198", + "@types/marked": "^5.0.1", + "@types/node": "^20.6.3", "@types/parcel-env": "^0.0.1", - "@types/react": "^18.2.14", + "@types/react": "^18.2.22", "@types/react-color": "^3.0.6", - "@types/react-dom": "^18.2.6", + "@types/react-dom": "^18.2.7", "@types/react-responsive": "^8.0.5", "@types/react-transition-group": "^4.4.6", - "@typescript-eslint/eslint-plugin": "^5.61.0", - "@typescript-eslint/parser": "^5.61.0", - "buffer": "^6.0.0", - "eslint": "^8.44.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.32.2", + "@typescript-eslint/eslint-plugin": "^6.7.2", + "@typescript-eslint/parser": "^6.7.2", + "buffer": "^6.0.3", + "eslint": "^8.49.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "parcel": "latest", - "prettier": "^3.0.0", + "parcel": "^2.9.3", + "prettier": "^3.0.3", "process": "^0.11.10", "ts-node": "^10.9.1", - "typescript": "^5.1.6", + "typescript": "^5.2.2", "typescript-language-server": "^3.3.2" } } \ No newline at end of file diff --git a/FrontEnd/pnpm-lock.yaml b/FrontEnd/pnpm-lock.yaml index 3f62918b..7084e82c 100644 --- a/FrontEnd/pnpm-lock.yaml +++ b/FrontEnd/pnpm-lock.yaml @@ -6,20 +6,20 @@ settings: dependencies: '@floating-ui/react-dom': - specifier: ^2.0.1 - version: 2.0.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^2.0.2 + version: 2.0.2(react-dom@18.2.0)(react@18.2.0) '@microsoft/signalr': - specifier: ^7.0.7 - version: 7.0.7 + specifier: ^7.0.11 + version: 7.0.11 axios: - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.5.0 + version: 1.5.0 bootstrap: - specifier: ^5.3.0 - version: 5.3.0(@popperjs/core@2.11.8) + specifier: ^5.3.2 + version: 5.3.2(@popperjs/core@2.11.8) bootstrap-icons: - specifier: ^1.10.5 - version: 1.10.5 + specifier: ^1.11.1 + version: 1.11.1 classnames: specifier: ^2.3.2 version: 2.3.2 @@ -27,11 +27,11 @@ dependencies: specifier: ^4.2.3 version: 4.2.3 core-js: - specifier: ^3.31.1 - version: 3.31.1 + specifier: ^3.32.2 + version: 3.32.2 i18next: - specifier: ^23.2.8 - version: 23.2.8 + specifier: ^23.5.1 + version: 23.5.1 i18next-browser-languagedetector: specifier: ^7.1.0 version: 7.1.0 @@ -39,8 +39,8 @@ dependencies: specifier: ^4.17.21 version: 4.17.21 marked: - specifier: ^5.1.1 - version: 5.1.1 + specifier: ^9.0.3 + version: 9.0.3 moment: specifier: ^2.29.4 version: 2.29.4 @@ -51,8 +51,8 @@ dependencies: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) react-i18next: - specifier: ^13.0.1 - version: 13.0.1(i18next@23.2.8)(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.2.2 + version: 13.2.2(i18next@23.5.1)(react-dom@18.2.0)(react@18.2.0) react-popper: specifier: ^2.3.0 version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) @@ -60,8 +60,8 @@ dependencies: specifier: ^9.0.2 version: 9.0.2(react@18.2.0) react-router-dom: - specifier: ^6.14.1 - version: 6.14.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^6.16.0 + version: 6.16.0(react-dom@18.2.0)(react@18.2.0) react-transition-group: specifier: ^4.4.5 version: 4.4.5(react-dom@18.2.0)(react@18.2.0) @@ -80,32 +80,32 @@ devDependencies: specifier: 2.9.3 version: 2.9.3(@parcel/core@2.9.3) '@tsconfig/node20': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^20.1.2 + version: 20.1.2 '@types/color': - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.0.4 + version: 3.0.4 '@types/lodash': - specifier: ^4.14.195 - version: 4.14.195 + specifier: ^4.14.198 + version: 4.14.198 '@types/marked': - specifier: ^5.0.0 - version: 5.0.0 + specifier: ^5.0.1 + version: 5.0.1 '@types/node': - specifier: ^20.4.1 - version: 20.4.1 + specifier: ^20.6.3 + version: 20.6.3 '@types/parcel-env': specifier: ^0.0.1 version: 0.0.1 '@types/react': - specifier: ^18.2.14 - version: 18.2.14 + specifier: ^18.2.22 + version: 18.2.22 '@types/react-color': specifier: ^3.0.6 version: 3.0.6 '@types/react-dom': - specifier: ^18.2.6 - version: 18.2.6 + specifier: ^18.2.7 + version: 18.2.7 '@types/react-responsive': specifier: ^8.0.5 version: 8.0.5 @@ -113,44 +113,44 @@ devDependencies: specifier: ^4.4.6 version: 4.4.6 '@typescript-eslint/eslint-plugin': - specifier: ^5.61.0 - version: 5.61.0(@typescript-eslint/parser@5.61.0)(eslint@8.44.0)(typescript@5.1.6) + specifier: ^6.7.2 + version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.49.0)(typescript@5.2.2) '@typescript-eslint/parser': - specifier: ^5.61.0 - version: 5.61.0(eslint@8.44.0)(typescript@5.1.6) + specifier: ^6.7.2 + version: 6.7.2(eslint@8.49.0)(typescript@5.2.2) buffer: - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^6.0.3 + version: 6.0.3 eslint: - specifier: ^8.44.0 - version: 8.44.0 + specifier: ^8.49.0 + version: 8.49.0 eslint-config-prettier: - specifier: ^8.8.0 - version: 8.8.0(eslint@8.44.0) + specifier: ^9.0.0 + version: 9.0.0(eslint@8.49.0) eslint-plugin-prettier: - specifier: ^4.2.1 - version: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.44.0)(prettier@3.0.0) + specifier: ^5.0.0 + version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.49.0)(prettier@3.0.3) eslint-plugin-react: - specifier: ^7.32.2 - version: 7.32.2(eslint@8.44.0) + specifier: ^7.33.2 + version: 7.33.2(eslint@8.49.0) eslint-plugin-react-hooks: specifier: ^4.6.0 - version: 4.6.0(eslint@8.44.0) + version: 4.6.0(eslint@8.49.0) parcel: - specifier: latest - version: 2.9.3 + specifier: ^2.9.3 + version: 2.9.3(typescript@5.2.2) prettier: - specifier: ^3.0.0 - version: 3.0.0 + specifier: ^3.0.3 + version: 3.0.3 process: specifier: ^0.11.10 version: 0.11.10 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@20.4.1)(typescript@5.1.6) + version: 10.9.1(@types/node@20.6.3)(typescript@5.2.2) typescript: - specifier: ^5.1.6 - version: 5.1.6 + specifier: ^5.2.2 + version: 5.2.2 typescript-language-server: specifier: ^3.3.2 version: 3.3.2 @@ -162,40 +162,41 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@babel/code-frame@7.22.5: - resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.22.5 + '@babel/highlight': 7.22.20 + chalk: 2.4.2 dev: true - /@babel/helper-validator-identifier@7.22.5: - resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} dev: true - /@babel/highlight@7.22.5: - resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} + /@babel/highlight@7.22.20: + resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 dev: true - /@babel/runtime-corejs3@7.22.6: - resolution: {integrity: sha512-M+37LLIRBTEVjktoJjbw4KVhupF0U/3PYUCbBwgAd9k17hoKhRu1n935QiG7Tuxv0LJOMrb2vuKEeYUlv0iyiw==} + /@babel/runtime-corejs3@7.22.15: + resolution: {integrity: sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ==} engines: {node: '>=6.9.0'} dependencies: - core-js-pure: 3.31.1 - regenerator-runtime: 0.13.11 + core-js-pure: 3.32.2 + regenerator-runtime: 0.14.0 dev: false - /@babel/runtime@7.22.6: - resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==} + /@babel/runtime@7.22.15: + resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} engines: {node: '>=6.9.0'} dependencies: - regenerator-runtime: 0.13.11 + regenerator-runtime: 0.14.0 dev: false /@cspotcode/source-map-support@0.8.1: @@ -205,29 +206,29 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.44.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.49.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.44.0 - eslint-visitor-keys: 3.4.1 + eslint: 8.49.0 + eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/regexpp@4.5.1: - resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + /@eslint-community/regexpp@4.8.1: + resolution: {integrity: sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.0: - resolution: {integrity: sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==} + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.6.0 - globals: 13.20.0 + espree: 9.6.1 + globals: 13.21.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -237,41 +238,41 @@ packages: - supports-color dev: true - /@eslint/js@8.44.0: - resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} + /@eslint/js@8.49.0: + resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@floating-ui/core@1.4.1: - resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} + /@floating-ui/core@1.5.0: + resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} dependencies: - '@floating-ui/utils': 0.1.1 + '@floating-ui/utils': 0.1.4 dev: false - /@floating-ui/dom@1.5.1: - resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==} + /@floating-ui/dom@1.5.3: + resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} dependencies: - '@floating-ui/core': 1.4.1 - '@floating-ui/utils': 0.1.1 + '@floating-ui/core': 1.5.0 + '@floating-ui/utils': 0.1.4 dev: false - /@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==} + /@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@floating-ui/dom': 1.5.1 + '@floating-ui/dom': 1.5.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@floating-ui/utils@0.1.1: - resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} + /@floating-ui/utils@0.1.4: + resolution: {integrity: sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==} dev: false - /@humanwhocodes/config-array@0.11.10: - resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + /@humanwhocodes/config-array@0.11.11: + resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -364,13 +365,13 @@ packages: dev: true optional: true - /@microsoft/signalr@7.0.7: - resolution: {integrity: sha512-RMWZLTxnjWPSaS9PYZxXAttql2JDM/+IsSJk0nACFhpLjnSw8UWfvUxOv/QjZSqLxhuksXxzBJ/91xUP6Y7Nvg==} + /@microsoft/signalr@7.0.11: + resolution: {integrity: sha512-//6ipnYKhHf2MJgM+MQSlgB5L/pcYeZ+v4w6YAr4epRM1iSDQ6WjUkCVX2ZMxcY06XGlLzggs3Z9ZIcL9ws9KQ==} dependencies: abort-controller: 3.0.0 eventsource: 2.0.2 fetch-cookie: 2.1.0 - node-fetch: 2.6.12 + node-fetch: 2.7.0 ws: 7.5.9 transitivePeerDependencies: - bufferutil @@ -499,7 +500,7 @@ packages: - '@parcel/core' dev: true - /@parcel/config-default@2.9.3(@parcel/core@2.9.3): + /@parcel/config-default@2.9.3(@parcel/core@2.9.3)(typescript@5.2.2): resolution: {integrity: sha512-tqN5tF7QnVABDZAu76co5E6N8mA9n8bxiWdK4xYyINYFIEHgX172oRTqXTnhEMjlMrdmASxvnGlbaPBaVnrCTw==} peerDependencies: '@parcel/core': ^2.9.3 @@ -509,7 +510,7 @@ packages: '@parcel/core': 2.9.3 '@parcel/namer-default': 2.9.3(@parcel/core@2.9.3) '@parcel/optimizer-css': 2.9.3(@parcel/core@2.9.3) - '@parcel/optimizer-htmlnano': 2.9.3(@parcel/core@2.9.3) + '@parcel/optimizer-htmlnano': 2.9.3(@parcel/core@2.9.3)(typescript@5.2.2) '@parcel/optimizer-image': 2.9.3(@parcel/core@2.9.3) '@parcel/optimizer-svgo': 2.9.3(@parcel/core@2.9.3) '@parcel/optimizer-swc': 2.9.3(@parcel/core@2.9.3) @@ -543,6 +544,7 @@ packages: - relateurl - srcset - terser + - typescript - uncss dev: true @@ -567,12 +569,12 @@ packages: '@parcel/workers': 2.9.3(@parcel/core@2.9.3) abortcontroller-polyfill: 1.7.5 base-x: 3.0.9 - browserslist: 4.21.9 + browserslist: 4.21.10 clone: 2.1.2 dotenv: 7.0.0 dotenv-expand: 5.1.0 json5: 2.2.3 - msgpackr: 1.9.5 + msgpackr: 1.9.9 nullthrows: 1.1.1 semver: 7.5.4 dev: true @@ -605,7 +607,7 @@ packages: '@parcel/fs-search': 2.9.3 '@parcel/types': 2.9.3(@parcel/core@2.9.3) '@parcel/utils': 2.9.3 - '@parcel/watcher': 2.2.0 + '@parcel/watcher': 2.3.0 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) dev: true @@ -671,19 +673,19 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.9 - lightningcss: 1.21.5 + browserslist: 4.21.10 + lightningcss: 1.22.0 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' dev: true - /@parcel/optimizer-htmlnano@2.9.3(@parcel/core@2.9.3): + /@parcel/optimizer-htmlnano@2.9.3(@parcel/core@2.9.3)(typescript@5.2.2): resolution: {integrity: sha512-9g/KBck3c6DokmJfvJ5zpHFBiCSolaGrcsTGx8C3YPdCTVTI9P1TDCwUxvAr4LjpcIRSa82wlLCI+nF6sSgxKA==} engines: {node: '>= 12.0.0', parcel: ^2.9.3} dependencies: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) - htmlnano: 2.0.4(svgo@2.8.0) + htmlnano: 2.0.4(svgo@2.8.0)(typescript@5.2.2) nullthrows: 1.1.1 posthtml: 0.16.6 svgo: 2.8.0 @@ -695,6 +697,7 @@ packages: - relateurl - srcset - terser + - typescript - uncss dev: true @@ -731,7 +734,7 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - '@swc/core': 1.3.68 + '@swc/core': 1.3.86 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -790,7 +793,7 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - globals: 13.20.0 + globals: 13.21.0 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -950,7 +953,7 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.9 + browserslist: 4.21.10 json5: 2.2.3 nullthrows: 1.1.1 semver: 7.5.4 @@ -966,8 +969,8 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.9 - lightningcss: 1.21.5 + browserslist: 4.21.10 + lightningcss: 1.22.0 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -1015,8 +1018,8 @@ packages: '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) - '@swc/helpers': 0.5.1 - browserslist: 4.21.9 + '@swc/helpers': 0.5.2 + browserslist: 4.21.10 nullthrows: 1.1.1 regenerator-runtime: 0.13.11 semver: 7.5.4 @@ -1139,8 +1142,8 @@ packages: nullthrows: 1.1.1 dev: true - /@parcel/watcher-android-arm64@2.2.0: - resolution: {integrity: sha512-nU2wh00CTQT9rr1TIKTjdQ9lAGYpmz6XuKw0nAwAN+S2A5YiD55BK1u+E5WMCT8YOIDe/n6gaj4o/Bi9294SSQ==} + /@parcel/watcher-android-arm64@2.3.0: + resolution: {integrity: sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] @@ -1148,8 +1151,8 @@ packages: dev: true optional: true - /@parcel/watcher-darwin-arm64@2.2.0: - resolution: {integrity: sha512-cJl0UZDcodciy3TDMomoK/Huxpjlkkim3SyMgWzjovHGOZKNce9guLz2dzuFwfObBFCjfznbFMIvAZ5syXotYw==} + /@parcel/watcher-darwin-arm64@2.3.0: + resolution: {integrity: sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] @@ -1157,8 +1160,8 @@ packages: dev: true optional: true - /@parcel/watcher-darwin-x64@2.2.0: - resolution: {integrity: sha512-QI77zxaGrCV1StKcoRYfsUfmUmvPMPfQrubkBBy5XujV2fwaLgZivQOTQMBgp5K2+E19u1ufpspKXAPqSzpbyg==} + /@parcel/watcher-darwin-x64@2.3.0: + resolution: {integrity: sha512-20oBj8LcEOnLE3mgpy6zuOq8AplPu9NcSSSfyVKgfOhNAc4eF4ob3ldj0xWjGGbOF7Dcy1Tvm6ytvgdjlfUeow==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] @@ -1166,8 +1169,17 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm-glibc@2.2.0: - resolution: {integrity: sha512-I2GPBcAXazPzabCmfsa3HRRW+MGlqxYd8g8RIueJU+a4o5nyNZDz0CR1cu0INT0QSQXEZV7w6UE8Hz9CF8u3Pg==} + /@parcel/watcher-freebsd-x64@2.3.0: + resolution: {integrity: sha512-7LftKlaHunueAEiojhCn+Ef2CTXWsLgTl4hq0pkhkTBFI3ssj2bJXmH2L67mKpiAD5dz66JYk4zS66qzdnIOgw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-arm-glibc@2.3.0: + resolution: {integrity: sha512-1apPw5cD2xBv1XIHPUlq0cO6iAaEUQ3BcY0ysSyD9Kuyw4MoWm1DV+W9mneWI+1g6OeP6dhikiFE6BlU+AToTQ==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] @@ -1175,8 +1187,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm64-glibc@2.2.0: - resolution: {integrity: sha512-St5mlfp+2lS9AmgixUqfwJa/DwVmTCJxC1HcOubUTz6YFOKIlkHCeUa1Bxi4E/tR/HSez8+heXHL8HQkJ4Bd8g==} + /@parcel/watcher-linux-arm64-glibc@2.3.0: + resolution: {integrity: sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] @@ -1184,8 +1196,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-arm64-musl@2.2.0: - resolution: {integrity: sha512-jS+qfhhoOBVWwMLP65MaG8xdInMK30pPW8wqTCg2AAuVJh5xepMbzkhHJ4zURqHiyY3EiIRuYu4ONJKCxt8iqA==} + /@parcel/watcher-linux-arm64-musl@2.3.0: + resolution: {integrity: sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] @@ -1193,8 +1205,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-x64-glibc@2.2.0: - resolution: {integrity: sha512-xJvJ7R2wJdi47WZBFS691RDOWvP1j/IAs3EXaWVhDI8FFITbWrWaln7KoNcR0Y3T+ZwimFY/cfb0PNht1q895g==} + /@parcel/watcher-linux-x64-glibc@2.3.0: + resolution: {integrity: sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] @@ -1202,8 +1214,8 @@ packages: dev: true optional: true - /@parcel/watcher-linux-x64-musl@2.2.0: - resolution: {integrity: sha512-D+NMpgr23a+RI5mu8ZPKWy7AqjBOkURFDgP5iIXXEf/K3hm0jJ3ogzi0Ed2237B/CdYREimCgXyeiAlE/FtwyA==} + /@parcel/watcher-linux-x64-musl@2.3.0: + resolution: {integrity: sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] @@ -1211,8 +1223,8 @@ packages: dev: true optional: true - /@parcel/watcher-win32-arm64@2.2.0: - resolution: {integrity: sha512-z225cPn3aygJsyVUOWwfyW+fY0Tvk7N3XCOl66qUPFxpbuXeZuiuuJemmtm8vxyqa3Ur7peU/qJxrpC64aeI7Q==} + /@parcel/watcher-win32-arm64@2.3.0: + resolution: {integrity: sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] @@ -1220,8 +1232,17 @@ packages: dev: true optional: true - /@parcel/watcher-win32-x64@2.2.0: - resolution: {integrity: sha512-JqGW0RJ61BkKx+yYzIURt9s53P7xMVbv0uxYPzAXLBINGaFmkIKSuUPyBVfy8TMbvp93lvF4SPBNDzVRJfvgOw==} + /@parcel/watcher-win32-ia32@2.3.0: + resolution: {integrity: sha512-FJS/IBQHhRpZ6PiCjFt1UAcPr0YmCLHRbTc00IBTrelEjlmmgIVLeOx4MSXzx2HFEy5Jo5YdhGpxCuqCyDJ5ow==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-win32-x64@2.3.0: + resolution: {integrity: sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] @@ -1229,8 +1250,8 @@ packages: dev: true optional: true - /@parcel/watcher@2.2.0: - resolution: {integrity: sha512-71S4TF+IMyAn24PK4KSkdKtqJDR3zRzb0HE3yXpacItqTM7XfF2f5q9NEGLEVl0dAaBAGfNwDCjH120y25F6Tg==} + /@parcel/watcher@2.3.0: + resolution: {integrity: sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ==} engines: {node: '>= 10.0.0'} dependencies: detect-libc: 1.0.3 @@ -1238,16 +1259,18 @@ packages: micromatch: 4.0.5 node-addon-api: 7.0.0 optionalDependencies: - '@parcel/watcher-android-arm64': 2.2.0 - '@parcel/watcher-darwin-arm64': 2.2.0 - '@parcel/watcher-darwin-x64': 2.2.0 - '@parcel/watcher-linux-arm-glibc': 2.2.0 - '@parcel/watcher-linux-arm64-glibc': 2.2.0 - '@parcel/watcher-linux-arm64-musl': 2.2.0 - '@parcel/watcher-linux-x64-glibc': 2.2.0 - '@parcel/watcher-linux-x64-musl': 2.2.0 - '@parcel/watcher-win32-arm64': 2.2.0 - '@parcel/watcher-win32-x64': 2.2.0 + '@parcel/watcher-android-arm64': 2.3.0 + '@parcel/watcher-darwin-arm64': 2.3.0 + '@parcel/watcher-darwin-x64': 2.3.0 + '@parcel/watcher-freebsd-x64': 2.3.0 + '@parcel/watcher-linux-arm-glibc': 2.3.0 + '@parcel/watcher-linux-arm64-glibc': 2.3.0 + '@parcel/watcher-linux-arm64-musl': 2.3.0 + '@parcel/watcher-linux-x64-glibc': 2.3.0 + '@parcel/watcher-linux-x64-musl': 2.3.0 + '@parcel/watcher-win32-arm64': 2.3.0 + '@parcel/watcher-win32-ia32': 2.3.0 + '@parcel/watcher-win32-x64': 2.3.0 dev: true /@parcel/workers@2.9.3(@parcel/core@2.9.3): @@ -1265,17 +1288,29 @@ packages: nullthrows: 1.1.1 dev: true + /@pkgr/utils@2.4.2: + resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + fast-glob: 3.3.1 + is-glob: 4.0.3 + open: 9.1.0 + picocolors: 1.0.0 + tslib: 2.6.2 + dev: true + /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@remix-run/router@1.7.1: - resolution: {integrity: sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==} - engines: {node: '>=14'} + /@remix-run/router@1.9.0: + resolution: {integrity: sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==} + engines: {node: '>=14.0.0'} dev: false - /@swc/core-darwin-arm64@1.3.68: - resolution: {integrity: sha512-Z5pNxeuP2NxpOHTzDQkJs0wAPLnTlglZnR3WjObijwvdwT/kw1Y5EPDKM/BVSIeG40SPMkDLBbI0aj0qyXzrBA==} + /@swc/core-darwin-arm64@1.3.86: + resolution: {integrity: sha512-hMvSDms0sJJHNtRa3Vhmr9StWN1vmikbf5VE0IZUYGnF1/JZTkXU1h6CdNUY4Hr6i7uCZjH6BEhxFHX1JtKV4w==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] @@ -1283,8 +1318,8 @@ packages: dev: true optional: true - /@swc/core-darwin-x64@1.3.68: - resolution: {integrity: sha512-ZHl42g6yXhfX4PzAQ0BNvBXpt/OcbAHfubWRN6eXELK3fiNnxL7QBW1if7iizlq6iA+Mj1pwHyyUit1pz0+fgA==} + /@swc/core-darwin-x64@1.3.86: + resolution: {integrity: sha512-Jro6HVH4uSOBM7tTDaQNKLNc8BJV7n+SO+Ft2HAZINyeKJS/8MfEYneG7Vmqg18gv00c6dz9AOCcyz+BR7BFkQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] @@ -1292,8 +1327,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm-gnueabihf@1.3.68: - resolution: {integrity: sha512-Mk8f6KCOQ2CNAR4PtWajIjS6XKSSR7ZYDOCf1GXRxhS3qEyQH7V8elWvqWYqHcT4foO60NUmxA/NOM/dQrdO1A==} + /@swc/core-linux-arm-gnueabihf@1.3.86: + resolution: {integrity: sha512-wYB9m0pzXJVSzedXSl4JwS3gKtvcPinpe9MbkddezpqL7OjyDP6pHHW9qIucsfgCrtMtbPC2nqulXLPtAAyIjw==} engines: {node: '>=10'} cpu: [arm] os: [linux] @@ -1301,8 +1336,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-gnu@1.3.68: - resolution: {integrity: sha512-RhBllggh9t9sIxaRgRcGrVaS7fDk6KsIqR6b9+dwU5OyDr4ZyHWw1ZaH/1/HAebuXYhNBjoNUiRtca6lKRIPgQ==} + /@swc/core-linux-arm64-gnu@1.3.86: + resolution: {integrity: sha512-fR44IyK5cdCaO8cC++IEH0Jn03tWnunJnjzA99LxlE5TRInSIOvFm+g5OSUQZDAvEXmQ38sd31LO2HOoDS1Edw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -1310,8 +1345,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-musl@1.3.68: - resolution: {integrity: sha512-8K3zjU+tFgn6yGDEeD343gkKaHU9dhz77NiVkI1VzwRaT/Ag5pwl5eMQ1yStm8koNFzn3zq6rGjHfI5g2yI5Wg==} + /@swc/core-linux-arm64-musl@1.3.86: + resolution: {integrity: sha512-EUPfdbK4dUk/nkX3Vmv/47XH+DqHOa9JI0CTthvJ8/ZXei1MKDUsUc+tI1zMQX2uCuSkSWsEIEpCmA0tMwFhtw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -1319,8 +1354,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-gnu@1.3.68: - resolution: {integrity: sha512-4xAnvsBOyeTL0AB8GWlRKDM/hsysJ5jr5qvdKKI3rZfJgnnxl/xSX6TJKPsJ8gygfUJ3BmfCbmUmEyeDZ3YPvA==} + /@swc/core-linux-x64-gnu@1.3.86: + resolution: {integrity: sha512-snVZZWv8XgNVaKrTxtO3rUN+BbbB6I8Fqwe8zM/DWGJ096J13r89doQ48x5ZyO+bW4D48eZIWP5pdfSW7oBE3w==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -1328,8 +1363,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-musl@1.3.68: - resolution: {integrity: sha512-RCpaBo1fcpy1EFdjF+I7N4lfzOaHXVV0iMw/ABM+0PD6tp3V/9pxsguaZyeAHyEiUlDA6PZ4TfXv5zfnXEgW4Q==} + /@swc/core-linux-x64-musl@1.3.86: + resolution: {integrity: sha512-PnnksUJymEJkdnbV2orOSOSB441UqsxYbJge9zbr5UTRXUfWO3eFRV0iTBegjTlOQGbW6yN+YRSDkenTbmCI6g==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -1337,8 +1372,8 @@ packages: dev: true optional: true - /@swc/core-win32-arm64-msvc@1.3.68: - resolution: {integrity: sha512-v2WZvXrSslYEpY1nqpItyamL4DyaJinmOkXvM8Bc1LLKU5rGuvmBdjUYg/5Y+o0AUynuiWubpgHNOkBWiCvfqw==} + /@swc/core-win32-arm64-msvc@1.3.86: + resolution: {integrity: sha512-XlGEGyHwLndm08VvgeAPGj40L+Hx575MQC+2fsyB1uSNUN+uf7fvke+wc7k50a92CaQe/8foLyIR5faayozEJA==} engines: {node: '>=10'} cpu: [arm64] os: [win32] @@ -1346,8 +1381,8 @@ packages: dev: true optional: true - /@swc/core-win32-ia32-msvc@1.3.68: - resolution: {integrity: sha512-HH5NJrIdzkJs+1xxprie0qSCMBeL9yeEhcC1yZTzYv8bwmabOUSdtKIqS55iYP/2hLWn9CTbvKPmLOIhCopW3Q==} + /@swc/core-win32-ia32-msvc@1.3.86: + resolution: {integrity: sha512-U1BhZa1x9yn+wZGTQmt1cYR79a0FzW/wL6Jas1Pn0bykKLxdRU4mCeZt2P+T3buLm8jr8LpPWiCrbvr658PzwA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] @@ -1355,8 +1390,8 @@ packages: dev: true optional: true - /@swc/core-win32-x64-msvc@1.3.68: - resolution: {integrity: sha512-9HZVtLQUgK8r/yXQdwe0VBexbIcrY6+fBROhs7AAPWdewpaUeLkwQEJk6TbYr9CQuHw26FFGg6SjwAiqXF+kgQ==} + /@swc/core-win32-x64-msvc@1.3.86: + resolution: {integrity: sha512-wRoQUajqpE3wITHhZVj/6BPu/QwHriFHLHuJA+9y6PeGtUtTmntL42aBKXIFhfL767dYFtohyNg1uZ9eqbGyGQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] @@ -1364,8 +1399,8 @@ packages: dev: true optional: true - /@swc/core@1.3.68: - resolution: {integrity: sha512-njGQuJO+Wy06dEayt70cf0c/KI3HGjm4iW9LLViVLBuYNzJ4SSdNfzejludzufu6im+dsDJ0i3QjgWhAIcVHMQ==} + /@swc/core@1.3.86: + resolution: {integrity: sha512-bEXUtm37bcmJ3q+geG7Zy4rJNUzpxalXQUrrqX1ZoGj3HRtzdeVZ0L/um3fG2j16qe61t8TX/OIZ2G6j6dkG/w==} engines: {node: '>=10'} requiresBuild: true peerDependencies: @@ -1373,23 +1408,29 @@ packages: peerDependenciesMeta: '@swc/helpers': optional: true + dependencies: + '@swc/types': 0.1.5 optionalDependencies: - '@swc/core-darwin-arm64': 1.3.68 - '@swc/core-darwin-x64': 1.3.68 - '@swc/core-linux-arm-gnueabihf': 1.3.68 - '@swc/core-linux-arm64-gnu': 1.3.68 - '@swc/core-linux-arm64-musl': 1.3.68 - '@swc/core-linux-x64-gnu': 1.3.68 - '@swc/core-linux-x64-musl': 1.3.68 - '@swc/core-win32-arm64-msvc': 1.3.68 - '@swc/core-win32-ia32-msvc': 1.3.68 - '@swc/core-win32-x64-msvc': 1.3.68 + '@swc/core-darwin-arm64': 1.3.86 + '@swc/core-darwin-x64': 1.3.86 + '@swc/core-linux-arm-gnueabihf': 1.3.86 + '@swc/core-linux-arm64-gnu': 1.3.86 + '@swc/core-linux-arm64-musl': 1.3.86 + '@swc/core-linux-x64-gnu': 1.3.86 + '@swc/core-linux-x64-musl': 1.3.86 + '@swc/core-win32-arm64-msvc': 1.3.86 + '@swc/core-win32-ia32-msvc': 1.3.86 + '@swc/core-win32-x64-msvc': 1.3.86 dev: true - /@swc/helpers@0.5.1: - resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} + /@swc/helpers@0.5.2: + resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: - tslib: 2.6.0 + tslib: 2.6.2 + dev: true + + /@swc/types@0.1.5: + resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} dev: true /@trysound/sax@0.2.0: @@ -1413,12 +1454,12 @@ packages: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} dev: true - /@tsconfig/node20@1.0.2: - resolution: {integrity: sha512-pw0MmECiSTbBfIlT0x3iQLuJ8s3i2mwYoGxJ3vzqTNMdc4nO2VeqfCOQ/doGFa8iyPlqmBd98/5pBctWz7uN2A==} + /@tsconfig/node20@20.1.2: + resolution: {integrity: sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==} dev: true - /@types/color-convert@2.0.0: - resolution: {integrity: sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==} + /@types/color-convert@2.0.1: + resolution: {integrity: sha512-GwXanrvq/tBHJtudbl1lSy9Ybt7KS9+rA+YY3bcuIIM+d6jSHUr+5yjO83gtiRpuaPiBccwFjSnAK2qSrIPA7w==} dependencies: '@types/color-name': 1.1.1 dev: true @@ -1427,65 +1468,65 @@ packages: resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} dev: true - /@types/color@3.0.3: - resolution: {integrity: sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==} + /@types/color@3.0.4: + resolution: {integrity: sha512-OpisS4bqJJwbkkQRrMvURf3DOxBoAg9mysHYI7WgrWpSYHqHGKYBULHdz4ih77SILcLDo/zyHGFyfIl9yb8NZQ==} dependencies: - '@types/color-convert': 2.0.0 + '@types/color-convert': 2.0.1 dev: true - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + /@types/json-schema@7.0.13: + resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} dev: true - /@types/lodash@4.14.195: - resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==} + /@types/lodash@4.14.198: + resolution: {integrity: sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==} dev: true - /@types/marked@5.0.0: - resolution: {integrity: sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==} + /@types/marked@5.0.1: + resolution: {integrity: sha512-Y3pAUzHKh605fN6fvASsz5FDSWbZcs/65Q6xYRmnIP9ZIYz27T4IOmXfH9gWJV1dpi7f1e7z7nBGUTx/a0ptpA==} dev: true - /@types/node@20.4.1: - resolution: {integrity: sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==} + /@types/node@20.6.3: + resolution: {integrity: sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==} dev: true /@types/parcel-env@0.0.1: resolution: {integrity: sha512-8WmdiJ1uEBcW6AOWzQH7i0141ZXZr7B03YfTpguUDrTHXJHwYU9eEOckBRCZzYGrzb4pdoyBlaIMiTee04uqPQ==} dev: true - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/prop-types@15.7.6: + resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} dev: true /@types/react-color@3.0.6: resolution: {integrity: sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w==} dependencies: - '@types/react': 18.2.14 + '@types/react': 18.2.22 '@types/reactcss': 1.2.6 dev: true - /@types/react-dom@18.2.6: - resolution: {integrity: sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==} + /@types/react-dom@18.2.7: + resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: - '@types/react': 18.2.14 + '@types/react': 18.2.22 dev: true /@types/react-responsive@8.0.5: resolution: {integrity: sha512-k3gQJgI87oP5IrVZe//3LKJFnAeFaqqWmmtl5eoYL2H3HqFcIhUaE30kRK1CsW3DHdojZxcVj4ZNc2ClsEu2PA==} dependencies: - '@types/react': 18.2.14 + '@types/react': 18.2.22 dev: true /@types/react-transition-group@4.4.6: resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} dependencies: - '@types/react': 18.2.14 + '@types/react': 18.2.22 dev: true - /@types/react@18.2.14: - resolution: {integrity: sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==} + /@types/react@18.2.22: + resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==} dependencies: - '@types/prop-types': 15.7.5 + '@types/prop-types': 15.7.6 '@types/scheduler': 0.16.3 csstype: 3.1.2 dev: true @@ -1493,145 +1534,146 @@ packages: /@types/reactcss@1.2.6: resolution: {integrity: sha512-qaIzpCuXNWomGR1Xq8SCFTtF4v8V27Y6f+b9+bzHiv087MylI/nTCqqdChNeWS7tslgROmYB7yeiruWX7WnqNg==} dependencies: - '@types/react': 18.2.14 + '@types/react': 18.2.22 dev: true /@types/scheduler@0.16.3: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} dev: true - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + /@types/semver@7.5.2: + resolution: {integrity: sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==} dev: true - /@typescript-eslint/eslint-plugin@5.61.0(@typescript-eslint/parser@5.61.0)(eslint@8.44.0)(typescript@5.1.6): - resolution: {integrity: sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/eslint-plugin@6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.61.0(eslint@8.44.0)(typescript@5.1.6) - '@typescript-eslint/scope-manager': 5.61.0 - '@typescript-eslint/type-utils': 5.61.0(eslint@8.44.0)(typescript@5.1.6) - '@typescript-eslint/utils': 5.61.0(eslint@8.44.0)(typescript@5.1.6) + '@eslint-community/regexpp': 4.8.1 + '@typescript-eslint/parser': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/type-utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.44.0 + eslint: 8.49.0 graphemer: 1.4.0 ignore: 5.2.4 - natural-compare-lite: 1.4.0 + natural-compare: 1.4.0 semver: 7.5.4 - tsutils: 3.21.0(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.1.6): - resolution: {integrity: sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/parser@6.7.2(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.61.0 - '@typescript-eslint/types': 5.61.0 - '@typescript-eslint/typescript-estree': 5.61.0(typescript@5.1.6) + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.44.0 - typescript: 5.1.6 + eslint: 8.49.0 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.61.0: - resolution: {integrity: sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/scope-manager@6.7.2: + resolution: {integrity: sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.61.0 - '@typescript-eslint/visitor-keys': 5.61.0 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/visitor-keys': 6.7.2 dev: true - /@typescript-eslint/type-utils@5.61.0(eslint@8.44.0)(typescript@5.1.6): - resolution: {integrity: sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/type-utils@6.7.2(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: '*' + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.61.0(typescript@5.1.6) - '@typescript-eslint/utils': 5.61.0(eslint@8.44.0)(typescript@5.1.6) + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.44.0 - tsutils: 3.21.0(typescript@5.1.6) - typescript: 5.1.6 + eslint: 8.49.0 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.61.0: - resolution: {integrity: sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/types@6.7.2: + resolution: {integrity: sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==} + engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@5.61.0(typescript@5.1.6): - resolution: {integrity: sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/typescript-estree@6.7.2(typescript@5.2.2): + resolution: {integrity: sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.61.0 - '@typescript-eslint/visitor-keys': 5.61.0 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - tsutils: 3.21.0(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.61.0(eslint@8.44.0)(typescript@5.1.6): - resolution: {integrity: sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/utils@6.7.2(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.44.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 5.61.0 - '@typescript-eslint/types': 5.61.0 - '@typescript-eslint/typescript-estree': 5.61.0(typescript@5.1.6) - eslint: 8.44.0 - eslint-scope: 5.1.1 + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@types/json-schema': 7.0.13 + '@types/semver': 7.5.2 + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + eslint: 8.49.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.61.0: - resolution: {integrity: sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/visitor-keys@6.7.2: + resolution: {integrity: sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.61.0 - eslint-visitor-keys: 3.4.1 + '@typescript-eslint/types': 6.7.2 + eslint-visitor-keys: 3.4.3 dev: true /abort-controller@3.0.0: @@ -1707,13 +1749,13 @@ packages: is-array-buffer: 3.0.2 dev: true - /array-includes@3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} + /array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 get-intrinsic: 1.2.1 is-string: 1.0.7 dev: true @@ -1723,36 +1765,55 @@ packages: engines: {node: '>=8'} dev: true - /array.prototype.flat@1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + /array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 es-shim-unscopables: 1.0.0 dev: true - /array.prototype.flatmap@1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} + /array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 es-shim-unscopables: 1.0.0 dev: true - /array.prototype.tosorted@1.1.1: - resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} + /array.prototype.tosorted@1.1.2: + resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 es-shim-unscopables: 1.0.0 get-intrinsic: 1.2.1 dev: true + /arraybuffer.prototype.slice@1.0.2: + resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /asynciterator.prototype@1.0.0: + resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + dependencies: + has-symbols: 1.0.3 + dev: true + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false @@ -1762,10 +1823,10 @@ packages: engines: {node: '>= 0.4'} dev: true - /axios@1.4.0: - resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + /axios@1.5.0: + resolution: {integrity: sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==} dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.3 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -1786,22 +1847,34 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: true + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true - /bootstrap-icons@1.10.5: - resolution: {integrity: sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==} + /bootstrap-icons@1.11.1: + resolution: {integrity: sha512-F0DDp7nKUX+x/QtpfRZ+XHFya60ng9nfdpdS59vDDfs4Uhuxp7zym/QavMsu/xx51txkoM9eVmpE7D08N35blw==} dev: false - /bootstrap@5.3.0(@popperjs/core@2.11.8): - resolution: {integrity: sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==} + /bootstrap@5.3.2(@popperjs/core@2.11.8): + resolution: {integrity: sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==} peerDependencies: - '@popperjs/core': ^2.11.7 + '@popperjs/core': ^2.11.8 dependencies: '@popperjs/core': 2.11.8 dev: false + /bplist-parser@0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} + dependencies: + big-integer: 1.6.51 + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1816,24 +1889,31 @@ packages: fill-range: 7.0.1 dev: true - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} + /browserslist@4.21.10: + resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001513 - electron-to-chromium: 1.4.454 + caniuse-lite: 1.0.30001538 + electron-to-chromium: 1.4.525 node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.9) + update-browserslist-db: 1.0.11(browserslist@4.21.10) dev: true - /buffer@6.0.0: - resolution: {integrity: sha512-Sxdxq98A+Y9kRjO/3+mc2IAxIyTAKqzBiYKpeo5EluWnw9535rI4fN8DeMGsiQqpqqaWtFtTdxQgHnku6IEjCA==} + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 dev: true + /bundle-name@3.0.0: + resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} + engines: {node: '>=12'} + dependencies: + run-applescript: 5.0.0 + dev: true + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -1846,8 +1926,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite@1.0.30001513: - resolution: {integrity: sha512-pnjGJo7SOOjAGytZZ203Em95MRM8Cr6jhCXNF/FAXTpCTRTECnqQWLpiTRqrFtdYcth8hf4WECUpkezuYsMVww==} + /caniuse-lite@1.0.30001538: + resolution: {integrity: sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==} dev: true /chalk@2.4.2: @@ -1931,24 +2011,30 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /core-js-pure@3.31.1: - resolution: {integrity: sha512-w+C62kvWti0EPs4KPMCMVv9DriHSXfQOCQ94bGGBiEW5rrbtt/Rz8n5Krhfw9cpFyzXBjf3DB3QnPdEzGDY4Fw==} + /core-js-pure@3.32.2: + resolution: {integrity: sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ==} requiresBuild: true dev: false - /core-js@3.31.1: - resolution: {integrity: sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ==} + /core-js@3.32.2: + resolution: {integrity: sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==} requiresBuild: true dev: false - /cosmiconfig@8.2.0: - resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} + /cosmiconfig@8.3.6(typescript@5.2.2): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 + typescript: 5.2.2 dev: true /create-require@1.1.1: @@ -2017,10 +2103,43 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + /default-browser-id@3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} + dependencies: + bplist-parser: 0.2.0 + untildify: 4.0.0 + dev: true + + /default-browser@4.0.0: + resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} + engines: {node: '>=14.16'} + dependencies: + bundle-name: 3.0.0 + default-browser-id: 3.0.0 + execa: 7.2.0 + titleize: 3.0.0 + dev: true + + /define-data-property@1.1.0: + resolution: {integrity: sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==} engines: {node: '>= 0.4'} dependencies: + get-intrinsic: 1.2.1 + gopd: 1.0.1 + has-property-descriptors: 1.0.0 + dev: true + + /define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dev: true + + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.0 has-property-descriptors: 1.0.0 object-keys: 1.1.1 dev: true @@ -2065,7 +2184,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.22.15 csstype: 3.1.2 dev: false @@ -2105,8 +2224,8 @@ packages: engines: {node: '>=6'} dev: true - /electron-to-chromium@1.4.454: - resolution: {integrity: sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==} + /electron-to-chromium@1.4.525: + resolution: {integrity: sha512-GIZ620hDK4YmIqAWkscG4W6RwY6gOx1y5J6f4JUQwctiJrqH2oxZYU4mXHi35oV32tr630UcepBzSBGJ/WYcZA==} dev: true /entities@2.2.0: @@ -2124,16 +2243,17 @@ packages: is-arrayish: 0.2.1 dev: true - /es-abstract@1.21.2: - resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} + /es-abstract@1.22.2: + resolution: {integrity: sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==} engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.2 available-typed-arrays: 1.0.5 call-bind: 1.0.2 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 - function.prototype.name: 1.1.5 + function.prototype.name: 1.1.6 get-intrinsic: 1.2.1 get-symbol-description: 1.0.0 globalthis: 1.0.3 @@ -2149,19 +2269,42 @@ packages: is-regex: 1.1.4 is-shared-array-buffer: 1.0.2 is-string: 1.0.7 - is-typed-array: 1.1.10 + is-typed-array: 1.1.12 is-weakref: 1.0.2 object-inspect: 1.12.3 object-keys: 1.1.1 object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 + regexp.prototype.flags: 1.5.1 + safe-array-concat: 1.0.1 safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 typed-array-length: 1.0.4 unbox-primitive: 1.0.2 - which-typed-array: 1.1.9 + which-typed-array: 1.1.11 + dev: true + + /es-iterator-helpers@1.0.15: + resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} + dependencies: + asynciterator.prototype: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + es-set-tostringtag: 2.0.1 + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + iterator.prototype: 1.1.2 + safe-array-concat: 1.0.1 dev: true /es-set-tostringtag@2.0.1: @@ -2203,96 +2346,93 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier@8.8.0(eslint@8.44.0): - resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} + /eslint-config-prettier@9.0.0(eslint@8.49.0): + resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.44.0 + eslint: 8.49.0 dev: true - /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0)(eslint@8.44.0)(prettier@3.0.0): - resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} - engines: {node: '>=12.0.0'} + /eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.49.0)(prettier@3.0.3): + resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - eslint: '>=7.28.0' + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' eslint-config-prettier: '*' - prettier: '>=2.0.0' + prettier: '>=3.0.0' peerDependenciesMeta: + '@types/eslint': + optional: true eslint-config-prettier: optional: true dependencies: - eslint: 8.44.0 - eslint-config-prettier: 8.8.0(eslint@8.44.0) - prettier: 3.0.0 + eslint: 8.49.0 + eslint-config-prettier: 9.0.0(eslint@8.49.0) + prettier: 3.0.3 prettier-linter-helpers: 1.0.0 + synckit: 0.8.5 dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.44.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.49.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.44.0 + eslint: 8.49.0 dev: true - /eslint-plugin-react@7.32.2(eslint@8.44.0): - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + /eslint-plugin-react@7.33.2(eslint@8.49.0): + resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 + array-includes: 3.1.7 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.2 doctrine: 2.1.0 - eslint: 8.44.0 + es-iterator-helpers: 1.0.15 + eslint: 8.49.0 estraverse: 5.3.0 - jsx-ast-utils: 3.3.4 + jsx-ast-utils: 3.3.5 minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - object.hasown: 1.1.2 - object.values: 1.1.6 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + object.hasown: 1.1.3 + object.values: 1.1.7 prop-types: 15.8.1 resolve: 2.0.0-next.4 - semver: 6.3.0 - string.prototype.matchall: 4.0.8 + semver: 6.3.1 + string.prototype.matchall: 4.0.10 dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.44.0: - resolution: {integrity: sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==} + /eslint@8.49.0: + resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.44.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.1.0 - '@eslint/js': 8.44.0 - '@humanwhocodes/config-array': 0.11.10 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/regexpp': 4.8.1 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.49.0 + '@humanwhocodes/config-array': 0.11.11 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 @@ -2301,19 +2441,18 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 - espree: 9.6.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.20.0 + globals: 13.21.0 graphemer: 1.4.0 ignore: 5.2.4 - import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -2325,19 +2464,18 @@ packages: natural-compare: 1.4.0 optionator: 0.9.3 strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color dev: true - /espree@9.6.0: - resolution: {integrity: sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==} + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.10.0 acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.3 dev: true /esquery@1.5.0: @@ -2354,11 +2492,6 @@ packages: estraverse: 5.3.0 dev: true - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true - /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2379,6 +2512,36 @@ packages: engines: {node: '>=12.0.0'} dev: false + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -2387,8 +2550,8 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true - /fast-glob@3.3.0: - resolution: {integrity: sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==} + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2423,7 +2586,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.0.4 + flat-cache: 3.1.0 dev: true /fill-range@7.0.1: @@ -2441,20 +2604,21 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + /flat-cache@3.1.0: + resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} + engines: {node: '>=12.0.0'} dependencies: - flatted: 3.2.7 + flatted: 3.2.9 + keyv: 4.5.3 rimraf: 3.0.2 dev: true - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + /flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true - /follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + /follow-redirects@1.15.3: + resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2486,13 +2650,13 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 functions-have-names: 1.2.3 dev: true @@ -2514,6 +2678,11 @@ packages: engines: {node: '>=6'} dev: true + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -2547,8 +2716,8 @@ packages: path-is-absolute: 1.0.1 dev: true - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + /globals@13.21.0: + resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -2558,7 +2727,7 @@ packages: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} dependencies: - define-properties: 1.2.0 + define-properties: 1.2.1 dev: true /globby@11.1.0: @@ -2567,7 +2736,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.0 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -2633,7 +2802,7 @@ packages: void-elements: 3.1.0 dev: false - /htmlnano@2.0.4(svgo@2.8.0): + /htmlnano@2.0.4(svgo@2.8.0)(typescript@5.2.2): resolution: {integrity: sha512-WGCkyGFwjKW1GeCBsPYacMvaMnZtFJ0zIRnC2NCddkA+IOEhTqskXrS7lep+3yYZw/nQ3dW1UAX4yA/GJyR8BA==} peerDependencies: cssnano: ^6.0.0 @@ -2662,10 +2831,12 @@ packages: uncss: optional: true dependencies: - cosmiconfig: 8.2.0 + cosmiconfig: 8.3.6(typescript@5.2.2) posthtml: 0.16.6 svgo: 2.8.0 timsort: 0.3.0 + transitivePeerDependencies: + - typescript dev: true /htmlparser2@7.2.0: @@ -2677,6 +2848,16 @@ packages: entities: 3.0.1 dev: true + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} + dev: true + /hyphenate-style-name@1.0.4: resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} dev: false @@ -2684,13 +2865,13 @@ packages: /i18next-browser-languagedetector@7.1.0: resolution: {integrity: sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.22.15 dev: false - /i18next@23.2.8: - resolution: {integrity: sha512-wU0pMlJ91ZbB89i77G3YQ11/pBQrzgWpxJYl7HFyA9aU9v3aHMI/oBKQmAJNURr0A8cLG4EHjgSMK8IqQTp4PQ==} + /i18next@23.5.1: + resolution: {integrity: sha512-JelYzcaCoFDaa+Ysbfz2JsGAKkrHiMG6S61+HLBUEIPaF40WMwW9hCPymlQGrP+wWawKxKPuSuD71WZscCsWHg==} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.22.15 dev: false /ieee754@1.2.1: @@ -2740,7 +2921,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.1 - is-typed-array: 1.1.10 + is-typed-array: 1.1.12 dev: true /is-arrayish@0.2.1: @@ -2751,6 +2932,13 @@ packages: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false + /is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: @@ -2770,8 +2958,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-core-module@2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: has: 1.0.3 dev: true @@ -2783,11 +2971,36 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: true + + /is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: true + /is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2795,10 +3008,22 @@ packages: is-extglob: 2.1.1 dev: true + /is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + dependencies: + is-docker: 3.0.0 + dev: true + /is-json@2.0.1: resolution: {integrity: sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==} dev: true + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -2829,12 +3054,26 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: call-bind: 1.0.2 dev: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -2849,15 +3088,15 @@ packages: has-symbols: 1.0.3 dev: true - /is-typed-array@1.1.10: - resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} engines: {node: '>= 0.4'} dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 + which-typed-array: 1.1.11 + dev: true + + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} dev: true /is-weakref@1.0.2: @@ -2866,10 +3105,38 @@ packages: call-bind: 1.0.2 dev: true + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.4 + set-function-name: 2.0.1 + dev: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2880,6 +3147,10 @@ packages: argparse: 2.0.1 dev: true + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -2898,14 +3169,20 @@ packages: hasBin: true dev: true - /jsx-ast-utils@3.3.4: - resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} + /jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} dependencies: - array-includes: 3.1.6 - array.prototype.flat: 1.3.1 + array-includes: 3.1.7 + array.prototype.flat: 1.3.2 object.assign: 4.1.4 - object.values: 1.1.6 + object.values: 1.1.7 + dev: true + + /keyv@4.5.3: + resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} + dependencies: + json-buffer: 3.0.1 dev: true /levn@0.4.1: @@ -2916,8 +3193,8 @@ packages: type-check: 0.4.0 dev: true - /lightningcss-darwin-arm64@1.21.5: - resolution: {integrity: sha512-z05hyLX85WY0UfhkFUOrWEFqD69lpVAmgl3aDzMKlIZJGygbhbegqb4PV8qfUrKKNBauut/qVNPKZglhTaDDxA==} + /lightningcss-darwin-arm64@1.22.0: + resolution: {integrity: sha512-aH2be3nNny+It5YEVm8tBSSdRlBVWQV8m2oJ7dESiYRzyY/E/bQUe2xlw5caaMuhlM9aoTMtOH25yzMhir0qPg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] @@ -2925,8 +3202,8 @@ packages: dev: true optional: true - /lightningcss-darwin-x64@1.21.5: - resolution: {integrity: sha512-MSJhmej/U9MrdPxDk7+FWhO8+UqVoZUHG4VvKT5RQ4RJtqtANTiWiI97LvoVNMtdMnHaKs1Pkji6wHUFxjJsHQ==} + /lightningcss-darwin-x64@1.22.0: + resolution: {integrity: sha512-9KHRFA0Y6mNxRHeoQMp0YaI0R0O2kOgUlYPRjuasU4d+pI8NRhVn9bt0yX9VPs5ibWX1RbDViSPtGJvYYrfVAQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] @@ -2934,8 +3211,17 @@ packages: dev: true optional: true - /lightningcss-linux-arm-gnueabihf@1.21.5: - resolution: {integrity: sha512-xN6+5/JsMrbZHL1lPl+MiNJ3Xza12ueBKPepiyDCFQzlhFRTj7D0LG+cfNTzPBTO8KcYQynLpl1iBB8LGp3Xtw==} + /lightningcss-freebsd-x64@1.22.0: + resolution: {integrity: sha512-xaYL3xperGwD85rQioDb52ozF3NAJb+9wrge3jD9lxGffplu0Mn35rXMptB8Uc2N9Mw1i3Bvl7+z1evlqVl7ww==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm-gnueabihf@1.22.0: + resolution: {integrity: sha512-epQGvXIjOuxrZpMpMnRjK54ZqzhiHhCPLtHvw2fb6NeK2kK9YtF0wqmeTBiQ1AkbWfnnXGTstYaFNiadNK+StQ==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] @@ -2943,8 +3229,8 @@ packages: dev: true optional: true - /lightningcss-linux-arm64-gnu@1.21.5: - resolution: {integrity: sha512-KfzFNhC4XTbmG3ma/xcTs/IhCwieW89XALIusKmnV0N618ZDXEB0XjWOYQRCXeK9mfqPdbTBpurEHV/XZtkniQ==} + /lightningcss-linux-arm64-gnu@1.22.0: + resolution: {integrity: sha512-AArGtKSY4DGTA8xP8SDyNyKtpsUl1Rzq6FW4JomeyUQ4nBrR71uPChksTpj3gmWuGhZeRKLeCUI1DBid/zhChg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] @@ -2952,8 +3238,8 @@ packages: dev: true optional: true - /lightningcss-linux-arm64-musl@1.21.5: - resolution: {integrity: sha512-bc0GytQO5Mn9QM6szaZ+31fQHNdidgpM1sSCwzPItz8hg3wOvKl8039rU0veMJV3ZgC9z0ypNRceLrSHeRHmXw==} + /lightningcss-linux-arm64-musl@1.22.0: + resolution: {integrity: sha512-RRraNgP8hnBPhInTTUdlFm+z16C/ghbxBG51Sw00hd7HUyKmEUKRozyc5od+/N6pOrX/bIh5vIbtMXIxsos0lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] @@ -2961,8 +3247,8 @@ packages: dev: true optional: true - /lightningcss-linux-x64-gnu@1.21.5: - resolution: {integrity: sha512-JwMbgypPQgc2kW2av3OwzZ8cbrEuIiDiXPJdXRE6aVxu67yHauJawQLqJKTGUhiAhy6iLDG8Wg0a3/ziL+m+Kw==} + /lightningcss-linux-x64-gnu@1.22.0: + resolution: {integrity: sha512-grdrhYGRi2KrR+bsXJVI0myRADqyA7ekprGxiuK5QRNkv7kj3Yq1fERDNyzZvjisHwKUi29sYMClscbtl+/Zpw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] @@ -2970,8 +3256,8 @@ packages: dev: true optional: true - /lightningcss-linux-x64-musl@1.21.5: - resolution: {integrity: sha512-Ib8b6IQ/OR/VrPU6YBgy4T3QnuHY7DUa95O+nz+cwrTkMSN6fuHcTcIaz4t8TJ6HI5pl3uxUOZjmtls2pyQWow==} + /lightningcss-linux-x64-musl@1.22.0: + resolution: {integrity: sha512-t5f90X+iQUtIyR56oXIHMBUyQFX/zwmPt72E6Dane3P8KNGlkijTg2I75XVQS860gNoEFzV7Mm5ArRRA7u5CAQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] @@ -2979,8 +3265,8 @@ packages: dev: true optional: true - /lightningcss-win32-x64-msvc@1.21.5: - resolution: {integrity: sha512-A8cSi8lUpBeVmoF+DqqW7cd0FemDbCuKr490IXdjyeI+KL8adpSKUs8tcqO0OXPh1EoDqK7JNkD/dELmd4Iz5g==} + /lightningcss-win32-x64-msvc@1.22.0: + resolution: {integrity: sha512-64HTDtOOZE9PUCZJiZZQpyqXBbdby1lnztBccnqh+NtbKxjnGzP92R2ngcgeuqMPecMNqNWxgoWgTGpC+yN5Sw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] @@ -2988,20 +3274,21 @@ packages: dev: true optional: true - /lightningcss@1.21.5: - resolution: {integrity: sha512-/pEUPeih2EwIx9n4T82aOG6CInN83tl/mWlw6B5gWLf36UplQi1L+5p3FUHsdt4fXVfOkkh9KIaM3owoq7ss8A==} + /lightningcss@1.22.0: + resolution: {integrity: sha512-+z0qvwRVzs4XGRXelnWRNwqsXUx8k3bSkbP8vD42kYKSk3z9OM2P3e/gagT7ei/gwh8DTS80LZOFZV6lm8Z8Fg==} engines: {node: '>= 12.0.0'} dependencies: detect-libc: 1.0.3 optionalDependencies: - lightningcss-darwin-arm64: 1.21.5 - lightningcss-darwin-x64: 1.21.5 - lightningcss-linux-arm-gnueabihf: 1.21.5 - lightningcss-linux-arm64-gnu: 1.21.5 - lightningcss-linux-arm64-musl: 1.21.5 - lightningcss-linux-x64-gnu: 1.21.5 - lightningcss-linux-x64-musl: 1.21.5 - lightningcss-win32-x64-msvc: 1.21.5 + lightningcss-darwin-arm64: 1.22.0 + lightningcss-darwin-x64: 1.22.0 + lightningcss-freebsd-x64: 1.22.0 + lightningcss-linux-arm-gnueabihf: 1.22.0 + lightningcss-linux-arm64-gnu: 1.22.0 + lightningcss-linux-arm64-musl: 1.22.0 + lightningcss-linux-x64-gnu: 1.22.0 + lightningcss-linux-x64-musl: 1.22.0 + lightningcss-win32-x64-msvc: 1.22.0 dev: true /lines-and-columns@1.2.4: @@ -3059,9 +3346,9 @@ packages: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /marked@5.1.1: - resolution: {integrity: sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg==} - engines: {node: '>= 18'} + /marked@9.0.3: + resolution: {integrity: sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==} + engines: {node: '>= 16'} hasBin: true dev: false @@ -3075,6 +3362,10 @@ packages: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3100,6 +3391,16 @@ packages: mime-db: 1.52.0 dev: false + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -3136,16 +3437,12 @@ packages: msgpackr-extract: 3.0.2 dev: true - /msgpackr@1.9.5: - resolution: {integrity: sha512-/IJ3cFSN6Ci3eG2wLhbFEL6GT63yEaoN/R5My2QkV6zro+OJaVRLPlwvxY7EtHYSmDlQpk8stvOQTL2qJFkDRg==} + /msgpackr@1.9.9: + resolution: {integrity: sha512-sbn6mioS2w0lq1O6PpGtsv6Gy8roWM+o3o4Sqjd6DudrL/nOugY+KyJUimoWzHnf9OkO0T6broHFnYE/R05t9A==} optionalDependencies: msgpackr-extract: 3.0.2 dev: true - /natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true - /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -3158,8 +3455,8 @@ packages: resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==} dev: true - /node-fetch@2.6.12: - resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 @@ -3186,6 +3483,20 @@ packages: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -3214,43 +3525,43 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 dev: true - /object.entries@1.1.6: - resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} + /object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true - /object.fromentries@2.0.6: - resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} + /object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true - /object.hasown@1.1.2: - resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} + /object.hasown@1.1.3: + resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} dependencies: - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true - /object.values@1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + /object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true /once@1.4.0: @@ -3259,6 +3570,30 @@ packages: wrappy: 1.0.2 dev: true + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /open@9.1.0: + resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} + engines: {node: '>=14.16'} + dependencies: + default-browser: 4.0.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 2.2.0 + dev: true + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -3289,7 +3624,7 @@ packages: p-limit: 3.1.0 dev: true - /parcel@2.9.3: + /parcel@2.9.3(typescript@5.2.2): resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==} engines: {node: '>= 12.0.0'} hasBin: true @@ -3297,7 +3632,7 @@ packages: '@parcel/core': optional: true dependencies: - '@parcel/config-default': 2.9.3(@parcel/core@2.9.3) + '@parcel/config-default': 2.9.3(@parcel/core@2.9.3)(typescript@5.2.2) '@parcel/core': 2.9.3 '@parcel/diagnostic': 2.9.3 '@parcel/events': 2.9.3 @@ -3319,6 +3654,7 @@ packages: - relateurl - srcset - terser + - typescript - uncss dev: true @@ -3333,7 +3669,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -3354,6 +3690,11 @@ packages: engines: {node: '>=8'} dev: true + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -3417,8 +3758,8 @@ packages: fast-diff: 1.3.0 dev: true - /prettier@3.0.0: - resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + /prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true dev: true @@ -3473,8 +3814,8 @@ packages: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} dev: false - /react-i18next@13.0.1(i18next@23.2.8)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-gMO6N2GfSfuH7xlHSsZ/mZf+Py9bLm/+EDKIn5fNTuDTjcCcwmMU5UEuGCDk5mdfivbo7ySyYXBN7B9tbGUxiA==} + /react-i18next@13.2.2(i18next@23.5.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+nFUkbRByFwnrfDcYqvzBuaeZb+nACHx+fAWN/pZMddWOCJH5hoc21+Sa/N/Lqi6ne6/9wC/qRGOoQhJa6IkEQ==} peerDependencies: i18next: '>= 23.2.3' react: '>= 16.8.0' @@ -3486,9 +3827,9 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.22.15 html-parse-stringify: 3.0.1 - i18next: 23.2.8 + i18next: 23.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -3528,26 +3869,26 @@ packages: shallow-equal: 1.2.1 dev: false - /react-router-dom@6.14.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==} - engines: {node: '>=14'} + /react-router-dom@6.16.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==} + engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.7.1 + '@remix-run/router': 1.9.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router: 6.14.1(react@18.2.0) + react-router: 6.16.0(react@18.2.0) dev: false - /react-router@6.14.1(react@18.2.0): - resolution: {integrity: sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==} - engines: {node: '>=14'} + /react-router@6.16.0(react@18.2.0): + resolution: {integrity: sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==} + engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.7.1 + '@remix-run/router': 1.9.0 react: 18.2.0 dev: false @@ -3557,7 +3898,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.22.15 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -3572,16 +3913,33 @@ packages: loose-envify: 1.4.0 dev: false + /reflect.getprototypeof@1.0.4: + resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + dev: true + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false - /regexp.prototype.flags@1.5.0: - resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + /regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - functions-have-names: 1.2.3 + define-properties: 1.2.1 + set-function-name: 2.0.1 dev: true /requires-port@1.0.0: @@ -3597,7 +3955,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -3614,6 +3972,13 @@ packages: glob: 7.2.3 dev: true + /run-applescript@5.0.0: + resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} + engines: {node: '>=12'} + dependencies: + execa: 5.1.1 + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -3623,9 +3988,19 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.6.0 + tslib: 2.6.2 dev: false + /safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true @@ -3644,8 +4019,8 @@ packages: loose-envify: 1.4.0 dev: false - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true dev: true @@ -3661,6 +4036,15 @@ packages: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} dev: false + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.0 + dev: true + /shallow-equal@1.2.1: resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} dev: false @@ -3685,6 +4069,10 @@ packages: object-inspect: 1.12.3 dev: true + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: @@ -3711,42 +4099,43 @@ packages: deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' dev: true - /string.prototype.matchall@4.0.8: - resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} + /string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 get-intrinsic: 1.2.1 has-symbols: 1.0.3 internal-slot: 1.0.5 - regexp.prototype.flags: 1.5.0 + regexp.prototype.flags: 1.5.1 + set-function-name: 2.0.1 side-channel: 1.0.4 dev: true - /string.prototype.trim@1.2.7: - resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + /string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true - /string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + /string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true - /string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 dev: true /strip-ansi@6.0.1: @@ -3756,6 +4145,16 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3794,6 +4193,14 @@ packages: stable: 0.1.8 dev: true + /synckit@0.8.5: + resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/utils': 2.4.2 + tslib: 2.6.2 + dev: true + /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -3807,6 +4214,11 @@ packages: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} dev: true + /titleize@3.0.0: + resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} + engines: {node: '>=12'} + dev: true + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3828,7 +4240,16 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /ts-node@10.9.1(@types/node@20.4.1)(typescript@5.1.6): + /ts-api-utils@1.0.3(typescript@5.2.2): + resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.2.2 + dev: true + + /ts-node@10.9.1(@types/node@20.6.3)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3847,34 +4268,20 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.4.1 + '@types/node': 20.6.3 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.1.6 + typescript: 5.2.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - - /tslib@2.6.0: - resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} - - /tsutils@3.21.0(typescript@5.1.6): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 5.1.6 - dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -3888,12 +4295,42 @@ packages: engines: {node: '>=10'} dev: true + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} dependencies: call-bind: 1.0.2 for-each: 0.3.3 - is-typed-array: 1.1.10 + is-typed-array: 1.1.12 dev: true /typescript-language-server@3.3.2: @@ -3902,11 +4339,11 @@ packages: hasBin: true dependencies: vscode-jsonrpc: 5.0.1 - vscode-languageserver-protocol: 3.17.3 + vscode-languageserver-protocol: 3.17.4 dev: true - /typescript@5.1.6: - resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -3925,13 +4362,18 @@ packages: engines: {node: '>= 4.0.0'} dev: false - /update-browserslist-db@1.0.11(browserslist@4.21.9): + /untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: true + + /update-browserslist-db@1.0.11(browserslist@4.21.10): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.9 + browserslist: 4.21.10 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -3968,20 +4410,20 @@ packages: engines: {node: '>=8.0.0 || >=10.0.0'} dev: true - /vscode-jsonrpc@8.1.0: - resolution: {integrity: sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==} + /vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} dev: true - /vscode-languageserver-protocol@3.17.3: - resolution: {integrity: sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==} + /vscode-languageserver-protocol@3.17.4: + resolution: {integrity: sha512-IpaHLPft+UBWf4dOIH15YEgydTbXGz52EMU2h16SfFpYu/yOQt3pY14049mtpJu+4CBHn+hq7S67e7O0AwpRqQ==} dependencies: - vscode-jsonrpc: 8.1.0 - vscode-languageserver-types: 3.17.3 + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.4 dev: true - /vscode-languageserver-types@3.17.3: - resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==} + /vscode-languageserver-types@3.17.4: + resolution: {integrity: sha512-9YXi5pA3XF2V+NUQg6g+lulNS0ncRCKASYdK3Cs7kiH9sVFXWq27prjkC/B8M/xJLRPPRSPCHVMuBTgRNFh2sQ==} dev: true /warning@4.0.3: @@ -4015,8 +4457,35 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.9: - resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + /which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.0 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.11 + dev: true + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array@1.1.11: + resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.5 @@ -4024,7 +4493,6 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - is-typed-array: 1.1.10 dev: true /which@2.0.2: @@ -4055,7 +4523,7 @@ packages: /xregexp@5.1.1: resolution: {integrity: sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==} dependencies: - '@babel/runtime-corejs3': 7.22.6 + '@babel/runtime-corejs3': 7.22.15 dev: false /xxhash-wasm@0.4.2: diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.tsx b/FrontEnd/src/components/dialog/ConfirmDialog.tsx index 97cad452..8b0a4219 100644 --- a/FrontEnd/src/components/dialog/ConfirmDialog.tsx +++ b/FrontEnd/src/components/dialog/ConfirmDialog.tsx @@ -25,27 +25,24 @@ export default function ConfirmDialog({ { - onConfirm(); - closeDialog(); - }, + action: "major", + text: "operationDialog.confirm", + color: "danger", + onClick: () => { + onConfirm(); + closeDialog(); }, }, ]} diff --git a/FrontEnd/src/components/dialog/DialogContainer.css b/FrontEnd/src/components/dialog/DialogContainer.css index b3c52511..f0d27a66 100644 --- a/FrontEnd/src/components/dialog/DialogContainer.css +++ b/FrontEnd/src/components/dialog/DialogContainer.css @@ -7,6 +7,7 @@ .cru-dialog-container-hr { margin: 1em 0; + border-color: var(--cru-text-minor-color); } .cru-dialog-container-button-row { @@ -16,4 +17,4 @@ .cru-dialog-container-button { margin-left: 1em; -} \ No newline at end of file +} diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css index 5bf0a7bb..01a6acd6 100644 --- a/FrontEnd/src/pages/timeline/Timeline.css +++ b/FrontEnd/src/pages/timeline/Timeline.css @@ -1,17 +1,19 @@ .timeline-container { + --timeline-background-color: hsl(0, 0%, 95%); --timeline-shadow-color: hsla(0, 0%, 0%, 0.5); --timeline-card-shadow: 2px 1px 10px -2px var(--timeline-shadow-color); - --timeline-post-line-color: hsl(56, 82%, 55%); - --timeline-post-line-shadow: 2px 1px 10px -1px var(--timeline-shadow-color); --timeline-post-card-background-color: hsl(0, 0%, 100%); - --timeline-post-card-shadow: 4px 2px 10px -2px var(--timeline-shadow-color); + --timeline-post-card-shadow: 0px 0px 11px -2px var(--timeline-shadow-color); --timeline-post-card-border-radius: 10px; --timeline-post-text-color: hsl(0, 0%, 0%); + --timeline-datetime-label-background-color: hsl(0, 0%, 30%); } @media (prefers-color-scheme: dark) { - .timeline { - --timeline-post-card-background-color: hsl(0, 0%, 12%); + .timeline-container { + --timeline-background-color: hsl(0, 0%, 0%); + --timeline-post-card-background-color: hsl(0, 0%, 15%); + --timeline-post-card-shadow: none; } } @@ -20,11 +22,7 @@ position: relative; width: 100%; padding-top: 10px; -} - -.timeline-content-image { - max-width: 80%; - max-height: 200px; + background: var(--timeline-background-color); } .timeline-post-item-options-mask { @@ -58,4 +56,3 @@ vertical-align: middle; margin-right: 0.6em; } - diff --git a/FrontEnd/src/pages/timeline/TimelineDateLabel.css b/FrontEnd/src/pages/timeline/TimelineDateLabel.css index 1c66045b..47a4cb44 100644 --- a/FrontEnd/src/pages/timeline/TimelineDateLabel.css +++ b/FrontEnd/src/pages/timeline/TimelineDateLabel.css @@ -1,9 +1,9 @@ .timeline-post-date-badge { display: inline-block; - padding: 0.1em 0.4em; + padding: 0.2em 0.5em; border-radius: 0.4em; - background: #7c7c7c; + background: var(--timeline-datetime-label-background-color); color: white; font-size: 0.8em; margin-left: 5em; -} \ No newline at end of file +} diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx index 7912260a..66262ccd 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostList.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -53,7 +53,6 @@ export default function TimelinePostList(props: TimelinePostListViewProps) { return (
-
{groupedPosts.map((group) => { return ( -- cgit v1.2.3