Skip to content

Commit

Permalink
Split Feature Settings (#39)
Browse files Browse the repository at this point in the history
* splited feature settings

* fixed nested object mutation with state proxy not functioning
  • Loading branch information
eric2788 authored Jan 10, 2024
1 parent 36e2b53 commit a1c3ef5
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 136 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "bilibili-jimaku-filter",
"name": "bilibili-vup-stream-enhancer",
"displayName": "Bilibili Vup Stream Enhancer",
"version": "2.0",
"description": "管人观众专用直播增强扩展",
Expand Down
5 changes: 1 addition & 4 deletions src/contents/index/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ function App(): JSX.Element {

const { info, settings } = useContext(StreamInfoContext)

const {
"settings.display": displaySettings,
"settings.features": featureSettings,
} = settings
const { "settings.display": displaySettings } = settings

// 狀態為離綫時,此處不需要顯示按鈕
// 離綫下載按鈕交給 feature UI 處理
Expand Down
6 changes: 3 additions & 3 deletions src/contents/index/components/ButtonList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ function ButtonList(): JSX.Element {
}

const { settings, info } = streamInfo
const { "settings.display": displaySettings, "settings.features": featureSettings } = settings
const { "settings.display": displaySettings, "settings.features": { common: { enabledPip, monitorWindow }} } = settings

const { createPopupWindow } = usePopupWindow(featureSettings.enabledPip, {
const { createPopupWindow } = usePopupWindow(enabledPip, {
width: 700,
height: 450
})
Expand All @@ -40,7 +40,7 @@ function ButtonList(): JSX.Element {
<Button variant="outlined" size="lg" className="text-lg" onClick={openSettings}>进入设置</Button>}
{displaySettings.restartButton &&
<Button variant="outlined" size="lg" className="text-lg" onClick={restart}>重新启动</Button>}
{featureSettings.monitorWindow &&
{monitorWindow &&
<Button variant="outlined" size="lg" className="text-lg" onClick={openMonitor}>打开监控式视窗</Button>}
</div>
)
Expand Down
6 changes: 3 additions & 3 deletions src/features/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as jimaku from './jimaku'
import * as superchat from './superchat'

import type { Settings } from '~settings'
import type { StreamInfo } from '~api/bilibili'
import type { Settings } from '~settings'

export type FeatureHookRender = (settings: Readonly<Settings>, info: StreamInfo) => Promise<(React.ReactPortal | React.ReactNode)[] | undefined>

Expand All @@ -16,8 +16,8 @@ export interface FeatureHandler {
}

const features = {
'jimaku': jimaku,
'superchat': superchat
jimaku,
superchat
}


Expand Down
2 changes: 1 addition & 1 deletion src/features/jimaku/components/ButtonArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function ButtonArea({ clearJimaku, jimakus }: ButtonAreaProps): JSX.Element {
下载字幕记录
</JimakuButton>
}
{info.status === 'online' && features.jimakuPopupWindow &&
{info.status === 'online' && features.jimaku.jimakuPopupWindow &&
<JimakuButton
onClick={createPopupWindow(`jimaku.html`, {
roomId: info.room,
Expand Down
7 changes: 6 additions & 1 deletion src/features/jimaku/components/JimakuCaptureLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ function JimakuCaptureLayer(props: JimakuCaptureLayerProps): JSX.Element {
const { settings, info } = useContext(StreamInfoContext)
const { offlineRecords } = props

const { jimakuPopupWindow, useStreamingTime, enabledRecording } = settings["settings.features"]
const {
jimaku: { jimakuPopupWindow },
common: { useStreamingTime },
enabledRecording
} = settings["settings.features"]

const { regex, color, position } = settings['settings.danmaku']
const jimakuStyle = settings['settings.jimaku']
const { tongchuanBlackList, tongchuanMans } = settings['settings.listings']
Expand Down
2 changes: 1 addition & 1 deletion src/features/jimaku/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const handler: FeatureHookRender = async (settings, info) => {
const dev = settings['settings.developer']
const { backgroundHeight, backgroundColor, color, firstLineSize, lineGap, size, order } = settings['settings.jimaku']
const { backgroundListColor } = settings['settings.button']
const { noNativeVtuber } = settings['settings.features']
const { noNativeVtuber } = settings['settings.features'].jimaku

const playerSection = document.querySelector(dev.elements.jimakuArea)
const jimakuArea = document.createElement('div')
Expand Down
5 changes: 4 additions & 1 deletion src/features/superchat/components/SuperChatCaptureLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ function SuperChatCaptureLayer(props: SuperChatCaptureLayerProps): JSX.Element {

const { settings, info } = useContext(StreamInfoContext)
const { offlineRecords } = props
const { enabledRecording, useStreamingTime } = settings['settings.features']
const {
enabledRecording,
common: { useStreamingTime }
} = settings['settings.features']

const [superchat, setSuperChat] = useState<SuperChatCard[]>(offlineRecords)
const clearSuperChat = useCallback(() => setSuperChat([]), [])
Expand Down
2 changes: 1 addition & 1 deletion src/features/superchat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { type SuperChatCard } from "./components/SuperChatItem";

const handler: FeatureHookRender = async (settings, info) => {

const { useStreamingTime } = settings['settings.features']
const { useStreamingTime } = settings['settings.features'].common

const list = await getSuperChatList(info.room)
const superchats: SuperChatCard[] = (list ?? [])
Expand Down
16 changes: 8 additions & 8 deletions src/hooks/binding.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type SyntheticEvent } from 'react'
import { useCallback, type SyntheticEvent, useMemo } from 'react'
import { stateProxy, stateWrapper } from 'react-state-proxy'

import type { Leaves, PathLeafType, Paths, PickLeaves } from '~types/common'
Expand Down Expand Up @@ -40,9 +40,9 @@ export type ExposeHandler<T extends object> = T & {
* // The state is updated when the input changes
* console.log(state.text) // Output: (new value of the input)
*/
export function useBinding<T extends object>(initialState: T): [T, StateHandler<T>] {
export function useBinding<T extends object>(initialState: T, noCopy: boolean = false): [T, StateHandler<T>] {

const proxyHandler = {
const proxyHandler = useMemo(() => ({
set<K extends Leaves<T>>(k: K, v: PathLeafType<T, K>, useThis: boolean = false): boolean {
const target = useThis ? this : proxy
const parts = (k as string).split('.') as string[]
Expand All @@ -58,29 +58,29 @@ export function useBinding<T extends object>(initialState: T): [T, StateHandler<
const parts = (k as string).split('.') as string[]
if (parts.length === 1) {
const v = Reflect.get(this, k)
return typeof v !== 'object' ? v : stateWrapper({ ...v, ...proxyHandler })
return typeof v !== 'object' ? v : stateWrapper(Object.assign(v, proxyHandler))
}
const [part, ...remain] = parts
const fragment = this.get(part)
return fragment.get(remain.join('.'))
}
}
}), [])

const state = stateWrapper<T>({
const state = stateWrapper<T>(noCopy ? Object.assign(initialState, proxyHandler) : {
...initialState,
...proxyHandler
})

const proxy = stateProxy<T>(state)
const useHandler: StateHandler<T> = <E extends SyntheticEvent<Element>, W = any>(getter: (e: E) => W) => {
const useHandler: StateHandler<T> = useCallback(<E extends SyntheticEvent<Element>, W = any>(getter: (e: E) => W) => {
type H = ReturnType<typeof getter>
return function <R extends PickLeaves<T, H>>(k: R) {
return (e: E) => {
const value = getter(e) as PathLeafType<T, R>
(state as ExposeHandler<T>).set<R>(k, value)
}
}
}
}, [])

return [proxy, useHandler] as const
}
Expand Down
2 changes: 1 addition & 1 deletion src/settings/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function DataTable<T extends object>(props: DataTableProps<T>): JSX.Element {
{headers.map(({ name, align = 'left' }) => (
<th
key={name}
className={`border-b border-blue-gray-100 dark:border-gray-800 bg-blue-gray-50 dark:bg-gray-800 p-4 ${align ? `text-${align}` : ''}`}
className={`border-b border-blue-gray-100 dark:border-gray-800 bg-blue-gray-50 dark:bg-gray-700 p-4 ${align ? `text-${align}` : ''}`}
>
<Typography
variant="small"
Expand Down
16 changes: 16 additions & 0 deletions src/settings/components/ExperientmentFeatureIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Tooltip } from "@material-tailwind/react";
import { memo } from "react";


function ExperienmentFeatureIcon(): JSX.Element {
return (
<Tooltip content="测试阶段, 可能会出现BUG或未知问题, 届时请到 github 回报。">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 0-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
</svg>
</Tooltip>
)
}


export default memo(ExperienmentFeatureIcon)
32 changes: 32 additions & 0 deletions src/settings/features/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

import * as jimaku from './jimaku'
import * as superchat from './superchat'

import type { FeatureType } from '~features'
import type { TableType } from "~database"
import type { StateProxy } from "~hooks/binding"

export type FeatureSettingsDefinition = {
offlineTable: TableType | false
enabledRoomList: boolean
}

export type FeatureSettingSchema<T> = T extends FeatureFragment<infer U> ? U : never

export interface FeatureFragment<T extends object> {
title: string
define: FeatureSettingsDefinition
default?: React.FC<StateProxy<T>>,
defaultSettings: Readonly<T>
}

export type FeatureSettings = typeof featureSettings

const featureSettings = {
jimaku,
superchat
}

export default (featureSettings as { [K in FeatureType]: FeatureSettings[K] })

export const featureTypes: FeatureType[] = Object.keys(featureSettings) as FeatureType[]
45 changes: 45 additions & 0 deletions src/settings/features/jimaku.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type ChangeEvent, Fragment } from "react"
import type { FeatureSettingsDefinition } from "."
import type { StateProxy } from "~hooks/binding"
import ExperienmentFeatureIcon from "~settings/components/ExperientmentFeatureIcon"
import SwitchListItem from "~settings/components/SwitchListItem"


export const title: string = '同传弹幕过滤'

export const define: FeatureSettingsDefinition= {
enabledRoomList: true,
offlineTable: 'jimakus'
}

export type FeatureSettingSchema = {
noNativeVtuber: boolean
jimakuPopupWindow: boolean
}

export const defaultSettings: Readonly<FeatureSettingSchema> = {
noNativeVtuber: false,
jimakuPopupWindow: false
}


function JimakuFeatureSettings({ state, useHandler }: StateProxy<FeatureSettingSchema>): JSX.Element {

const checker = useHandler<ChangeEvent<HTMLInputElement>, boolean>(e => e.target.checked)

return (
<Fragment>
<SwitchListItem
label="过滤国内虚拟主播"
hint="此功能目前处于测试阶段, 因此无法过滤所有的国V"
value={state.noNativeVtuber}
onChange={checker('noNativeVtuber')}
marker={<ExperienmentFeatureIcon />}
/>
<SwitchListItem label="启用同传弹幕彈出式视窗" hint="使用弹出式视窗时必须开着直播间才能运行" value={state.jimakuPopupWindow} onChange={checker('jimakuPopupWindow')} />
</Fragment>
)
}


export default JimakuFeatureSettings
22 changes: 22 additions & 0 deletions src/settings/features/superchat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { FeatureSettingsDefinition } from "."

export const title: string = '醒目留言'

export const define: FeatureSettingsDefinition = {
enabledRoomList: false,
offlineTable: 'superchats'
}

export type FeatureSettingSchema = {}

export const defaultSettings: Readonly<FeatureSettingSchema> = {}


function SuperchatFeatureSettings(): JSX.Element {
return (
<></>
)
}


export default SuperchatFeatureSettings
1 change: 1 addition & 0 deletions src/settings/fragments/capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Selector from '~settings/components/Selector';
import type { AdapterType } from '~adapters';
import SwitchListItem from '~settings/components/SwitchListItem';
import { List } from '@material-tailwind/react';

export type SettingSchema = {
captureMechanism: AdapterType
boostWebSocketHook: boolean
Expand Down
Loading

0 comments on commit a1c3ef5

Please sign in to comment.