From adfa86e1acaca7609bd0fde61577b7a18763925f Mon Sep 17 00:00:00 2001 From: hyrious Date: Tue, 16 Apr 2024 17:53:37 +0800 Subject: [PATCH] fix(flat-components): edit periodic sub room times --- .../EditRoomPage/EditRoomBody/index.tsx | 11 +- .../EditRoomBody/renderBeginTimePicker.tsx | 174 +++++------------- .../EditRoomBody/renderEndTimePicker.tsx | 132 +++++-------- .../EditRoomPage/EditRoomBody/rules.ts | 34 ++++ packages/flat-i18n/locales/en.json | 3 +- packages/flat-i18n/locales/zh-CN.json | 3 +- .../OrdinaryRoomForm.tsx | 5 +- .../PeriodicSubRoomForm.tsx | 29 +-- .../src/ModifyPeriodicRoomPage/index.tsx | 4 +- packages/flat-server-api/src/room.ts | 1 + 10 files changed, 171 insertions(+), 225 deletions(-) create mode 100644 packages/flat-components/src/components/EditRoomPage/EditRoomBody/rules.ts diff --git a/packages/flat-components/src/components/EditRoomPage/EditRoomBody/index.tsx b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/index.tsx index 5a800c154bb..5bd48f1cd8e 100644 --- a/packages/flat-components/src/components/EditRoomPage/EditRoomBody/index.tsx +++ b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/index.tsx @@ -71,6 +71,7 @@ export interface EditRoomBodyProps { loading: boolean; onSubmit: (value: EditRoomFormValues) => void; previousPeriodicRoomBeginTime?: number | null; + nextPeriodicRoomBeginTime?: number | null; nextPeriodicRoomEndTime?: number | null; pmi?: string | null; autoPmiOn?: boolean; @@ -88,6 +89,7 @@ export const EditRoomBody: React.FC = ({ onSubmit, updateAutoPmiOn, previousPeriodicRoomBeginTime, + nextPeriodicRoomBeginTime, nextPeriodicRoomEndTime, }) => { const history = useHistory(); @@ -169,9 +171,16 @@ export const EditRoomBody: React.FC = ({ t, form, previousPeriodicRoomBeginTime, + nextPeriodicRoomBeginTime, + nextPeriodicRoomEndTime, + )} + {renderEndTimePicker( + t, + form, + previousPeriodicRoomBeginTime, + nextPeriodicRoomBeginTime, nextPeriodicRoomEndTime, )} - {renderEndTimePicker(t, form, nextPeriodicRoomEndTime)} {updateAutoPmiOn && ( , - previousPeriodicRoomBeginTime?: number | null, - nextPeriodicRoomEndTime?: number | null, + prevBeginTime?: number | null, + nextBeginTime?: number | null, + nextEndTime?: number | null, ): React.ReactElement { return ( @@ -43,133 +43,59 @@ export function renderBeginTimePicker( }; } - function disabledDate(date: Date): boolean { - if (previousPeriodicRoomBeginTime && nextPeriodicRoomEndTime) { - const isBeforeNow = isBefore(date, getRoughNow()); - const isBeforePreTime = isBefore(date, previousPeriodicRoomBeginTime); - const isAfterNextTime = isAfter(date, nextPeriodicRoomEndTime); - return isBeforePreTime || isAfterNextTime || isBeforeNow; - } else if (nextPeriodicRoomEndTime && previousPeriodicRoomBeginTime === null) { - const isBeforeNow = isBefore(date, startOfDay(getRoughNow())); - const isAfterNextTime = isAfter(date, nextPeriodicRoomEndTime); - return isBeforeNow || isAfterNextTime; - } else if (previousPeriodicRoomBeginTime && nextPeriodicRoomEndTime === null) { - const isBeforePreTime = isBefore(date, previousPeriodicRoomBeginTime); - const isBeforeNow = isBefore(date, getRoughNow()); - return isBeforePreTime || isBeforeNow; + // This function has to iterate through every (43200) minutes of a month to find the available time. + // There is a performance bottleneck. + function disabledDate(beginTime: Date): boolean { + beginTime = new Date(beginTime); + let allowed = false; + out: for (const hour of excludeRange(24)) { + for (const minute of excludeRange(60)) { + beginTime.setHours(hour); + beginTime.setMinutes(minute); + if (isAllowed(beginTime, null, prevBeginTime, nextBeginTime, nextEndTime)) { + allowed = true; + break out; + } + } } - return isBefore(date, startOfDay(getRoughNow())); + return !allowed; } function disabledHours(): number[] { - const beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); - - const now = getRoughNow(); - - const diff = compareDay(now, beginTime); - if (previousPeriodicRoomBeginTime && nextPeriodicRoomEndTime) { - const preBeginTime = new Date(previousPeriodicRoomBeginTime); - const nextEndTime = new Date(nextPeriodicRoomEndTime); - const diff = compareDay(preBeginTime, beginTime); - const endDiff = compareDay(nextEndTime, beginTime); - - if (diff < 0) { - if (endDiff === 0) { - if (nextEndTime.getMinutes() < 15) { - return excludeRange(nextEndTime.getHours(), 23); - } - return excludeRange(nextEndTime.getHours() + 1, 23); - } - return []; + let beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); + // Clone beginTime to avoid modifying the original value. + beginTime = new Date(beginTime); + beginTime.setSeconds(0); + beginTime.setMilliseconds(0); + const result: number[] = []; + for (const hour of excludeRange(24)) { + beginTime.setHours(hour); + if ( + !excludeRange(60).some(minute => { + beginTime.setMinutes(minute); + return isAllowed(beginTime, null, prevBeginTime, nextBeginTime, nextEndTime); + }) + ) { + result.push(hour); } - - if (diff === 0) { - return excludeRange(preBeginTime.getHours()); - } - - return excludeRange(24); - } else if (previousPeriodicRoomBeginTime) { - const preBeginTime = new Date(previousPeriodicRoomBeginTime); - const diff = compareDay(preBeginTime, beginTime); - - if (diff < 0) { - return []; - } - - if (diff === 0) { - return excludeRange(preBeginTime.getHours()); - } - } - - if (diff < 0) { - return []; - } - - if (diff === 0) { - return excludeRange(now.getHours()); } - - return excludeRange(24); + return result; } function disabledMinutes(selectedHour: number): number[] { - const beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); - const now = getRoughNow(); - - const diff = compareHour(now, setHours(beginTime, selectedHour)); - - if (previousPeriodicRoomBeginTime && nextPeriodicRoomEndTime) { - const preBeginTime = new Date(previousPeriodicRoomBeginTime); - const nextEndTime = new Date(nextPeriodicRoomEndTime); - const diff = compareHour(preBeginTime, setHours(beginTime, selectedHour)); - - const sameHour = selectedHour === nextEndTime.getHours(); - - if (diff < 0) { - if (sameHour) { - return excludeRange(nextEndTime.getMinutes(), 59); - } - const isPreHours = nextEndTime.getHours() - selectedHour === 1; - - if (isPreHours) { - if (nextEndTime.getMinutes() < 15) { - const roomDurationCompareTime = - MIN_CLASS_DURATION - nextEndTime.getMinutes(); - return excludeRange(60 - roomDurationCompareTime, 59); - } - return []; - } - return []; - } - - if (diff === 0) { - return excludeRange(preBeginTime.getMinutes() + 1); + let beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); + beginTime = new Date(beginTime); + beginTime.setHours(selectedHour); + beginTime.setSeconds(0); + beginTime.setMilliseconds(0); + const result: number[] = []; + for (const minute of excludeRange(60)) { + beginTime.setMinutes(minute); + if (!isAllowed(beginTime, null, prevBeginTime, nextBeginTime, nextEndTime)) { + result.push(minute); } - - return excludeRange(59); - } else if (previousPeriodicRoomBeginTime) { - const preBeginTime = new Date(previousPeriodicRoomBeginTime); - const diff = compareHour(preBeginTime, setHours(beginTime, selectedHour)); - if (diff < 0) { - return []; - } - - if (diff === 0) { - return excludeRange(preBeginTime.getMinutes() + 1); - } - - return excludeRange(59); } - - if (diff < 0) { - return []; - } - - if (diff === 0) { - return excludeRange(now.getMinutes()); - } - - return excludeRange(59); + return result; } /** make sure end time is at least min duration after begin time */ diff --git a/packages/flat-components/src/components/EditRoomPage/EditRoomBody/renderEndTimePicker.tsx b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/renderEndTimePicker.tsx index 15febe878a0..1408ec629f3 100644 --- a/packages/flat-components/src/components/EditRoomPage/EditRoomBody/renderEndTimePicker.tsx +++ b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/renderEndTimePicker.tsx @@ -1,17 +1,20 @@ -import { Form } from "antd"; -import { FormInstance, RuleObject } from "antd/lib/form"; -import { addMinutes, isAfter, isBefore, setHours } from "date-fns"; import React from "react"; +import { Form } from "antd"; import { FlatI18nTFunction } from "@netless/flat-i18n"; -import type { EditRoomFormValues } from "."; -import { compareDay, compareHour, excludeRange } from "../../../utils/room"; +import { FormInstance, RuleObject } from "antd/lib/form"; +import { addMinutes, isBefore } from "date-fns"; +import { EditRoomFormValues } from "./index"; +import { excludeRange } from "../../../utils/room"; import { FullTimePicker } from "../FullTimePicker"; import { MIN_CLASS_DURATION } from "./constants"; +import { isAllowed } from "./rules"; export function renderEndTimePicker( t: FlatI18nTFunction, form: FormInstance, - nextPeriodicRoomEndTime?: number | null, + prevBeginTime?: number | null, + nextBeginTime?: number | null, + nextEndTime?: number | null, ): React.ReactElement { return ( @@ -36,93 +39,58 @@ export function renderEndTimePicker( }; } - function disabledDate(date: Date): boolean { + function disabledDate(endTime: Date): boolean { const beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); - if (nextPeriodicRoomEndTime) { - const isBeforeTime = compareDay(addMinutes(beginTime, MIN_CLASS_DURATION), date) > 0; - const isAfterTime = isAfter(date, nextPeriodicRoomEndTime); - return isBeforeTime || isAfterTime; + endTime = new Date(endTime); + let allowed = false; + out: for (const hour of excludeRange(24)) { + for (const minute of excludeRange(60)) { + endTime.setHours(hour); + endTime.setMinutes(minute); + if (isAllowed(beginTime, endTime, prevBeginTime, nextBeginTime, nextEndTime)) { + allowed = true; + break out; + } + } } - return compareDay(addMinutes(beginTime, MIN_CLASS_DURATION), date) > 0; + return !allowed; } function disabledHours(): number[] { - const { beginTime, endTime }: Pick = - form.getFieldsValue(["beginTime", "endTime"]); - - const compareTime = addMinutes(beginTime, MIN_CLASS_DURATION); - const diff = compareDay(compareTime, endTime); - - if (nextPeriodicRoomEndTime) { - const nextPeriodicEndTime = new Date(nextPeriodicRoomEndTime); - const endDiff = compareDay(nextPeriodicEndTime, endTime); - - if (diff < 0) { - if (endDiff === 0) { - return excludeRange(nextPeriodicEndTime.getHours() + 1, 23); - } - return []; - } - - if (diff === 0) { - if (endDiff === 0) { - return excludeRange(nextPeriodicEndTime.getHours() + 1, 23); - } - return excludeRange(compareTime.getHours()); + const beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); + let endTime: EditRoomFormValues["endTime"] = form.getFieldValue("endTime"); + endTime = new Date(endTime); + endTime.setSeconds(0); + endTime.setMilliseconds(0); + const result: number[] = []; + for (const hour of excludeRange(24)) { + endTime.setHours(hour); + if ( + !excludeRange(60).some(minute => { + endTime.setMinutes(minute); + return isAllowed(beginTime, endTime, prevBeginTime, nextBeginTime, nextEndTime); + }) + ) { + result.push(hour); } - - return excludeRange(24); - } - - if (diff < 0) { - return []; } - - if (diff === 0) { - return excludeRange(compareTime.getHours()); - } - - return excludeRange(24); + return result; } function disabledMinutes(selectedHour: number): number[] { - const { beginTime, endTime }: Pick = - form.getFieldsValue(["beginTime", "endTime"]); - - const comparedTime = addMinutes(beginTime, MIN_CLASS_DURATION); - const selectedEndTime = setHours(endTime, selectedHour); - - const diff = compareHour(comparedTime, selectedEndTime); - - if (nextPeriodicRoomEndTime) { - const nextPeriodicEndTime = new Date(nextPeriodicRoomEndTime); - const endDiff = compareHour(nextPeriodicEndTime, endTime); - - if (diff < 0) { - if (endDiff === 0) { - return excludeRange(nextPeriodicEndTime.getMinutes() + 1, 59); - } - return []; - } - - if (diff === 0) { - if (endDiff === 0) { - return excludeRange(nextPeriodicEndTime.getMinutes() + 1, 59); - } - return excludeRange(comparedTime.getMinutes()); + const beginTime: EditRoomFormValues["beginTime"] = form.getFieldValue("beginTime"); + let endTime: EditRoomFormValues["endTime"] = form.getFieldValue("endTime"); + endTime = new Date(endTime); + endTime.setHours(selectedHour); + endTime.setSeconds(0); + endTime.setMilliseconds(0); + const result: number[] = []; + for (const minute of excludeRange(60)) { + endTime.setMinutes(minute); + if (!isAllowed(beginTime, endTime, prevBeginTime, nextBeginTime, nextEndTime)) { + result.push(minute); } - - return excludeRange(59); - } - - if (diff < 0) { - return []; - } - - if (diff === 0) { - return excludeRange(comparedTime.getMinutes()); } - - return excludeRange(59); + return result; } } diff --git a/packages/flat-components/src/components/EditRoomPage/EditRoomBody/rules.ts b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/rules.ts new file mode 100644 index 00000000000..22d32c7297e --- /dev/null +++ b/packages/flat-components/src/components/EditRoomPage/EditRoomBody/rules.ts @@ -0,0 +1,34 @@ +// Create/update room rules: +// * now <= begin +// * duration(begin, end) >= 15 minutes +// * prev.begin < begin < next.begin +// * end <= next.end + +import { getRoughNow } from "../../../utils/room"; +import { MIN_CLASS_DURATION } from "./constants"; + +/** + * Pass `null` in `endTime` when choosing `beginTime`. + */ +export function isAllowed( + beginTime: Date, + endTime: Date | null | undefined, + prevBeginTime: number | null | undefined, + nextBeginTime: number | null | undefined, + nextEndTime: number | null | undefined, +): boolean { + nextBeginTime ||= nextEndTime; + + const now = getRoughNow().getTime(); + const begin = beginTime.getTime(); + const end = endTime ? endTime.getTime() : begin + MIN_CLASS_DURATION * 60 * 1000; + const duration = end - begin; + + return ( + now <= begin && + duration >= MIN_CLASS_DURATION * 60 * 1000 && + (!prevBeginTime || prevBeginTime < begin) && + (!nextBeginTime || begin < nextBeginTime) && + (!nextEndTime || !end || end <= nextEndTime) + ); +} diff --git a/packages/flat-i18n/locales/en.json b/packages/flat-i18n/locales/en.json index f3058491d50..15736cc1721 100644 --- a/packages/flat-i18n/locales/en.json +++ b/packages/flat-i18n/locales/en.json @@ -764,5 +764,6 @@ "drop-to-storage": "Drop file to cloud storage", "drop-to-board": "Drop file to whiteboard", "mirror": "Mirror", - "mirror-mode": "Mirror Mode" + "mirror-mode": "Mirror Mode", + "edit-success": "Updated successfully" } diff --git a/packages/flat-i18n/locales/zh-CN.json b/packages/flat-i18n/locales/zh-CN.json index b2f1925e14f..3c78e51b6a6 100644 --- a/packages/flat-i18n/locales/zh-CN.json +++ b/packages/flat-i18n/locales/zh-CN.json @@ -764,5 +764,6 @@ "drop-to-storage": "拖拽文件到云盘", "drop-to-board": "拖拽文件到白板", "mirror": "镜像", - "mirror-mode": "镜像模式" + "mirror-mode": "镜像模式", + "edit-success": "修改成功" } diff --git a/packages/flat-pages/src/ModifyOrdinaryRoomPage/OrdinaryRoomForm.tsx b/packages/flat-pages/src/ModifyOrdinaryRoomPage/OrdinaryRoomForm.tsx index 3c0c844339b..273f431a4e1 100644 --- a/packages/flat-pages/src/ModifyOrdinaryRoomPage/OrdinaryRoomForm.tsx +++ b/packages/flat-pages/src/ModifyOrdinaryRoomPage/OrdinaryRoomForm.tsx @@ -8,9 +8,11 @@ import { LoadingPage, errorTips, } from "flat-components"; +import { useTranslate } from "@netless/flat-i18n"; import { ordinaryRoomInfo, updateOrdinaryRoom } from "@netless/flat-server-api"; import EditRoomPage from "../components/EditRoomPage"; import { useSafePromise } from "../utils/hooks/lifecycle"; + export interface OrdinaryRoomFormProps { roomUUID: string; } @@ -20,6 +22,7 @@ export const OrdinaryRoomForm = observer(function RoomFor const history = useHistory(); const sp = useSafePromise(); + const t = useTranslate(); const [initialValues, setInitialValues] = useState(); @@ -70,7 +73,7 @@ export const OrdinaryRoomForm = observer(function RoomFor type: values.type, }), ); - void message.success("修改成功"); + void message.success(t("edit-success")); history.goBack(); } catch (e) { console.error(e); diff --git a/packages/flat-pages/src/ModifyOrdinaryRoomPage/PeriodicSubRoomForm.tsx b/packages/flat-pages/src/ModifyOrdinaryRoomPage/PeriodicSubRoomForm.tsx index e3c6858a8fd..f8593862713 100644 --- a/packages/flat-pages/src/ModifyOrdinaryRoomPage/PeriodicSubRoomForm.tsx +++ b/packages/flat-pages/src/ModifyOrdinaryRoomPage/PeriodicSubRoomForm.tsx @@ -9,12 +9,9 @@ import { errorTips, } from "flat-components"; import { periodicSubRoomInfo, updatePeriodicSubRoom } from "@netless/flat-server-api"; -import EditRoomPage from "../components/EditRoomPage"; import { useSafePromise } from "../utils/hooks/lifecycle"; - -/** - * TODO: we forget set i18n in current file!!! - */ +import EditRoomPage from "../components/EditRoomPage"; +import { useTranslate } from "@netless/flat-i18n"; export interface PeriodicSubRoomFormProps { roomUUID: string; @@ -29,11 +26,13 @@ export const PeriodicSubRoomForm = observer(function R const history = useHistory(); const sp = useSafePromise(); + const t = useTranslate(); const [initialValues, setInitialValues] = useState(); const [previousPeriodicRoomBeginTime, setPreviousPeriodicRoomBeginTime] = useState< number | null >(0); + const [nextPeriodicRoomBeginTime, setNextPeriodicRoomBeginTime] = useState(0); const [nextPeriodicRoomEndTime, setNextPeriodicRoomEndTime] = useState(0); useEffect(() => { sp( @@ -43,17 +42,18 @@ export const PeriodicSubRoomForm = observer(function R needOtherRoomTimeInfo: true, }), ) - .then(({ roomInfo, previousPeriodicRoomBeginTime, nextPeriodicRoomEndTime }) => { + .then(data => { setInitialValues({ - title: roomInfo.title, - type: roomInfo.roomType, - beginTime: new Date(roomInfo.beginTime), - endTime: new Date(roomInfo.endTime), + title: data.roomInfo.title, + type: data.roomInfo.roomType, + beginTime: new Date(data.roomInfo.beginTime), + endTime: new Date(data.roomInfo.endTime), isPeriodic: false, - region: roomInfo.region, + region: data.roomInfo.region, }); - setPreviousPeriodicRoomBeginTime(previousPeriodicRoomBeginTime); - setNextPeriodicRoomEndTime(nextPeriodicRoomEndTime); + setPreviousPeriodicRoomBeginTime(data.previousPeriodicRoomBeginTime); + setNextPeriodicRoomBeginTime(data.nextPeriodicRoomBeginTime); + setNextPeriodicRoomEndTime(data.nextPeriodicRoomEndTime); }) .catch(e => { console.error(e); @@ -72,6 +72,7 @@ export const PeriodicSubRoomForm = observer(function R (function R endTime: values.endTime.valueOf(), }), ); - void message.success("修改成功"); + void message.success(t("edit-success")); history.goBack(); } catch (e) { console.error(e); diff --git a/packages/flat-pages/src/ModifyPeriodicRoomPage/index.tsx b/packages/flat-pages/src/ModifyPeriodicRoomPage/index.tsx index 59d4b3cd210..2c618f58db1 100644 --- a/packages/flat-pages/src/ModifyPeriodicRoomPage/index.tsx +++ b/packages/flat-pages/src/ModifyPeriodicRoomPage/index.tsx @@ -13,6 +13,7 @@ import { import { useSafePromise } from "../utils/hooks/lifecycle"; import EditRoomPage from "../components/EditRoomPage"; import { RouteNameType, RouteParams, usePushHistory } from "../utils/routes"; +import { useTranslate } from "@netless/flat-i18n"; import { periodicRoomInfo, updatePeriodicRoom } from "@netless/flat-server-api"; import { useLoginCheck } from "../utils/use-login-check"; @@ -32,6 +33,7 @@ export const ModifyPeriodicRoomPage = observer( const history = useHistory(); const pushHistory = usePushHistory(); const sp = useSafePromise(); + const t = useTranslate(); const [isLoading, setLoading] = useState(false); const [initialValues, setInitialValues] = useState(); @@ -115,7 +117,7 @@ export const ModifyPeriodicRoomPage = observer( }, }), ); - void message.success("修改成功"); + void message.success(t("edit-success")); pushHistory(RouteNameType.HomePage); } catch (e) { console.error(e); diff --git a/packages/flat-server-api/src/room.ts b/packages/flat-server-api/src/room.ts index c0a9fdd7d21..59439f4c4d6 100644 --- a/packages/flat-server-api/src/room.ts +++ b/packages/flat-server-api/src/room.ts @@ -162,6 +162,7 @@ export interface PeriodicSubRoomInfo { export interface PeriodicSubRoomInfoResult { roomInfo: PeriodicSubRoomInfo; previousPeriodicRoomBeginTime: number | null; + nextPeriodicRoomBeginTime: number | null; nextPeriodicRoomEndTime: number | null; count: number; }