From 9fb5b5d237d700fac6ee585dcbf31539e531fa35 Mon Sep 17 00:00:00 2001 From: hyrious Date: Tue, 30 Jan 2024 18:40:15 +0800 Subject: [PATCH] refactor(flat-components): update styles (#2112) - always update server config on init - slightly change the "join early" info - add "room will be closed" message in room - remove mobx configure (we are using only one mobx now) - update styles of the count up/down timer in room --- desktop/renderer-app/src/stores/utils.ts | 52 ------------- .../src/tasks/init-region-configs.ts | 4 +- desktop/renderer-app/src/tasks/init-ui.tsx | 6 -- .../src/tasks/init-url-protocol.ts | 5 -- .../components/ClassroomPage/Timer/index.tsx | 75 ++++++++++--------- .../components/ClassroomPage/Timer/style.less | 12 +++ .../HomePage/RoomList/RoomListItem/index.tsx | 14 ++-- .../HomePage/RoomList/RoomListItem/style.less | 35 +++++++++ packages/flat-i18n/locales/en.json | 4 + packages/flat-i18n/locales/zh-CN.json | 4 + .../flat-pages/src/BigClassPage/index.tsx | 3 + .../flat-pages/src/OneToOnePage/index.tsx | 3 + .../flat-pages/src/SmallClassPage/index.tsx | 3 + .../src/components/ClosableMessage.less | 34 +++++++++ .../src/components/ClosableMessage.tsx | 47 ++++++++++++ .../flat-stores/src/classroom-store/index.ts | 24 +++--- packages/flat-stores/src/room-store.ts | 5 ++ web/flat-web/src/tasks/init-region-configs.ts | 4 +- web/flat-web/src/tasks/init-ui.tsx | 6 +- 19 files changed, 210 insertions(+), 130 deletions(-) delete mode 100644 desktop/renderer-app/src/stores/utils.ts create mode 100644 packages/flat-pages/src/components/ClosableMessage.less create mode 100644 packages/flat-pages/src/components/ClosableMessage.tsx diff --git a/desktop/renderer-app/src/stores/utils.ts b/desktop/renderer-app/src/stores/utils.ts deleted file mode 100644 index bfab67f588b..00000000000 --- a/desktop/renderer-app/src/stores/utils.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { autorun, makeAutoObservable, toJS } from "mobx"; - -type LSPersistStore = [number, TStore]; - -export function autoPersistStore({ - storeLSName, - store, - version = 1, -}: { - storeLSName: string; - store: TStore; - version?: number; -}): void { - const config = getLSStore(storeLSName, version); - if (config) { - const keys = Object.keys(config) as unknown as Array; - for (const key of keys) { - if (typeof store[key] !== "function") { - store[key] = config[key]; - } - } - } - - makeAutoObservable(store); - - autorun(() => setLSStore(storeLSName, toJS(store), version)); -} - -function getLSStore(storeLSName: string, lsVersion: number): null | TStore { - try { - const str = localStorage.getItem(storeLSName); - if (!str) { - return null; - } - - const [version, store]: LSPersistStore = JSON.parse(str); - - if (version !== lsVersion) { - // clear storage if not match - setLSStore(storeLSName, null, lsVersion); - return null; - } - - return store; - } catch (e) { - return null; - } -} - -function setLSStore(storeLSName: string, configStore: TStore, lsVersion: number): void { - localStorage.setItem(storeLSName, JSON.stringify([lsVersion, configStore])); -} diff --git a/desktop/renderer-app/src/tasks/init-region-configs.ts b/desktop/renderer-app/src/tasks/init-region-configs.ts index afda1c1671f..caf1878ab13 100644 --- a/desktop/renderer-app/src/tasks/init-region-configs.ts +++ b/desktop/renderer-app/src/tasks/init-region-configs.ts @@ -5,9 +5,7 @@ import { errorTips } from "flat-components"; export const initRegionConfigs = async (): Promise => { try { const regionConfigs = await getServerRegionConfigs(); - if (regionConfigs.hash !== globalStore.configHash) { - globalStore.updateServerRegionConfig(regionConfigs); - } + globalStore.updateServerRegionConfig(regionConfigs); } catch (error) { globalStore.updateServerRegionConfig(null); console.error(error); diff --git a/desktop/renderer-app/src/tasks/init-ui.tsx b/desktop/renderer-app/src/tasks/init-ui.tsx index 3aa3aa2b15d..3890d2ed323 100644 --- a/desktop/renderer-app/src/tasks/init-ui.tsx +++ b/desktop/renderer-app/src/tasks/init-ui.tsx @@ -15,12 +15,6 @@ import { windowsBtnContext } from "../components/WindowsBtnContext"; import { runtime } from "../utils/runtime"; import { autoUpdate } from "../utils/auto-update"; -/** configure right after import */ -import { configure } from "mobx"; -configure({ - isolateGlobalState: true, -}); - const App: React.FC = () => { const language = useLanguage(); diff --git a/desktop/renderer-app/src/tasks/init-url-protocol.ts b/desktop/renderer-app/src/tasks/init-url-protocol.ts index c83bb89fc41..f204d3198fd 100644 --- a/desktop/renderer-app/src/tasks/init-url-protocol.ts +++ b/desktop/renderer-app/src/tasks/init-url-protocol.ts @@ -1,11 +1,6 @@ import { ipcReceive } from "../utils/ipc"; -import { configure } from "mobx"; import { urlProtocolStore } from "../stores/url-protocol-store"; -configure({ - isolateGlobalState: true, -}); - const requestJoinRoom = (): void => { ipcReceive("request-join-room", ({ roomUUID }) => { urlProtocolStore.updateToJoinRoomUUID(roomUUID); diff --git a/packages/flat-components/src/components/ClassroomPage/Timer/index.tsx b/packages/flat-components/src/components/ClassroomPage/Timer/index.tsx index 037fafef55b..3c63a9799a9 100644 --- a/packages/flat-components/src/components/ClassroomPage/Timer/index.tsx +++ b/packages/flat-components/src/components/ClassroomPage/Timer/index.tsx @@ -4,73 +4,78 @@ import React, { useState, useEffect, useMemo } from "react"; import { useTranslate } from "@netless/flat-i18n"; import { useIsUnMounted } from "../../../utils/hooks"; import { RoomStatus } from "../../../types/room"; -import { intervalToDuration } from "date-fns/fp"; +import { intervalToDuration } from "date-fns"; export type TimerProps = { roomStatus: RoomStatus; beginTime: number; + // Will show 'left time' when expireAt is set in the last 5 minutes + // 0 or undefined means no expire time + expireAt?: number; }; +const enum TimerStatus { + Prepare, // 0, show a count down time with normal color + Started, // 1, show a count up time in success color + WillEnd, // 2, show a count down time in warning color + Ended, // 3, show a count up time in error color +} + const paddingHexCode = (number: number): string => { return String(number).padStart(2, "0"); }; -const useClockTick = (beginTime: number, roomStatus: RoomStatus): string => { +const formatInterval = (start: number, end: number): string => { + const { days = 0, hours = 0, minutes = 0, seconds = 0 } = intervalToDuration({ start, end }); + + const minutesAndSeconds = `${paddingHexCode(minutes)}:${paddingHexCode(seconds)}`; + const dayHours = hours + days * 24; + + return dayHours > 0 ? `${paddingHexCode(dayHours)}:${minutesAndSeconds}` : minutesAndSeconds; +}; + +const useClockTick = (beginTime: number, expireAt: number | undefined): [TimerStatus, string] => { const [timestamp, updateTimestamp] = useState(Date.now()); const unmounted = useIsUnMounted(); useEffect(() => { - let timer = NaN; + let timer = 0; if (unmounted.current) { return; } - if (roomStatus === RoomStatus.Started) { - const startTimer = (): void => { - updateTimestamp(Math.floor(Date.now() / 1000) * 1000); - timer = window.requestAnimationFrame(startTimer); - }; - startTimer(); - } + const startTimer = (): void => { + updateTimestamp(Math.floor(Date.now() / 1000) * 1000); + timer = window.requestAnimationFrame(startTimer); + }; + startTimer(); return () => { window.cancelAnimationFrame(timer); }; - }, [roomStatus, unmounted]); + }, [unmounted]); return useMemo(() => { if (beginTime > timestamp) { - return "00:00"; + return [TimerStatus.Prepare, formatInterval(timestamp, beginTime)]; + } else if (expireAt && expireAt - timestamp < 5 * 60_000) { + return [TimerStatus.WillEnd, formatInterval(timestamp, expireAt)]; + } else if (expireAt && expireAt < timestamp) { + return [TimerStatus.Ended, formatInterval(expireAt, timestamp)]; + } else { + return [TimerStatus.Started, formatInterval(beginTime, timestamp)]; } - - const { - days = 0, - hours = 0, - minutes = 0, - seconds = 0, - } = intervalToDuration({ - start: beginTime, - end: timestamp, - }); - - const minutesAndSeconds = `${paddingHexCode(minutes)}:${paddingHexCode(seconds)}`; - const dayHours = hours + days * 24; - - return dayHours > 0 - ? `${paddingHexCode(dayHours)}:${minutesAndSeconds}` - : minutesAndSeconds; - }, [beginTime, timestamp]); + }, [beginTime, timestamp, expireAt]); }; -export const Timer: React.FC = ({ roomStatus = RoomStatus.Paused, beginTime }) => { - const timing = useClockTick(beginTime, roomStatus); - +export const Timer: React.FC = ({ beginTime, expireAt }) => { const t = useTranslate(); + const [status, timing] = useClockTick(beginTime, expireAt); return ( - - {t("room-started")} + + {t(`timer-status-${status}`)} {timing} ); diff --git a/packages/flat-components/src/components/ClassroomPage/Timer/style.less b/packages/flat-components/src/components/ClassroomPage/Timer/style.less index b3beb1e308d..3bfdcbcb127 100644 --- a/packages/flat-components/src/components/ClassroomPage/Timer/style.less +++ b/packages/flat-components/src/components/ClassroomPage/Timer/style.less @@ -3,6 +3,18 @@ font-size: 12px; margin: 0 8px; + &-1 { + color: var(--success); + } + + &-2 { + color: var(--warning); + } + + &-3 { + color: var(--danger); + } + .timer-text { display: inline-block; font-variant-numeric: tabular-nums; diff --git a/packages/flat-components/src/components/HomePage/RoomList/RoomListItem/index.tsx b/packages/flat-components/src/components/HomePage/RoomList/RoomListItem/index.tsx index 1707cc5f873..697e3e4815f 100644 --- a/packages/flat-components/src/components/HomePage/RoomList/RoomListItem/index.tsx +++ b/packages/flat-components/src/components/HomePage/RoomList/RoomListItem/index.tsx @@ -71,7 +71,7 @@ export function RoomListItem({ // Force update after 1 minute to update the "will start after x minutes" text useEffect(() => { - if (diffMinutes !== null && joinEarly < diffMinutes && diffMinutes < oneHour) { + if (diffMinutes !== null && joinEarly <= diffMinutes && diffMinutes < oneHour) { const timer = setTimeout( () => forceUpdate(a => (a + 1) | 0), // Random delay to avoid performance issue @@ -129,14 +129,14 @@ export function RoomListItem({ let actionView: ReactNode = null; if (diffMinutes === null) { actionView = primaryView || statusView; - } else if (joinEarly < diffMinutes && diffMinutes < oneHour) { + } else if (joinEarly <= diffMinutes && diffMinutes < oneHour) { actionView = ( {t("will-start-after-minutes", { minutes: diffMinutes })} ); } else { - actionView = diffMinutes <= joinEarly && primaryView ? primaryView : statusView; + actionView = diffMinutes < joinEarly && primaryView ? primaryView : statusView; } return ( @@ -157,7 +157,7 @@ export function RoomListItem({

{title}

- {beginTime && format(beginTime, "HH:mm")}~ + {beginTime && format(beginTime, "HH:mm")} ~{" "} {endTime && format(endTime, "HH:mm")} {date} @@ -169,12 +169,10 @@ export function RoomListItem({
@@ -111,6 +113,7 @@ export const BigClassPage = withClassroomStore( {classroomStore.roomInfo?.beginTime && ( )} diff --git a/packages/flat-pages/src/OneToOnePage/index.tsx b/packages/flat-pages/src/OneToOnePage/index.tsx index ad9f9fedf75..de68abf39ad 100644 --- a/packages/flat-pages/src/OneToOnePage/index.tsx +++ b/packages/flat-pages/src/OneToOnePage/index.tsx @@ -36,6 +36,7 @@ import { ExtraPadding } from "../components/ExtraPadding"; import { UsersButton } from "../components/UsersButton"; import { Shortcuts, Rewards } from "../components/Shortcuts"; import { PreferencesButton } from "../components/PreferencesButton"; +import { ClosableMessage } from "../components/ClosableMessage"; export type OneToOnePageProps = {}; @@ -92,6 +93,7 @@ export const OneToOnePage = withClassroomStore( isRemoteLogin={classroomStore.isRemoteLogin} roomStatus={classroomStore.roomStatus} /> + @@ -105,6 +107,7 @@ export const OneToOnePage = withClassroomStore( {classroomStore.roomInfo?.beginTime && ( )} diff --git a/packages/flat-pages/src/SmallClassPage/index.tsx b/packages/flat-pages/src/SmallClassPage/index.tsx index 2f80b37f7d8..ef11226f75a 100644 --- a/packages/flat-pages/src/SmallClassPage/index.tsx +++ b/packages/flat-pages/src/SmallClassPage/index.tsx @@ -42,6 +42,7 @@ import { UsersButton } from "../components/UsersButton"; import { Shortcuts, Rewards } from "../components/Shortcuts"; import { PreferencesButton } from "../components/PreferencesButton"; import { useScrollable } from "./utils"; +import { ClosableMessage } from "../components/ClosableMessage"; export type SmallClassPageProps = {}; @@ -115,6 +116,7 @@ export const SmallClassPage = withClassroomStore( isRemoteLogin={classroomStore.isRemoteLogin} roomStatus={classroomStore.roomStatus} /> + @@ -201,6 +203,7 @@ export const SmallClassPage = withClassroomStore( {classroomStore.roomInfo?.beginTime && ( )} diff --git a/packages/flat-pages/src/components/ClosableMessage.less b/packages/flat-pages/src/components/ClosableMessage.less new file mode 100644 index 00000000000..c3fee0d997e --- /dev/null +++ b/packages/flat-pages/src/components/ClosableMessage.less @@ -0,0 +1,34 @@ +.closable-message { + opacity: 0; + transform: translateY(20px); + transition: all 0.3s ease-in-out; + + &.is-open { + opacity: 1; + transform: translateY(0); + } + + &-btn { + width: 24px; + height: 24px; + display: inline-flex; + margin-right: -8px; + padding: 0; + border: none; + background: none; + cursor: pointer; + + .anticon { + margin-right: 0; + margin-left: 8px; + color: inherit; + font-size: 12px; + line-height: 24px; + transition: color 0.2s; + } + + &:hover .anticon { + color: var(--text-strong); + } + } +} diff --git a/packages/flat-pages/src/components/ClosableMessage.tsx b/packages/flat-pages/src/components/ClosableMessage.tsx new file mode 100644 index 00000000000..1c364a391a5 --- /dev/null +++ b/packages/flat-pages/src/components/ClosableMessage.tsx @@ -0,0 +1,47 @@ +import "./ClosableMessage.less"; + +import React, { useEffect, useState } from "react"; +import classNames from "classnames"; +import { CloseOutlined, InfoCircleFilled } from "@ant-design/icons"; +import { observer } from "mobx-react-lite"; +import { ClassroomStore } from "@netless/flat-stores"; + +interface ClosableMessageProps { + classroom: ClassroomStore; +} + +export const ClosableMessage = observer(function ClosableMessage({ + classroom, +}) { + // Save the last non-empty message so that it can be displayed when the message is closed + const [lastMsg, setLastMsg] = useState(classroom.adminMessage); + + useEffect(() => { + classroom.adminMessage && setLastMsg(classroom.adminMessage); + }, [classroom.adminMessage]); + + return ( +
+
+
+
+
+ + {lastMsg} + +
+
+
+
+
+ ); +}); diff --git a/packages/flat-stores/src/classroom-store/index.ts b/packages/flat-stores/src/classroom-store/index.ts index 61f13182a4d..56c1778f281 100644 --- a/packages/flat-stores/src/classroom-store/index.ts +++ b/packages/flat-stores/src/classroom-store/index.ts @@ -29,7 +29,7 @@ import { IServiceWhiteboard, } from "@netless/flat-services"; import { preferencesStore } from "../preferences-store"; -import { noop, sampleSize } from "lodash-es"; +import { sampleSize } from "lodash-es"; import { format } from "date-fns"; export * from "./constants"; @@ -113,6 +113,9 @@ export class ClassroomStore { public shareScreenPickerVisible = false; + public adminMessage = ""; + public expireAt = 0; + public networkQuality = { delay: 0, uplink: 0, @@ -223,10 +226,6 @@ export class ClassroomStore { }), ); - this.sideEffect.addDisposer(() => { - this.hideLastAdminMessage(); - }); - if (!this.isCreator) { this.sideEffect.addDisposer( this.rtm.events.on("update-room-status", event => { @@ -1473,18 +1472,18 @@ export class ClassroomStore { } } - private hideLastAdminMessage: () => void = noop; + public hideAdminMessage = (): void => { + this.adminMessage = ""; + }; private handleAdminMessage(text: string): void { - this.hideLastAdminMessage(); - if (text && text[0] === "{") { try { const data = JSON.parse(text); if (data && typeof data === "object") { const msg = data as { roomLevel: 0 | 1; - expireAt: string; + expireAt: number; leftMinutes: number; message: string; }; @@ -1497,7 +1496,8 @@ export class ClassroomStore { expireAt, minutes, }); - this.hideLastAdminMessage = message.info(info, 0); + this.expireAt = msg.expireAt; + this.adminMessage = info; return; } } catch (error) { @@ -1506,8 +1506,6 @@ export class ClassroomStore { } } - if (text) { - this.hideLastAdminMessage = message.info(text, 0); - } + this.adminMessage = text; } } diff --git a/packages/flat-stores/src/room-store.ts b/packages/flat-stores/src/room-store.ts index 5b5ec0c595a..05820dab37c 100644 --- a/packages/flat-stores/src/room-store.ts +++ b/packages/flat-stores/src/room-store.ts @@ -53,6 +53,10 @@ export interface RoomItem { hasRecord?: boolean; recordings?: RoomRecording[]; isPmi?: boolean; + billing?: { + expireAt: number; + vipLevel: 0 | 1; + }; } // Only keep sub-room ids. sub-room info are stored in ordinaryRooms. @@ -139,6 +143,7 @@ export class RoomStore { roomUUID: data.roomUUID, ownerUUID: data.ownerUUID, roomType: data.roomType, + billing: data.billing, }); return data; } diff --git a/web/flat-web/src/tasks/init-region-configs.ts b/web/flat-web/src/tasks/init-region-configs.ts index afda1c1671f..caf1878ab13 100644 --- a/web/flat-web/src/tasks/init-region-configs.ts +++ b/web/flat-web/src/tasks/init-region-configs.ts @@ -5,9 +5,7 @@ import { errorTips } from "flat-components"; export const initRegionConfigs = async (): Promise => { try { const regionConfigs = await getServerRegionConfigs(); - if (regionConfigs.hash !== globalStore.configHash) { - globalStore.updateServerRegionConfig(regionConfigs); - } + globalStore.updateServerRegionConfig(regionConfigs); } catch (error) { globalStore.updateServerRegionConfig(null); console.error(error); diff --git a/web/flat-web/src/tasks/init-ui.tsx b/web/flat-web/src/tasks/init-ui.tsx index d4535c3ad56..724f27d258b 100644 --- a/web/flat-web/src/tasks/init-ui.tsx +++ b/web/flat-web/src/tasks/init-ui.tsx @@ -11,11 +11,7 @@ import { AppRoutes } from "@netless/flat-pages/src/AppRoutes"; import { StoreProvider } from "@netless/flat-pages/src/components/StoreProvider"; import { FlatServicesContextProvider } from "@netless/flat-pages/src/components/FlatServicesContext"; -/** configure right after import */ -import { configure, toJS } from "mobx"; -configure({ - isolateGlobalState: true, -}); +import { toJS } from "mobx"; if (process.env.DEV) { (window as any).toJS = toJS;