mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-26 00:31:20 -08:00
refactor(core): CORE-14 split typed settings and scope tree lifetimes
This commit is contained in:
1 parent
697817eceb
commit
64d1939daa
51 files changed
+28144
-1519
No files matched your search
@@ -0,0 +1,210 @@
|
||||
import type { SettingItem, SettingManager } from './types'
|
||||
import { getScope, isClosing } from '../lifecycle/instance'
|
||||
import { ResourceCleanupError } from '../lifecycle/scope'
|
||||
import captureTemplate from '../lifecycle/template-rollback'
|
||||
import { inverseClass } from '../utils/dom'
|
||||
import { pauseSettingScope } from './activity'
|
||||
import { captureSettingItem } from './checkpoint'
|
||||
import { releaseSettingPanel, settingPanelScope } from './panels'
|
||||
import { cancelSettingAdd } from './registration'
|
||||
import { suspendSettingItem } from './resources'
|
||||
|
||||
const pending = new WeakMap<SettingItem, () => void>()
|
||||
|
||||
function finish(cleanups: (() => void)[]): void {
|
||||
const errors: unknown[] = []
|
||||
for (const cleanup of cleanups) {
|
||||
try {
|
||||
cleanup()
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof ResourceCleanupError)
|
||||
errors.push(...error.errors)
|
||||
else
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
if (errors.length)
|
||||
throw new ResourceCleanupError(errors)
|
||||
}
|
||||
|
||||
function suspendTree(setting: SettingManager, root: SettingItem): { items: Set<SettingItem>, resume: () => void, dispose: () => void } {
|
||||
const items = new Set<SettingItem>()
|
||||
const panels = new Set<HTMLDivElement>()
|
||||
const collect = (item: SettingItem) => {
|
||||
if (items.has(item))
|
||||
return
|
||||
items.add(item)
|
||||
if (item.selector) {
|
||||
const panel = setting.cache.get(item.selector)
|
||||
if (panel)
|
||||
panels.add(panel)
|
||||
item.selector.forEach(collect)
|
||||
}
|
||||
}
|
||||
collect(root)
|
||||
const suspended: NonNullable<ReturnType<typeof suspendSettingItem>>[] = []
|
||||
const unpause: (() => void)[] = []
|
||||
try {
|
||||
for (const item of items) {
|
||||
const suspension = suspendSettingItem(setting.art, item)
|
||||
if (suspension)
|
||||
suspended.push(suspension)
|
||||
}
|
||||
for (const panel of panels)
|
||||
unpause.push(pauseSettingScope(settingPanelScope(panel)))
|
||||
}
|
||||
catch (error) {
|
||||
try {
|
||||
finish([...suspended.reverse().map(item => item.resume), ...unpause])
|
||||
}
|
||||
catch (cleanupError) {
|
||||
console.warn('ArtPlayer setting restore failed:', cleanupError)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
let active = true
|
||||
return {
|
||||
items,
|
||||
resume() {
|
||||
if (!active)
|
||||
return
|
||||
active = false
|
||||
finish([...suspended.reverse().map(item => item.resume), ...unpause])
|
||||
},
|
||||
dispose() {
|
||||
if (!active)
|
||||
return
|
||||
active = false
|
||||
finish([...suspended.reverse().map(item => item.dispose), ...Array.from(panels).reverse().map(panel => () => releaseSettingPanel(panel)), ...unpause])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// A newer operation restores the pending one before applying its own changes.
|
||||
export function cancelSettingUpdate(item: SettingItem): void {
|
||||
pending.get(item)?.()
|
||||
}
|
||||
|
||||
export function captureSettingUpdate(item: SettingItem): () => boolean {
|
||||
const operation = pending.get(item)
|
||||
return () => !operation || pending.get(item) === operation
|
||||
}
|
||||
|
||||
function assignSetting(item: SettingItem, target: SettingItem, current: () => boolean): void {
|
||||
for (const key of Reflect.ownKeys(target)) {
|
||||
if (!current())
|
||||
return
|
||||
const descriptor = Object.getOwnPropertyDescriptor(target, key)
|
||||
if (!descriptor?.enumerable || !current())
|
||||
continue
|
||||
const value: unknown = Reflect.get(target, key)
|
||||
if (!current())
|
||||
return
|
||||
if (!Reflect.set(item, key, value))
|
||||
throw new TypeError(`Cannot assign setting item property [${String(key)}]`)
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSetting(setting: SettingManager, target: SettingItem): SettingItem {
|
||||
const item = setting.find(target.name)
|
||||
if (isClosing(setting.art))
|
||||
return item || target
|
||||
if (!item)
|
||||
return setting.add(target)
|
||||
cancelSettingAdd(item)
|
||||
cancelSettingUpdate(item)
|
||||
const restoreItem = captureSettingItem(item)
|
||||
const element = item.$item
|
||||
const parent = element?.parentNode
|
||||
const next = element?.nextSibling
|
||||
const restoreTemplate = element ? captureTemplate(element) : undefined
|
||||
const previous = setting.active
|
||||
const layout = ['height', 'width', 'left', 'right'].map(key => [key, setting.$parent.style.getPropertyValue(key), setting.$parent.style.getPropertyPriority(key)] as const)
|
||||
const tree = suspendTree(setting, item)
|
||||
let active = true
|
||||
let rendered = false
|
||||
const cancel = () => {
|
||||
if (!active)
|
||||
return
|
||||
active = false
|
||||
for (const affected of tree.items) {
|
||||
if (pending.get(affected) === cancel)
|
||||
pending.delete(affected)
|
||||
}
|
||||
if (isClosing(setting.art)) {
|
||||
tree.dispose()
|
||||
return
|
||||
}
|
||||
const replacement = item.$item
|
||||
try {
|
||||
finish([
|
||||
tree.resume,
|
||||
() => {
|
||||
if (replacement && replacement !== element)
|
||||
replacement.remove()
|
||||
},
|
||||
restoreItem,
|
||||
() => restoreTemplate?.(),
|
||||
() => {
|
||||
if (parent && element)
|
||||
parent.insertBefore(element, next?.parentNode === parent ? next : null)
|
||||
},
|
||||
() => setting.format(),
|
||||
() => {
|
||||
if (rendered) {
|
||||
setting.active = previous
|
||||
if (previous && setting.cache.has(previous))
|
||||
inverseClass(setting.cache.get(previous), 'art-current')
|
||||
for (const [key, value, priority] of layout)
|
||||
setting.$parent.style.setProperty(key, value, priority)
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('ArtPlayer setting restore failed:', error)
|
||||
}
|
||||
}
|
||||
const current = () => active && !isClosing(setting.art) && pending.get(item) === cancel
|
||||
for (const affected of tree.items)
|
||||
pending.set(affected, cancel)
|
||||
const release = getScope(setting.art).add(() => {
|
||||
active = false
|
||||
for (const affected of tree.items) {
|
||||
if (pending.get(affected) === cancel)
|
||||
pending.delete(affected)
|
||||
}
|
||||
tree.dispose()
|
||||
})
|
||||
try {
|
||||
assignSetting(item, target, current)
|
||||
if (!current())
|
||||
return item
|
||||
setting.format()
|
||||
if (!current())
|
||||
return item
|
||||
setting.createItem(item, true)
|
||||
if (!current())
|
||||
return item
|
||||
rendered = true
|
||||
setting.render()
|
||||
if (!current())
|
||||
return item
|
||||
active = false
|
||||
for (const affected of tree.items) {
|
||||
if (pending.get(affected) === cancel)
|
||||
pending.delete(affected)
|
||||
}
|
||||
tree.dispose()
|
||||
return item
|
||||
}
|
||||
catch (error) {
|
||||
if (current())
|
||||
cancel()
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user