mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-24 06: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,378 @@
|
||||
import type { SettingKind } from './selection'
|
||||
import type { SettingItem, SettingManager, SettingRange } from './types'
|
||||
import { isClosing } from '../lifecycle/instance'
|
||||
import { timeout } from '../lifecycle/resources'
|
||||
import { addClass, append, def, has, inverseClass, setStyle } from '../utils'
|
||||
import { settingScopeActive } from './activity'
|
||||
import { captureSettingItem, rememberSettingState } from './checkpoint'
|
||||
import { ownSettingPanel, releaseSettingPanel, settingPanelScope } from './panels'
|
||||
import { ownSettingItem, proxySetting, settingItemOwner } from './resources'
|
||||
import { bindSettingActions } from './selection'
|
||||
import { captureSettingUpdate } from './update'
|
||||
|
||||
const navigations = new WeakMap<SettingManager, number>()
|
||||
|
||||
function bindContent(item: SettingItem, key: 'icon' | 'html' | 'tooltip', element: HTMLDivElement): void {
|
||||
def(item, `$${key}`, { configurable: true, get: () => element })
|
||||
def(item, key, {
|
||||
configurable: true,
|
||||
get: () => element.innerHTML,
|
||||
set(value: unknown) {
|
||||
element.innerHTML = ''
|
||||
append(element, value)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function createSettingHeader(setting: SettingManager, item: SettingItem): void {
|
||||
if (!setting.cache.has(item.$option!))
|
||||
return
|
||||
const $panel = setting.cache.get(item.$option!)!
|
||||
|
||||
const {
|
||||
icons: { arrowLeft },
|
||||
constructor: { SETTING_ITEM_HEIGHT },
|
||||
} = setting.art
|
||||
|
||||
const $item = document.createElement('div')
|
||||
setStyle($item, 'height', `${SETTING_ITEM_HEIGHT}px`)
|
||||
addClass($item, 'art-setting-item')
|
||||
addClass($item, 'art-setting-item-back')
|
||||
const $left = append($item, '<div class="art-setting-item-left"></div>')
|
||||
const $icon = document.createElement('div')
|
||||
addClass($icon, 'art-setting-item-left-icon')
|
||||
append($icon, arrowLeft)
|
||||
append($left, $icon)
|
||||
append($left, item.$parent!.html)
|
||||
proxySetting(setting.art, item.$parent!, $item, 'click', () => setting.render(item.$parents), settingPanelScope($panel))
|
||||
append($panel, $item)
|
||||
}
|
||||
|
||||
export function createSettingItem(setting: SettingManager, item: SettingItem, isUpdate = false): void {
|
||||
const currentUpdate = captureSettingUpdate(item)
|
||||
if (!setting.cache.has(item.$option!))
|
||||
return
|
||||
const $panel = setting.cache.get(item.$option!)!
|
||||
const oldItem = item.$item
|
||||
|
||||
let type: SettingKind = 'selector'
|
||||
|
||||
if (has(item, 'switch')) {
|
||||
type = 'switch'
|
||||
}
|
||||
|
||||
if (has(item, 'range')) {
|
||||
type = 'range'
|
||||
}
|
||||
|
||||
if (has(item, 'onClick')) {
|
||||
type = 'button'
|
||||
}
|
||||
|
||||
const { icons, constructor } = setting.art
|
||||
|
||||
if (!currentUpdate())
|
||||
return
|
||||
const scope = ownSettingItem(setting.art, item, settingPanelScope($panel))
|
||||
const current = () => settingScopeActive(scope) && !isClosing(setting.art) && currentUpdate()
|
||||
if (!current())
|
||||
return
|
||||
const $item = document.createElement('div')
|
||||
try {
|
||||
rememberSettingState(item)
|
||||
addClass($item, 'art-setting-item')
|
||||
setStyle($item, 'height', `${constructor.SETTING_ITEM_HEIGHT}px`)
|
||||
|
||||
$item.dataset.name = item.name || ''
|
||||
$item.dataset.value = String(item.value || '')
|
||||
if (!current())
|
||||
return
|
||||
|
||||
const $left = append($item, '<div class="art-setting-item-left"></div>')
|
||||
const $right = append($item, '<div class="art-setting-item-right"></div>')
|
||||
|
||||
const $icon = document.createElement('div')
|
||||
addClass($icon, 'art-setting-item-left-icon')
|
||||
|
||||
switch (type) {
|
||||
case 'button':
|
||||
case 'switch':
|
||||
case 'range': {
|
||||
const icon = item.icon || icons.config
|
||||
if (!current())
|
||||
return
|
||||
append($icon, icon)
|
||||
break
|
||||
}
|
||||
case 'selector':
|
||||
if (item.selector?.length) {
|
||||
const icon = item.icon || icons.config
|
||||
if (!current())
|
||||
return
|
||||
append($icon, icon)
|
||||
}
|
||||
else {
|
||||
append($icon, icons.check)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
append($left, $icon)
|
||||
|
||||
if (!current())
|
||||
return
|
||||
bindContent(item, 'icon', $icon)
|
||||
|
||||
const $html = document.createElement('div')
|
||||
addClass($html, 'art-setting-item-left-text')
|
||||
const html = item.html || ''
|
||||
if (!current())
|
||||
return
|
||||
append($html, html)
|
||||
append($left, $html)
|
||||
|
||||
bindContent(item, 'html', $html)
|
||||
|
||||
const $tooltip = document.createElement('div')
|
||||
addClass($tooltip, 'art-setting-item-right-tooltip')
|
||||
const tooltip = item.tooltip || ''
|
||||
if (!current())
|
||||
return
|
||||
append($tooltip, tooltip)
|
||||
append($right, $tooltip)
|
||||
|
||||
bindContent(item, 'tooltip', $tooltip)
|
||||
|
||||
switch (type) {
|
||||
case 'switch': {
|
||||
const $switch = document.createElement('div')
|
||||
addClass($switch, 'art-setting-item-right-icon')
|
||||
const $switchOn = append($switch, icons.switchOn)
|
||||
const $switchOff = append($switch, icons.switchOff)
|
||||
const initialSwitch = item.switch
|
||||
if (!current())
|
||||
return
|
||||
setStyle(initialSwitch ? $switchOff : $switchOn, 'display', 'none')
|
||||
append($right, $switch)
|
||||
|
||||
def(item, '$switch', {
|
||||
configurable: true,
|
||||
get: () => $switch,
|
||||
})
|
||||
|
||||
let $switchValue = item.switch
|
||||
if (!current())
|
||||
return
|
||||
rememberSettingState(item, () => {
|
||||
const value = $switchValue
|
||||
return () => {
|
||||
$switchValue = value
|
||||
}
|
||||
})
|
||||
def(item, 'switch', {
|
||||
configurable: true,
|
||||
get: () => $switchValue,
|
||||
set(value) {
|
||||
$switchValue = value
|
||||
if (value) {
|
||||
setStyle($switchOff, 'display', 'none')
|
||||
setStyle($switchOn, 'display', null)
|
||||
}
|
||||
else {
|
||||
setStyle($switchOff, 'display', null)
|
||||
setStyle($switchOn, 'display', 'none')
|
||||
}
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'range':
|
||||
{
|
||||
const $state = document.createElement('div')
|
||||
addClass($state, 'art-setting-item-right-icon')
|
||||
const $range = document.createElement('input')
|
||||
$range.type = 'range'
|
||||
append($state, $range)
|
||||
for (const [index, key] of ['value', 'min', 'max', 'step'].entries()) {
|
||||
const value = item.range![index]
|
||||
if (!current())
|
||||
return
|
||||
Reflect.set($range, key, String(value))
|
||||
}
|
||||
addClass($range, 'art-setting-range')
|
||||
append($right, $state)
|
||||
|
||||
def(item, '$range', {
|
||||
configurable: true,
|
||||
get: () => $range,
|
||||
})
|
||||
|
||||
let $rangeValue = [...item.range!]
|
||||
if (!current())
|
||||
return
|
||||
rememberSettingState(item, () => {
|
||||
const value = $rangeValue
|
||||
const values = [...value]
|
||||
const input = { min: $range.min, max: $range.max, step: $range.step, value: $range.value }
|
||||
return () => {
|
||||
value.splice(0, value.length, ...values)
|
||||
$rangeValue = value
|
||||
Object.assign($range, input)
|
||||
}
|
||||
})
|
||||
def(item, 'range', {
|
||||
configurable: true,
|
||||
get: () => $rangeValue,
|
||||
set(value: SettingRange) {
|
||||
$rangeValue = [...value]
|
||||
$range.value = String(value[0])
|
||||
$range.min = String(value[1])
|
||||
$range.max = String(value[2])
|
||||
$range.step = String(value[3])
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'selector':
|
||||
if (item.selector?.length) {
|
||||
const $state = document.createElement('div')
|
||||
addClass($state, 'art-setting-item-right-icon')
|
||||
append($state, icons.arrowRight)
|
||||
append($right, $state)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!current())
|
||||
return
|
||||
bindSettingActions(setting, item, $item, type)
|
||||
if (!current())
|
||||
return
|
||||
|
||||
def(item, '$item', {
|
||||
configurable: true,
|
||||
get: () => $item,
|
||||
})
|
||||
|
||||
if (isUpdate) {
|
||||
if (oldItem?.parentNode)
|
||||
oldItem.replaceWith($item)
|
||||
else
|
||||
append($panel, $item)
|
||||
}
|
||||
else {
|
||||
append($panel, $item)
|
||||
}
|
||||
|
||||
if (item.mounted && current()) {
|
||||
timeout(scope, () => {
|
||||
if (isClosing(setting.art))
|
||||
return
|
||||
try {
|
||||
const result = item.mounted?.call(setting.art, $item, item)
|
||||
void Promise.resolve(result).catch(error => console.warn('ArtPlayer setting mounted failed:', error))
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('ArtPlayer setting mounted failed:', error)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
try {
|
||||
scope.dispose()
|
||||
}
|
||||
catch (cleanupError) {
|
||||
console.warn('ArtPlayer setting cleanup failed:', cleanupError)
|
||||
}
|
||||
$item.remove()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function renderSetting(setting: SettingManager, option = setting.option): void {
|
||||
if (isClosing(setting.art))
|
||||
return
|
||||
const cached = setting.cache.get(option)
|
||||
if (cached && !settingScopeActive(settingPanelScope(cached)))
|
||||
return
|
||||
const navigation = (navigations.get(setting) || 0) + 1
|
||||
navigations.set(setting, navigation)
|
||||
const currentNavigation = () => navigations.get(setting) === navigation && !isClosing(setting.art)
|
||||
const previous = setting.active
|
||||
const previousLayout = ['height', 'width', 'left', 'right'].map(key => [key, setting.$parent.style.getPropertyValue(key), setting.$parent.style.getPropertyPriority(key)] as const)
|
||||
const restoreNavigation = () => {
|
||||
setting.active = previous
|
||||
if (previous && setting.cache.has(previous))
|
||||
inverseClass(setting.cache.get(previous), 'art-current')
|
||||
for (const [key, value, priority] of previousLayout)
|
||||
setting.$parent.style.setProperty(key, value, priority)
|
||||
}
|
||||
setting.active = option
|
||||
if (cached) {
|
||||
try {
|
||||
inverseClass(cached, 'art-current')
|
||||
setting.resize()
|
||||
}
|
||||
catch (error) {
|
||||
if (currentNavigation() && setting.cache.get(option) === cached && !settingPanelScope(cached).closed)
|
||||
restoreNavigation()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
else {
|
||||
const $panel = document.createElement('div')
|
||||
setting.cache.set(option, $panel)
|
||||
const scope = ownSettingPanel(setting, option, $panel)
|
||||
const current = () => !scope.closed && !isClosing(setting.art) && setting.cache.get(option) === $panel
|
||||
const checkpoints: { item: SettingItem, restore: () => void }[] = []
|
||||
try {
|
||||
if (!current())
|
||||
return
|
||||
addClass($panel, 'art-setting-panel')
|
||||
append(setting.$parent, $panel)
|
||||
inverseClass($panel, 'art-current')
|
||||
|
||||
if (option[0]?.$parent)
|
||||
setting.createHeader(option[0])
|
||||
|
||||
for (let index = 0; index < option.length && current(); index++) {
|
||||
const item = option[index]!
|
||||
checkpoints.push({ item, restore: captureSettingItem(item) })
|
||||
setting.createItem(item)
|
||||
}
|
||||
if (!current())
|
||||
return
|
||||
setting.resize()
|
||||
}
|
||||
catch (error) {
|
||||
const restore = current()
|
||||
const restoreActive = setting.active === option && currentNavigation()
|
||||
try {
|
||||
releaseSettingPanel($panel)
|
||||
}
|
||||
catch (cleanupError) {
|
||||
console.warn('ArtPlayer setting cleanup failed:', cleanupError)
|
||||
}
|
||||
if (restore && !isClosing(setting.art)) {
|
||||
for (const checkpoint of checkpoints.reverse()) {
|
||||
if (settingItemOwner(checkpoint.item) === scope) {
|
||||
try {
|
||||
checkpoint.restore()
|
||||
}
|
||||
catch (restoreError) {
|
||||
console.warn('ArtPlayer setting restore failed:', restoreError)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (restoreActive)
|
||||
restoreNavigation()
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user