合并冲突

This commit is contained in:
Yukino_fox
2026-04-19 12:33:58 +08:00
parent 6e74534e21
commit 6ce87541b7
6 changed files with 158 additions and 154 deletions
+2 -7
View File
@@ -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])
+7 -9
View File
@@ -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,
+4 -2
View File
@@ -167,7 +167,7 @@ export function ContentArea({
: permission === "points"
? t("permissions.points")
: t("permissions.view")}
</Tag>
</Tag>
)
const fallbackDisplayName =
permission === "admin"
@@ -371,7 +371,9 @@ export function ContentArea({
}}
>
<div style={{ fontSize: "12px", color: "var(--ss-text-secondary)" }}></div>
<div style={{ fontSize: "12px", color: "var(--ss-text-main)" }}>{formattedLastSyncTime}</div>
<div style={{ fontSize: "12px", color: "var(--ss-text-main)" }}>
{formattedLastSyncTime}
</div>
</div>
<div style={{ fontSize: "13px", color: "var(--ss-text-secondary)" }}></div>
{storageUsageLoading ? (
+134 -122
View File
@@ -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<HomeProps> = ({
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<HomeProps> = ({
}
}, [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", {
+3 -1
View File
@@ -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
+8 -13
View File
@@ -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)
}
}, [])