import { Player, $, isMobile } from '@nusaplayer/core' import Danmaku from 'danmaku' import { getMode } from './danmaku-parse' import { Options, Comment } from './types' function registerInputStyle() { const modeSelectionWrap = $.css({ display: 'none', 'box-sizing': 'border-box', width: '216px', height: 'auto', padding: '8px 0 10px', position: 'absolute', bottom: 'calc(100% + 6px)', left: '50%', 'margin-left': '-108px', background: 'rgba(21,21,21,.9)', 'border-radius': '2px', 'user-select': 'none', 'z-index': 20, 'pointer-events': 'auto', '&::before': { content: "''", position: 'absolute', left: 0, right: 0, top: '100%', height: '10px' } }) const modeSelectionRowSelection = $.css(` display: flex; flex-wrap: wrap; margin: 8px -8px 0 0;`) const danmakuTypeWrap = $.css({ width: '24px', height: '32px', padding: '0', 'line-height': '32px', 'text-align': 'center', color: '#757575', fill: 'currentColor', stroke: 'none', 'flex-shrink': 0, 'margin-left': '6px', position: 'relative', 'z-index': 12, 'box-sizing': 'border-box', overflow: 'visible', cursor: 'pointer', '&:hover': { color: 'var(--primary-color)' }, [`&:hover .${modeSelectionWrap}`]: { display: 'block' }, [`&[data-open="true"] .${modeSelectionWrap}`]: { display: 'block' }, [`& .${modeSelectionRowSelection} label`]: { flex: 1, 'margin-bottom': '8px' }, '& input[type="radio"]': { display: 'none' }, '& input[type="radio"]:checked + label > div': { background: 'var(--primary-color)' } }) const danmakuType = $.css(` display: inline-flex; height: 100%; align-items: center; `) const danmakuTypeIcon = $.css(` display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; `) const danmakuTypeSvg = $.css(`width: 100%;height: 100%;fill:currentColor;stroke:none;transition: fill .15s ease-in-out;`) const modeSelectionRow = $.css(` min-height: 22px; margin: 10px 20px; width: 176px; line-height: 22px; font-size: 12px;`) const modeSelectionRowTitle = $.css(`text-align: left;color: #fff; line-height: 16px;`) const modeSelectionSpan = $.css(` position: relative; cursor: pointer; border-radius: 2px; color: #fff; text-align: center; margin-right: 8px; background: hsla(0,0%,100%,.2); font-size: 12px; `) const colorPickerInput = $.css({ outline: 'none', 'box-shadow': 'none', 'background-color': '#fff', color: '#000', border: '1px solid hsla(0,0%,100%,.2)', 'border-radius': '2px', padding: '4px 7px', margin: '0 auto 8px auto', 'text-shadow': '0px 0px 6px #FFF', 'text-align': 'center', '&:focus, &:focus-visible': { outline: 'none', 'box-shadow': 'none' } }) const colorPickerWrap = $.css( `display: grid;grid-template-columns: repeat(7,auto);width: 100%;justify-items: center;` ) const activeColor = $.css('box-shadow: 0 0 1px 1px #fff;border-color: #000;') const colorPicker = $.css(` width: 16px; height: 16px; border: 1px solid rgba(0,0,0,.3); box-sizing: border-box; border-radius: 2px; margin-bottom: 4px; cursor: pointer; display: inline-block; `) const inputBar = $.css({ display: 'flex', 'align-items': 'center', flex: isMobile ? '1 1 auto' : '0 1 22em', width: isMobile ? '100%' : '22em', 'max-width': '100%', 'min-width': 0, 'box-sizing': 'border-box', 'border-radius': '4px', background: '#f4f4f4', color: '#999', height: '32px', 'max-height': '32px', 'min-height': '32px', 'align-self': 'center', margin: isMobile ? '0' : '1px 0 0', overflow: 'visible', position: 'relative', outline: 'none', 'box-shadow': 'none', '&:focus, &:focus-within, &:focus-visible': { outline: 'none', 'box-shadow': 'none' } }) const inputBarWrap = $.css(` flex: 1 1 auto; min-width: 0; display: flex; align-items: center; overflow: visible; `) const input = $.css({ flex: '1 1 auto', 'min-width': 0, padding: '0 8px', height: '32px', border: 0, 'box-sizing': 'border-box', background: 'none', 'line-height': '32px', color: '#212121', 'font-size': '12px', 'text-decoration': 'none', outline: 'none', 'box-shadow': 'none', 'touch-action': 'manipulation', overflow: 'hidden', 'text-overflow': 'ellipsis', '-webkit-tap-highlight-color': 'transparent', '-webkit-appearance': 'none', appearance: 'none', '&:focus, &:focus-visible, &:active': { outline: 'none', 'box-shadow': 'none', border: 0 } }) const send = $.css(` height: 100%; width: ${isMobile ? '3.2em' : '60px'}; min-width: ${isMobile ? '3.2em' : '60px'}; flex: 0 0 auto; border-radius: 0 4px 4px 0; cursor: pointer; box-sizing: border-box; overflow: hidden; `) const sendBottom = $.css(` height: 100%; background-color: var(--primary-color,#fb7299); color: #fff; width: 100%; min-width: 0; outline: none; font-size: 12px; display: flex; align-items: center; justify-content: center; `) return { inputBar, inputBarWrap, input, send, sendBottom, danmakuTypeWrap, danmakuType, danmakuTypeIcon, danmakuTypeSvg, modeSelectionWrap, modeSelectionRow, modeSelectionRowTitle, modeSelectionRowSelection, modeSelectionSpan, colorPicker, colorPickerInput, colorPickerWrap, activeColor } } export function registerInput(player: Player, danmaku: Danmaku, options: Options) { if (!player.context.ui) return const { inputBar, inputBarWrap, input, send, sendBottom, danmakuTypeWrap, danmakuType, danmakuTypeIcon, danmakuTypeSvg, modeSelectionWrap, modeSelectionRow, modeSelectionRowTitle, modeSelectionRowSelection, modeSelectionSpan, colorPickerWrap, colorPicker, colorPickerInput, activeColor } = registerInputStyle() const t = (key: string) => player.locales.get(key) const $tpl = $.create( `div.${inputBar}`, {}, `
${t('FontSize')}
${t('Mode')}
${t('Color')}
${[ '#FE0302', '#FF7204', '#FFAA02', '#FFD302', '#FFFF00', '#A0EE00', '#00CD00', '#019899', '#4266BE', '#89D5FF', '#CC0273', '#222222', '#9B9B9B', '#FFFFFF' ] .map((color, i) => { return `` }) .join('')}
${t('Send')}
` ) const $colorPickerInput = $tpl.querySelector(`.${colorPickerInput}`)! const $colorPickerWrap = $tpl.querySelector(`.${colorPickerWrap}`)! $colorPickerInput.oninput = function (e: Event) { ;(e.target).style.backgroundColor = (e.target).value } $colorPickerWrap.onclick = function (e: Event) { const target = e.target as HTMLSpanElement if (target.tagName == 'SPAN') { $colorPickerInput.style.backgroundColor = $colorPickerInput.value = target.getAttribute('data-value')! Array.from(target.parentElement!.children).forEach((it) => it.classList.remove(activeColor)) target.classList.add(activeColor) } } const parent = player.context.ui.$controllerBottom const $input = $tpl.querySelector(`.${input}`)! const $typeWrap = $tpl.querySelector(`.${danmakuTypeWrap}`)! const slot = parent.children[1] as HTMLElement | undefined if (slot) { slot.appendChild($tpl) } else { parent.insertBefore($tpl, parent.lastElementChild) } $typeWrap.addEventListener('click', (e) => { if ((e.target as HTMLElement).closest(`.${modeSelectionWrap}`)) return e.stopPropagation() const open = $typeWrap.getAttribute('data-open') == 'true' $typeWrap.setAttribute('data-open', open ? 'false' : 'true') }) const closeTypePanel = (e: Event) => { const node = e.target as HTMLElement if (node.closest?.(`.${danmakuTypeWrap}`)) return $typeWrap.removeAttribute('data-open') } player.$root.addEventListener('click', closeTypePanel) player.on('controlshidden', () => $typeWrap.removeAttribute('data-open')) const $playerRoot = player.$root player.on('destroy', () => $playerRoot.removeEventListener('click', closeTypePanel)) function submit() { const fontSize = $tpl.querySelector('input[name="font-size"]:checked')?.value ?? '25' const mode = $tpl.querySelector('input[name="mode"]:checked')?.value ?? '1' const color = $tpl.querySelector(`.${colorPickerInput}`)?.value || '#FFFFFF' if ($input.value) { const comment: Comment = { mode: getMode(+mode), text: $input.value, time: player.currentTime, style: { color: color, fontSize: `${fontSize}px` } } if (options.onEmit && options.onEmit(comment) === false) return const uiRoot = player.context.ui?.$root if (!uiRoot) return const primaryColor = window.getComputedStyle(uiRoot).getPropertyValue('--primary-color') //@ts-ignore comment.style!.border = `2px solid ${primaryColor}` //@ts-ignore comment.style!.marginTop = '4px' danmaku.emit(comment) $input.value = '' $input.blur() } } $tpl.querySelector(`.${sendBottom}`)!.addEventListener('click', submit) $input.addEventListener('keypress', (e) => { if (e.key === 'Enter') submit() }) }