mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
import type { SettingKind } from './selection'
|
|
import type { SettingItem, SettingManager, SettingRange } from './types'
|
|
import { appendElement } from '../component/dom'
|
|
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 = appendElement($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 = appendElement($item, '<div class="art-setting-item-left"></div>')
|
|
const $right = appendElement($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 = appendElement($switch, icons.switchOn)
|
|
const $switchOff = appendElement($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
|
|
}
|
|
}
|
|
}
|