From 6664fb3506b1ea4af712fa849bd7c761a06c9843 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 31 Aug 2023 23:56:13 +0800 Subject: ... --- FrontEnd/src/components/dialog/Dialog.tsx | 4 +- FrontEnd/src/components/dialog/FullPageDialog.css | 28 +-- FrontEnd/src/components/dialog/FullPageDialog.tsx | 80 +++++---- FrontEnd/src/components/hooks/responsive.ts | 4 +- FrontEnd/src/pages/timeline/CollapseButton.tsx | 25 --- FrontEnd/src/pages/timeline/Timeline.tsx | 4 +- FrontEnd/src/pages/timeline/TimelineCard.css | 63 ------- FrontEnd/src/pages/timeline/TimelineCard.tsx | 158 ----------------- FrontEnd/src/pages/timeline/TimelineInfoCard.css | 63 +++++++ FrontEnd/src/pages/timeline/TimelineInfoCard.tsx | 199 ++++++++++++++++++++++ 10 files changed, 314 insertions(+), 314 deletions(-) delete mode 100644 FrontEnd/src/pages/timeline/CollapseButton.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelineCard.css delete mode 100644 FrontEnd/src/pages/timeline/TimelineCard.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineInfoCard.css create mode 100644 FrontEnd/src/pages/timeline/TimelineInfoCard.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/components/dialog/Dialog.tsx b/FrontEnd/src/components/dialog/Dialog.tsx index 85e8ca46..043a8eec 100644 --- a/FrontEnd/src/components/dialog/Dialog.tsx +++ b/FrontEnd/src/components/dialog/Dialog.tsx @@ -2,7 +2,7 @@ import { ReactNode, useRef } from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; -import { ThemeColor } from "../common"; +import { ThemeColor, UiLogicError } from "../common"; import { useCloseDialog } from "./DialogProvider"; @@ -10,7 +10,7 @@ import "./Dialog.css"; const optionalPortalElement = document.getElementById("portal"); if (optionalPortalElement == null) { - throw new Error("Portal element not found"); + throw new UiLogicError(); } const portalElement = optionalPortalElement; diff --git a/FrontEnd/src/components/dialog/FullPageDialog.css b/FrontEnd/src/components/dialog/FullPageDialog.css index 2f1fc636..ce07c6ac 100644 --- a/FrontEnd/src/components/dialog/FullPageDialog.css +++ b/FrontEnd/src/components/dialog/FullPageDialog.css @@ -1,44 +1,30 @@ -.cru-full-page { +.cru-dialog-full-page { position: fixed; z-index: 1030; left: 0; top: 0; right: 0; bottom: 0; - background-color: white; + background-color: var(--cru-background-color); padding-top: 56px; } -.cru-full-page-top-bar { +.cru-dialog-full-page-top-bar { height: 56px; position: absolute; top: 0; left: 0; right: 0; z-index: 1; - background-color: var(--cru-primary-color); + background-color: var(--cru-theme-color); display: flex; align-items: center; } -.cru-full-page-content-container { +.cru-dialog-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); +.cru-dialog-full-page-back-button { + margin-left: 0.5em; } diff --git a/FrontEnd/src/components/dialog/FullPageDialog.tsx b/FrontEnd/src/components/dialog/FullPageDialog.tsx index cba57e21..d18bcf73 100644 --- a/FrontEnd/src/components/dialog/FullPageDialog.tsx +++ b/FrontEnd/src/components/dialog/FullPageDialog.tsx @@ -1,53 +1,51 @@ -import * as React from "react"; +import { ReactNode } from "react"; import { createPortal } from "react-dom"; -import classnames from "classnames"; -import { CSSTransition } from "react-transition-group"; +import classNames from "classnames"; + +import { ThemeColor, UiLogicError } from "../common"; +import { IconButton } from "../button"; + +import { useCloseDialog } from "./DialogProvider"; import "./FullPageDialog.css"; -import IconButton from "../button/IconButton"; -export interface FullPageDialogProps { - show: boolean; - onBack: () => void; +const optionalPortalElement = document.getElementById("portal"); +if (optionalPortalElement == null) { + throw new UiLogicError(); +} +const portalElement = optionalPortalElement; + +interface FullPageDialogProps { + color?: ThemeColor; contentContainerClassName?: string; - children: React.ReactNode; + children: ReactNode; } -const FullPageDialog: React.FC = ({ - show, - onBack, +export default function FullPageDialog({ + color, children, contentContainerClassName, -}) => { +}: FullPageDialogProps) { + const closeDialog = useCloseDialog(); + return createPortal( - -
-
- -
-
- {children} -
+
+
+ +
+
+ {children}
- , - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")!, +
, + portalElement, ); -}; - -export default FullPageDialog; +} diff --git a/FrontEnd/src/components/hooks/responsive.ts b/FrontEnd/src/components/hooks/responsive.ts index 6bcce96c..42c134ef 100644 --- a/FrontEnd/src/components/hooks/responsive.ts +++ b/FrontEnd/src/components/hooks/responsive.ts @@ -2,6 +2,6 @@ import { useMediaQuery } from "react-responsive"; import { breakpoints } from "../breakpoints"; -export function useMobile(): boolean { - return useMediaQuery({ maxWidth: breakpoints.sm }); +export function useMobile(onChange?: (mobile: boolean) => void): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }, undefined, onChange); } diff --git a/FrontEnd/src/pages/timeline/CollapseButton.tsx b/FrontEnd/src/pages/timeline/CollapseButton.tsx deleted file mode 100644 index 1c4fa2ba..00000000 --- a/FrontEnd/src/pages/timeline/CollapseButton.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { CSSProperties } from "react"; - -import IconButton from "~src/components/button/IconButton"; - -export default function CollapseButton({ - collapse, - onClick, - className, - style, -}: { - collapse: boolean; - onClick: () => void; - className?: string; - style?: CSSProperties; -}) { - return ( - - ); -} diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index caf4f502..4fe5c521 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -19,7 +19,7 @@ import { useScrollToBottom } from "~src/components/hooks"; import TimelinePostList from "./TimelinePostList"; import TimelinePostEdit from "./TimelinePostCreateView"; -import TimelineCard from "./TimelineCard"; +import TimelineInfoCard from "./TimelineInfoCard"; import "./Timeline.css"; @@ -159,7 +159,7 @@ export function Timeline(props: TimelineProps) { return (
{timeline && ( - void; -} - -export default function TimelineCard(props: TimelinePageCardProps) { - const { timeline, connectionStatus, onReload } = props; - - const user = useUser(); - - const [collapse, setCollapse] = useState(true); - const toggleCollapse = (): void => { - setCollapse((o) => !o); - }; - - const isMobile = useMobile(); - - const { controller, createDialogSwitch } = useDialog({ - member: ( - - - - ), - property: ( - - ), - delete: , - }); - - const content = ( -
-

- {timeline.title} - {timeline.nameV2} -

-
- - - {timeline.owner.nickname} - - - @{timeline.owner.username} - -
-

{timeline.description}

-
- {user && ( - { - getHttpBookmarkClient() - [timeline.isBookmark ? "delete" : "post"]( - user.username, - timeline.owner.username, - timeline.nameV2, - ) - .then(onReload, () => { - pushAlert({ - message: timeline.isBookmark - ? "timeline.removeBookmarkFail" - : "timeline.addBookmarkFail", - color: "danger", - }); - }); - }} - /> - )} - - {timeline.manageable && ( - - - - )} -
-
- ); - - return ( - -
- - -
- {isMobile ? ( - - {content} - - ) : ( -
{content}
- )} - -
- ); -} diff --git a/FrontEnd/src/pages/timeline/TimelineInfoCard.css b/FrontEnd/src/pages/timeline/TimelineInfoCard.css new file mode 100644 index 00000000..29e59b62 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelineInfoCard.css @@ -0,0 +1,63 @@ +.timeline-card { + position: fixed; + z-index: 1029; + top: 56px; + right: 0; + margin: 0.5em; + padding: 0.5em; + box-shadow: var(--timeline-card-shadow); +} + +@media (min-width: 576px) { + .timeline-card-expand { + min-width: 400px; + } +} + +.timeline-card-title { + display: inline-block; + vertical-align: middle; + color: var(--cru-text-major-color); + margin: 0.5em 1em; +} + +.timeline-card-title-name { + margin-inline-start: 1em; + color: var(--cru-text-minor-color); +} + +.timeline-card-user { + display: flex; + align-items: center; + margin: 0 1em 0.5em; +} + +.timeline-card-user-avatar { + width: 2em; + height: 2em; + border-radius: 50%; +} + +.timeline-card-user-nickname { + margin-inline: 0.6em; +} + +.timeline-card-description { + margin: 0 1em 0.5em; +} + +.timeline-card-top-right-area { + float: right; + display: flex; + align-items: center; + margin: 0 1em; +} + +.timeline-card-buttons { + display: flex; + justify-content: end; +} + +.timeline-card-button { + margin: 0 0.2em; +} \ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx b/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx new file mode 100644 index 00000000..b1310be9 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelineInfoCard.tsx @@ -0,0 +1,199 @@ +import { useState } from "react"; +import { HubConnectionState } from "@microsoft/signalr"; + +import { useUser } from "~src/services/user"; + +import { HttpTimelineInfo } from "~src/http/timeline"; +import { getHttpBookmarkClient } from "~src/http/bookmark"; + +import { pushAlert } from "~src/components/alert"; +import { useMobile } from "~src/components/hooks"; +import { IconButton } from "~src/components/button"; +import { + Dialog, + FullPageDialog, + DialogProvider, + useDialog, +} from "~src/components/dialog"; +import UserAvatar from "~src/components/user/UserAvatar"; +import PopupMenu from "~src/components/menu/PopupMenu"; +import Card from "~src/components/Card"; + +import TimelineDeleteDialog from "./TimelineDeleteDialog"; +import ConnectionStatusBadge from "./ConnectionStatusBadge"; +import TimelineMember from "./TimelineMember"; +import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; + +import "./TimelineInfoCard.css"; + +function CollapseButton({ + collapse, + onClick, + className, +}: { + collapse: boolean; + onClick: () => void; + className?: string; +}) { + return ( + + ); +} + +interface TimelineInfoCardProps { + timeline: HttpTimelineInfo; + connectionStatus: HubConnectionState; + onReload: () => void; +} + +function TimelineInfoContent({ + timeline, + onReload, +}: Omit) { + const user = useUser(); + + const { controller, createDialogSwitch } = useDialog({ + member: ( + + + + ), + property: ( + + ), + delete: , + }); + + return ( +
+

+ {timeline.title} + {timeline.nameV2} +

+
+ + + {timeline.owner.nickname} + + + @{timeline.owner.username} + +
+

{timeline.description}

+
+ {user && ( + { + getHttpBookmarkClient() + [timeline.isBookmark ? "delete" : "post"]( + user.username, + timeline.owner.username, + timeline.nameV2, + ) + .then(onReload, () => { + pushAlert({ + message: timeline.isBookmark + ? "timeline.removeBookmarkFail" + : "timeline.addBookmarkFail", + color: "danger", + }); + }); + }} + /> + )} + + {timeline.manageable && ( + + + + )} +
+ +
+ ); +} + +export default function TimelineInfoCard(props: TimelineInfoCardProps) { + const { timeline, connectionStatus, onReload } = props; + + const [collapse, setCollapse] = useState(true); + + const isMobile = useMobile((mobile) => { + if (!mobile) { + switchDialog(null); + } else { + setCollapse(true); + } + }); + + const { controller, switchDialog } = useDialog({ + "full-page": ( + + + + ), + }); + + return ( + +
+ + { + const open = collapse; + setCollapse(!open); + if (isMobile && open) { + switchDialog("full-page"); + } + }} + /> +
+ {!collapse && !isMobile && ( + + )} + +
+ ); +} -- cgit v1.2.3