mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
fix(glass): stabilize optical surfaces and page transitions (#594)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BACKGROUND_ROTATION_GRACE_MS,
|
||||
createBackgroundCandidateOrderResolver,
|
||||
findFirstAvailableBackground,
|
||||
preloadBackgroundRotationImages,
|
||||
shouldAllowBackgroundRotation,
|
||||
@@ -27,6 +28,27 @@ describe('background rotation lifecycle', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('createBackgroundCandidateOrderResolver', () => {
|
||||
it('shuffles a source list without mutating the backend response', () => {
|
||||
const urls = ['one.jpg', 'two.jpg', 'three.jpg']
|
||||
const resolveOrder = createBackgroundCandidateOrderResolver(() => 0)
|
||||
|
||||
expect(resolveOrder(urls)).toEqual(['two.jpg', 'three.jpg', 'one.jpg'])
|
||||
expect(urls).toEqual(['one.jpg', 'two.jpg', 'three.jpg'])
|
||||
})
|
||||
|
||||
it('keeps the same order across retries and reshuffles only after the source list changes', () => {
|
||||
const random = vi.fn().mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValueOnce(0.5)
|
||||
const resolveOrder = createBackgroundCandidateOrderResolver(random)
|
||||
const firstOrder = resolveOrder(['one.jpg', 'two.jpg', 'three.jpg'])
|
||||
|
||||
expect(resolveOrder(['one.jpg', 'two.jpg', 'three.jpg'])).toEqual(firstOrder)
|
||||
expect(random).toHaveBeenCalledTimes(2)
|
||||
expect(resolveOrder(['one.jpg', 'two.jpg'])).toEqual(['one.jpg', 'two.jpg'])
|
||||
expect(random).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('preloadBackgroundRotationImages', () => {
|
||||
it('does not let an unused optical texture block the visible wallpaper', async () => {
|
||||
const preload = vi.fn(async (url: string) => url === 'display.jpg')
|
||||
|
||||
@@ -3,11 +3,36 @@ import type { AppActivityState } from '@/utils/appActivityLifecycle'
|
||||
/** 壁纸在窗口失焦后继续轮换的最长时间,交互 renderer 仍由应用生命周期独立暂停。 */
|
||||
export const BACKGROUND_ROTATION_GRACE_MS = 60_000
|
||||
|
||||
type RandomSource = () => number
|
||||
|
||||
/** 壁纸轮换只在前台活动或失焦宽限期内运行,系统减少动态效果时始终停止。 */
|
||||
export function shouldAllowBackgroundRotation(state: AppActivityState, graceActive: boolean, reducedMotion: boolean) {
|
||||
return !reducedMotion && (state === 'active' || graceActive)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为每个前端页面生命周期生成稳定的随机候选顺序;相同来源列表在重试和状态恢复时不会再次洗牌。
|
||||
*/
|
||||
export function createBackgroundCandidateOrderResolver(random: RandomSource = Math.random) {
|
||||
let sourceUrls: string[] | null = null
|
||||
let orderedUrls: string[] = []
|
||||
|
||||
return (urls: string[]) => {
|
||||
const sourceChanged =
|
||||
!sourceUrls || sourceUrls.length !== urls.length || sourceUrls.some((url, index) => url !== urls[index])
|
||||
if (!sourceChanged) return [...orderedUrls]
|
||||
|
||||
sourceUrls = [...urls]
|
||||
orderedUrls = [...urls]
|
||||
for (let index = orderedUrls.length - 1; index > 0; index -= 1) {
|
||||
const swapIndex = Math.floor(random() * (index + 1))
|
||||
;[orderedUrls[index], orderedUrls[swapIndex]] = [orderedUrls[swapIndex], orderedUrls[index]]
|
||||
}
|
||||
|
||||
return [...orderedUrls]
|
||||
}
|
||||
}
|
||||
|
||||
interface BackgroundRotationImagePreloadOptions {
|
||||
/** 外层背景实际显示的壁纸地址。 */
|
||||
displayUrl: string
|
||||
@@ -18,7 +43,7 @@ interface BackgroundRotationImagePreloadOptions {
|
||||
}
|
||||
|
||||
interface FirstAvailableBackgroundOptions {
|
||||
/** 保持后端返回顺序的候选壁纸。 */
|
||||
/** 当前页面生命周期已确定顺序的候选壁纸。 */
|
||||
urls: string[]
|
||||
/** 当前加载批次仍可提交时返回 true。 */
|
||||
canContinue: () => boolean
|
||||
@@ -26,7 +51,7 @@ interface FirstAvailableBackgroundOptions {
|
||||
preload: (url: string) => Promise<boolean>
|
||||
}
|
||||
|
||||
/** 按来源顺序寻找首张可用壁纸,单项失败或过期批次不会提交可见状态。 */
|
||||
/** 按当前候选顺序寻找首张可用壁纸,单项失败或过期批次不会提交可见状态。 */
|
||||
export async function findFirstAvailableBackground(options: FirstAvailableBackgroundOptions) {
|
||||
for (let index = 0; index < options.urls.length; index += 1) {
|
||||
if (!options.canContinue()) return null
|
||||
|
||||
@@ -72,9 +72,14 @@ export interface GlassOpticalSpringState {
|
||||
velocity: number
|
||||
}
|
||||
|
||||
/** 光学表面在共享 renderer 中采用的动态响应合同。 */
|
||||
export type GlassOpticalSurfaceMode = 'dynamic' | 'static-material'
|
||||
|
||||
export interface GlassOpticalSurfaceCandidate<TKey> {
|
||||
/** renderer 生命周期内稳定的表面身份。 */
|
||||
key: TKey
|
||||
/** 表面使用完整动态光学,或只保留稳定材质能量。 */
|
||||
mode?: GlassOpticalSurfaceMode
|
||||
/** 表面的当前视口几何。 */
|
||||
rect: GlassOpticalRect
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user