diff --git a/src/App.tsx b/src/App.tsx index 8f64d5c..2fe4dd2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -250,9 +250,7 @@ function MainContent(): React.JSX.Element { }, []) useEffect(() => { - const handleOAuthUserUpdated = ( - event: Event - ) => { + const handleOAuthUserUpdated = (event: Event) => { const customEvent = event as CustomEvent<{ user?: { name?: string } | null }> @@ -267,10 +265,7 @@ function MainContent(): React.JSX.Element { window.addEventListener("ss:oauth-user-updated", handleOAuthUserUpdated as EventListener) return () => { - window.removeEventListener( - "ss:oauth-user-updated", - handleOAuthUserUpdated as EventListener - ) + window.removeEventListener("ss:oauth-user-updated", handleOAuthUserUpdated as EventListener) } }, [refreshPermissionFromAuth]) diff --git a/src/components/BoardManager.tsx b/src/components/BoardManager.tsx index 2b8b021..e36d7d9 100644 --- a/src/components/BoardManager.tsx +++ b/src/components/BoardManager.tsx @@ -862,9 +862,9 @@ ORDER BY reward_points DESC, score DESC`, ? "repeat(auto-fill, minmax(102px, 1fr))" : list.viewMode === "largeAvatar" ? "repeat(auto-fill, minmax(180px, 1fr))" - : list.viewMode === "list" - ? "1fr" - : "repeat(auto-fill, minmax(220px, 1fr))", + : list.viewMode === "list" + ? "1fr" + : "repeat(auto-fill, minmax(220px, 1fr))", gap: 12, }} > @@ -941,9 +941,9 @@ ORDER BY reward_points DESC, score DESC`, ? "8px" : list.viewMode === "largeAvatar" ? 0 - : list.viewMode === "list" - ? "10px 12px" - : "12px 14px", + : list.viewMode === "list" + ? "10px 12px" + : "12px 14px", height: list.viewMode === "grid" || list.viewMode === "largeAvatar" ? "100%" @@ -1108,9 +1108,7 @@ ORDER BY reward_points DESC, score DESC`, fontSize: 28, lineHeight: 1, color: - primaryMetric && primaryMetric.value >= 0 - ? "#52c41a" - : "#ff4d4f", + primaryMetric && primaryMetric.value >= 0 ? "#52c41a" : "#ff4d4f", background: "rgba(255,255,255,0.62)", border: "1px solid rgba(255,255,255,0.82)", borderRadius: 8, diff --git a/src/components/ContentArea.tsx b/src/components/ContentArea.tsx index 156326e..2f34d36 100644 --- a/src/components/ContentArea.tsx +++ b/src/components/ContentArea.tsx @@ -167,7 +167,7 @@ export function ContentArea({ : permission === "points" ? t("permissions.points") : t("permissions.view")} - + ) const fallbackDisplayName = permission === "admin" @@ -371,7 +371,9 @@ export function ContentArea({ }} >
最近同步时间
-
{formattedLastSyncTime}
+
+ {formattedLastSyncTime} +
云空间用量
{storageUsageLoading ? ( diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 4146a97..24827a3 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -13,7 +13,13 @@ import { Divider, Dropdown, } from "antd" -import { SearchOutlined, DeleteOutlined, UndoOutlined, UploadOutlined, CopyOutlined } from "@ant-design/icons" +import { + SearchOutlined, + DeleteOutlined, + UndoOutlined, + UploadOutlined, + CopyOutlined, +} from "@ant-design/icons" import { useTranslation } from "react-i18next" import { match, pinyin } from "pinyin-pro" import { getAvatarFromExtraJson, setAvatarInExtraJson } from "../utils/studentAvatar" @@ -651,111 +657,114 @@ export const Home: React.FC = ({ setOperationVisible(true) } - const playOperationMorph = useCallback((attempt = 0) => { - if (isPortraitMode || !operationOriginRect) return false - const modalEl = document.querySelector(`.${operationModalClass}`) as HTMLElement | null - if (!modalEl) { - const byClassCount = document.querySelectorAll(`.${operationModalClass}`).length - const byRootCount = document.querySelectorAll(`.${operationModalRootClass}`).length - const modalCount = document.querySelectorAll(".ant-modal").length - const rootExists = Boolean(document.querySelector(`.${operationModalRootClass}`)) - logHome("operation:morph:modal-miss", { - attempt, - rootExists, - byClassCount, - byRootCount, - modalCount, - }) - return false - } - - // Clear any leftover transform from previous close animation before measuring. - operationMorphAnimationRef.current?.cancel() - for (const animation of modalEl.getAnimations()) { - animation.cancel() - } - modalEl.style.transform = "" - modalEl.style.opacity = "" - modalEl.style.visibility = "" - const maskEl = document.querySelector(`.${operationModalRootClass} .ant-modal-mask`) as - | HTMLElement - | null - if (maskEl) { - operationMaskAnimationRef.current?.cancel() - maskEl.style.opacity = "" - maskEl.style.visibility = "" - operationMaskAnimationRef.current = maskEl.animate([{ opacity: 0 }, { opacity: 1 }], { - duration: 220, - easing: "cubic-bezier(0.2, 0, 0, 1)", - fill: "both", - }) - } - const modalRect = modalEl.getBoundingClientRect() - if (modalRect.width <= 0 || modalRect.height <= 0) { - logHome("operation:morph:modal-rect-invalid", { - attempt, - width: modalRect.width, - height: modalRect.height, - }) - return false - } - const fromX = operationOriginRect.left - modalRect.left - const fromY = operationOriginRect.top - modalRect.top - const scaleX = operationOriginRect.width / modalRect.width - const scaleY = operationOriginRect.height / modalRect.height - - logHome("operation:morph:computed", { - modalRect: { - left: modalRect.left, - top: modalRect.top, - width: modalRect.width, - height: modalRect.height, - }, - fromX, - fromY, - scaleX, - scaleY, - expectedStartRect: { - left: modalRect.left + fromX, - top: modalRect.top + fromY, - width: modalRect.width * scaleX, - height: modalRect.height * scaleY, - }, - animateSupported: typeof modalEl.animate === "function", - }) - - modalEl.style.transformOrigin = "top left" - modalEl.style.willChange = "transform, opacity" - operationMorphAnimationRef.current = modalEl.animate( - [ - { - transform: `translate3d(${fromX}px, ${fromY}px, 0) scale(${scaleX}, ${scaleY})`, - opacity: 0.86, - }, - { - transform: "translate3d(0, 0, 0) scale(1, 1)", - opacity: 1, - }, - ], - { - duration: 480, - easing: "cubic-bezier(0.16, 1, 0.3, 1)", - fill: "both", + const playOperationMorph = useCallback( + (attempt = 0) => { + if (isPortraitMode || !operationOriginRect) return false + const modalEl = document.querySelector(`.${operationModalClass}`) as HTMLElement | null + if (!modalEl) { + const byClassCount = document.querySelectorAll(`.${operationModalClass}`).length + const byRootCount = document.querySelectorAll(`.${operationModalRootClass}`).length + const modalCount = document.querySelectorAll(".ant-modal").length + const rootExists = Boolean(document.querySelector(`.${operationModalRootClass}`)) + logHome("operation:morph:modal-miss", { + attempt, + rootExists, + byClassCount, + byRootCount, + modalCount, + }) + return false } - ) - logHome("operation:morph:animation-start", { - attempt, - currentTime: operationMorphAnimationRef.current.currentTime, - playState: operationMorphAnimationRef.current.playState, - }) - operationMorphAnimationRef.current.onfinish = () => { - logHome("operation:morph:animation-finish") - } - operationMorphAnimationRef.current.oncancel = () => { - logHome("operation:morph:animation-cancel") - } - return true - }, [isPortraitMode, operationOriginRect, operationModalClass, operationModalRootClass]) + + // Clear any leftover transform from previous close animation before measuring. + operationMorphAnimationRef.current?.cancel() + for (const animation of modalEl.getAnimations()) { + animation.cancel() + } + modalEl.style.transform = "" + modalEl.style.opacity = "" + modalEl.style.visibility = "" + const maskEl = document.querySelector( + `.${operationModalRootClass} .ant-modal-mask` + ) as HTMLElement | null + if (maskEl) { + operationMaskAnimationRef.current?.cancel() + maskEl.style.opacity = "" + maskEl.style.visibility = "" + operationMaskAnimationRef.current = maskEl.animate([{ opacity: 0 }, { opacity: 1 }], { + duration: 220, + easing: "cubic-bezier(0.2, 0, 0, 1)", + fill: "both", + }) + } + const modalRect = modalEl.getBoundingClientRect() + if (modalRect.width <= 0 || modalRect.height <= 0) { + logHome("operation:morph:modal-rect-invalid", { + attempt, + width: modalRect.width, + height: modalRect.height, + }) + return false + } + const fromX = operationOriginRect.left - modalRect.left + const fromY = operationOriginRect.top - modalRect.top + const scaleX = operationOriginRect.width / modalRect.width + const scaleY = operationOriginRect.height / modalRect.height + + logHome("operation:morph:computed", { + modalRect: { + left: modalRect.left, + top: modalRect.top, + width: modalRect.width, + height: modalRect.height, + }, + fromX, + fromY, + scaleX, + scaleY, + expectedStartRect: { + left: modalRect.left + fromX, + top: modalRect.top + fromY, + width: modalRect.width * scaleX, + height: modalRect.height * scaleY, + }, + animateSupported: typeof modalEl.animate === "function", + }) + + modalEl.style.transformOrigin = "top left" + modalEl.style.willChange = "transform, opacity" + operationMorphAnimationRef.current = modalEl.animate( + [ + { + transform: `translate3d(${fromX}px, ${fromY}px, 0) scale(${scaleX}, ${scaleY})`, + opacity: 0.86, + }, + { + transform: "translate3d(0, 0, 0) scale(1, 1)", + opacity: 1, + }, + ], + { + duration: 480, + easing: "cubic-bezier(0.16, 1, 0.3, 1)", + fill: "both", + } + ) + logHome("operation:morph:animation-start", { + attempt, + currentTime: operationMorphAnimationRef.current.currentTime, + playState: operationMorphAnimationRef.current.playState, + }) + operationMorphAnimationRef.current.onfinish = () => { + logHome("operation:morph:animation-finish") + } + operationMorphAnimationRef.current.oncancel = () => { + logHome("operation:morph:animation-cancel") + } + return true + }, + [isPortraitMode, operationOriginRect, operationModalClass, operationModalRootClass] + ) useLayoutEffect(() => { if (isPortraitMode || !operationVisible) return @@ -787,23 +796,26 @@ export const Home: React.FC = ({ } }, [isPortraitMode, operationVisible, operationOriginRect, playOperationMorph]) - const finishCloseOperationModal = useCallback((skipCancelMorph = false, skipCancelMask = false) => { - if (!skipCancelMorph) { - operationMorphAnimationRef.current?.cancel() - } - operationMorphAnimationRef.current = null - if (!skipCancelMask) { - operationMaskAnimationRef.current?.cancel() - } - operationMaskAnimationRef.current = null - if (operationMorphRafRef.current !== null) { - window.cancelAnimationFrame(operationMorphRafRef.current) - operationMorphRafRef.current = null - } - operationClosingRef.current = false - setOperationVisible(false) - setOperationOriginRect(null) - }, []) + const finishCloseOperationModal = useCallback( + (skipCancelMorph = false, skipCancelMask = false) => { + if (!skipCancelMorph) { + operationMorphAnimationRef.current?.cancel() + } + operationMorphAnimationRef.current = null + if (!skipCancelMask) { + operationMaskAnimationRef.current?.cancel() + } + operationMaskAnimationRef.current = null + if (operationMorphRafRef.current !== null) { + window.cancelAnimationFrame(operationMorphRafRef.current) + operationMorphRafRef.current = null + } + operationClosingRef.current = false + setOperationVisible(false) + setOperationOriginRect(null) + }, + [] + ) const closeOperationModal = useCallback(() => { logHome("operation:morph:close", { diff --git a/src/components/RewardExchange.tsx b/src/components/RewardExchange.tsx index 7423095..f87718c 100644 --- a/src/components/RewardExchange.tsx +++ b/src/components/RewardExchange.tsx @@ -120,7 +120,9 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { } const frame = window.requestAnimationFrame(() => { - const modalEl = document.querySelector(".ss-reward-morph-modal.ant-modal") as HTMLElement | null + const modalEl = document.querySelector( + ".ss-reward-morph-modal.ant-modal" + ) as HTMLElement | null if (!modalEl) { setModalMorphStage("idle") return diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 363485c..0e11dba 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -416,15 +416,13 @@ export const Settings: React.FC<{ useEffect(() => { const handleOAuthUserUpdated = (event: Event) => { const customEvent = event as CustomEvent<{ - user?: - | { - user_id?: string - email?: string - name?: string - github_username?: string - permission?: number - } - | null + user?: { + user_id?: string + email?: string + name?: string + github_username?: string + permission?: number + } | null }> const user = customEvent?.detail?.user if (user?.user_id && user.email && user.name && typeof user.permission === "number") { @@ -442,10 +440,7 @@ export const Settings: React.FC<{ window.addEventListener("ss:oauth-user-updated", handleOAuthUserUpdated as EventListener) return () => { - window.removeEventListener( - "ss:oauth-user-updated", - handleOAuthUserUpdated as EventListener - ) + window.removeEventListener("ss:oauth-user-updated", handleOAuthUserUpdated as EventListener) } }, [])