mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-10 12:46:15 -08:00
191 lines
5.2 KiB
TypeScript
191 lines
5.2 KiB
TypeScript
import type { SettingHost, SettingItem, SettingManager } from './types'
|
|
import { getScope, isClosing } from '../lifecycle/instance'
|
|
import { ResourceCleanupError } from '../lifecycle/scope'
|
|
import { errorHandle } from '../utils'
|
|
import Component from '../utils/component'
|
|
import aspectRatio from './aspectRatio'
|
|
import { installSettingEvents } from './events'
|
|
import flip from './flip'
|
|
import { installSettingKeyboard } from './keyboard'
|
|
import { captureSettingFocus } from './keyboard-focus'
|
|
import { resizeSetting } from './layout'
|
|
import { findItem, formatTree, registerTreeOwner, releaseTreeOwner, traverseTree } from './model'
|
|
import playbackRate from './playbackRate'
|
|
import { beginSettingAdd, cancelSettingAdd } from './registration'
|
|
import { createSettingHeader, createSettingItem, renderSetting } from './render'
|
|
import { releaseSettingTree } from './resources'
|
|
import { checkSetting } from './selection'
|
|
import subtitleOffset from './subtitleOffset'
|
|
import { cancelSettingUpdate, updateSetting } from './update'
|
|
|
|
// Keep the exact runtime inheritance; Setting overrides the registry operations and cache.
|
|
const SettingBase: new (art: SettingHost) => Pick<Component<SettingHost>, 'id' | 'art' | 'show' | 'toggle'> = Component
|
|
|
|
export default class Setting extends SettingBase implements SettingManager {
|
|
declare name: string
|
|
declare $parent: HTMLElement
|
|
declare cache: Map<SettingItem[], HTMLDivElement>
|
|
declare active: SettingItem[] | null
|
|
declare option: SettingItem[]
|
|
|
|
constructor(art: SettingHost) {
|
|
super(art)
|
|
|
|
const {
|
|
option,
|
|
template: { $setting },
|
|
} = art
|
|
|
|
this.name = 'setting'
|
|
this.$parent = $setting
|
|
|
|
this.id = 0
|
|
this.active = null
|
|
this.cache = new Map()
|
|
this.option = [...this.builtin, ...option.settings]
|
|
const scope = getScope(art)
|
|
registerTreeOwner(this, scope)
|
|
scope.add(() => {
|
|
releaseTreeOwner(this)
|
|
})
|
|
|
|
if (option.setting) {
|
|
this.format()
|
|
this.render()
|
|
|
|
installSettingEvents(this)
|
|
}
|
|
installSettingKeyboard(this)
|
|
}
|
|
|
|
get builtin(): SettingItem[] {
|
|
const result: SettingItem[] = []
|
|
const { option } = this.art
|
|
|
|
if (option.playbackRate) {
|
|
result.push(playbackRate(this.art))
|
|
}
|
|
|
|
if (option.aspectRatio) {
|
|
result.push(aspectRatio(this.art))
|
|
}
|
|
|
|
if (option.flip) {
|
|
result.push(flip(this.art))
|
|
}
|
|
|
|
if (option.subtitleOffset) {
|
|
result.push(subtitleOffset(this.art))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
traverse(callback: (item: SettingItem) => void, option = this.option): void {
|
|
traverseTree(option, callback)
|
|
}
|
|
|
|
check(target?: SettingItem | null): void {
|
|
checkSetting(this, target)
|
|
}
|
|
|
|
format(option = this.option, parent?: SettingItem, parents?: SettingItem[], names: string[] = []): void {
|
|
this.option = formatTree(this, option, parent, parents, names)
|
|
}
|
|
|
|
find(name = ''): SettingItem | null {
|
|
return findItem(this.option, name)
|
|
}
|
|
|
|
resize(): void {
|
|
resizeSetting(this)
|
|
}
|
|
|
|
inactivate(item: SettingItem): void {
|
|
releaseSettingTree(this, item)
|
|
}
|
|
|
|
remove(name: string): void {
|
|
const restoreFocus = captureSettingFocus(this)
|
|
const item = this.find(name)!
|
|
errorHandle(item, `Can't find [${name}] in the [setting]`)
|
|
this.traverse((item) => {
|
|
cancelSettingAdd(item)
|
|
cancelSettingUpdate(item)
|
|
}, [item])
|
|
const index = item.$option!.indexOf(item)
|
|
item.$option!.splice(index, 1)
|
|
const element = item.$item
|
|
const failures: unknown[] = []
|
|
for (const cleanup of [() => this.inactivate(item), () => element?.remove(), () => this.render()]) {
|
|
try {
|
|
cleanup()
|
|
}
|
|
catch (error) {
|
|
if (error instanceof ResourceCleanupError)
|
|
failures.push(...error.errors)
|
|
else
|
|
failures.push(error)
|
|
}
|
|
}
|
|
if (failures.length)
|
|
throw new ResourceCleanupError(failures)
|
|
restoreFocus()
|
|
}
|
|
|
|
update(target: SettingItem): SettingItem {
|
|
return updateSetting(this, target)
|
|
}
|
|
|
|
add<Item extends SettingItem>(item: Item, option: SettingItem[] = this.option): Item {
|
|
if (isClosing(this.art))
|
|
return item
|
|
let registered = false
|
|
this.traverse((existing) => {
|
|
registered ||= existing === item
|
|
})
|
|
const index = option.length
|
|
const registration = registered ? undefined : beginSettingAdd(item)
|
|
let formatted = false
|
|
try {
|
|
option.push(item)
|
|
this.format()
|
|
formatted = true
|
|
this.createItem(item)
|
|
this.render()
|
|
}
|
|
catch (error) {
|
|
if (isClosing(this.art) || (registration && !registration.current()))
|
|
throw error
|
|
if (option[index] === item)
|
|
option.splice(index, 1)
|
|
if (!registered && formatted) {
|
|
try {
|
|
this.inactivate(item)
|
|
}
|
|
catch (cleanupError) {
|
|
console.warn('ArtPlayer setting cleanup failed:', cleanupError)
|
|
}
|
|
item.$item?.remove()
|
|
}
|
|
throw error
|
|
}
|
|
finally {
|
|
registration?.finish()
|
|
}
|
|
return item
|
|
}
|
|
|
|
createHeader(item: SettingItem): void {
|
|
createSettingHeader(this, item)
|
|
}
|
|
|
|
createItem(item: SettingItem, isUpdate = false): void {
|
|
createSettingItem(this, item, isUpdate)
|
|
}
|
|
|
|
render(option = this.option): void {
|
|
renderSetting(this, option)
|
|
}
|
|
}
|