Add type of component options

This commit is contained in:
timongh 2022-03-18 16:28:25 +08:00
parent b7626e3cbf
commit 4ec3f68964
5 changed files with 144 additions and 42 deletions

View File

@ -90,7 +90,7 @@ export const loadComponent = async (component: ComponentMetadata) => {
metadata: component,
coreApis,
})
loadedComponents[component.name] = data || {}
loadedComponents[component.name] = data as any || {}
}
if (component.reload && component.unload) {
addComponentListener(component.name, async (enable: boolean) => {
@ -175,3 +175,4 @@ export const loadAllComponents = async () => {
}
export * from './types'
export * from './define'

24
src/components/define.ts Normal file
View File

@ -0,0 +1,24 @@
import {
ComponentMetadata,
OptionalOptionsMetadata,
OptionMetadata,
OptionsMetadata,
UnknownOptions,
} from './types'
/** 从 OptionsMetadata 中获取 Options即 OptionsMetadata 的类型参数) */
export type OptionsOfMeta<M extends OptionsMetadata> = (
M extends OptionsMetadata<infer O> ? O : never
)
export const defOptionMeta = <T>(
m: OptionMetadata<T>,
): OptionMetadata<T> => m
export const defOptionsMeta = <
O extends UnknownOptions
>(m: OptionsMetadata<O>): OptionsMetadata<O> => m
export const defComponentMeta = <
Om extends OptionalOptionsMetadata
>(m: ComponentMetadata<Om>): ComponentMetadata<Om> => m

View File

@ -15,6 +15,7 @@ export type Author = {
name: string
link: string
}
export interface FeatureBase {
// TODO: 可在编译时转换 Markdown 以提高运行时性能
/** 描述 (支持 markdown), 可以设置为对象提供多语言的描述 (`key: 语言代码`) */
@ -26,9 +27,11 @@ export interface FeatureBase {
/** 编译时的 core version, 由 Babel 注入, 不需要手动填写 */
coreVersion?: string
}
type Optional<Target, Props extends keyof Target> = {
[P in Props]?: Target[P]
} & Omit<Target, Props>
/** 组件标签 */
export interface ComponentTag {
/** 标签的名称 */
@ -42,13 +45,18 @@ export interface ComponentTag {
/** 设置面板中的呈现顺序 */
order: number
}
type ComponentOptionValidator<T> = (value: T, oldValue: T) => T | undefined | null
/**
* @todo extends Option ,
*/
export interface ComponentOption {
// TODO: 参考 discussion #3041。当不兼容代码替换完成后将 any 改为 unknown
export type UnknownOptions = Record<string, any>
export type EmptyOptions = Record<string, never>
/** 单个选项的信息 */
export interface OptionMetadata<V = unknown> {
/** 默认值 */
defaultValue: unknown
defaultValue: V
/** 显示名称 */
displayName?: string
/** 如果希望这个选项显示为一个下拉框, 可以用相应的 `enum` 提供下拉框的选值, 或者也可以传入 `string[]` */
@ -67,10 +75,15 @@ export interface ComponentOption {
validator?: ComponentOptionValidator<Range<string>> |
ComponentOptionValidator<string> | ComponentOptionValidator<number>
}
/** 组件选项信息 */
export interface ComponentOptions {
[key: string]: ComponentOption
/** 多个选项的信息 */
export type OptionsMetadata<O extends UnknownOptions = UnknownOptions> = {
[OptionName in keyof O]: OptionMetadata<O[OptionName]>
}
// TODO: 参考 discussion #3041。当不兼容代码替换完成后删除
export type ComponentOptions = OptionsMetadata
/** 组件标签 */
export const componentsTags = {
/** 视频 */
@ -139,21 +152,42 @@ export const componentsTags = {
order: 8,
} as ComponentTag,
}
/**
* OptionalOptionsMetadata Options
* undefined EmptyOptions
*/
export type OptionsFromOptionalMeta<M extends OptionalOptionsMetadata> = (
M extends undefined | Record<string, never> ? EmptyOptions :
M extends OptionsMetadata<infer O> ? O : never
)
/** 组件入口函数的参数 */
export interface ComponentEntryContext<
Om extends OptionalOptionsMetadata = OptionalOptionsMetadata
> {
/** 当前组件的设置 */
settings: ComponentSettings<OptionsFromOptionalMeta<Om>>
/** 当前组件的信息 */
metadata: ComponentMetadata<Om>
/** 核心 API */
coreApis: CoreApis
}
/** 组件入口函数 */
export type ComponentEntry<T = unknown> = (
context: {
/** 当前组件的设置 */
settings: ComponentSettings
/** 当前组件的信息 */
metadata: ComponentMetadata
/** 核心 API */
coreApis: CoreApis
}
export type ComponentEntry<
Om extends OptionalOptionsMetadata = OptionalOptionsMetadata,
T = unknown
> = (
context: ComponentEntryContext<Om>
) => T | Promise<T>
/** 带有函数/复杂对象的组件信息 */
export interface FunctionalMetadata {
export interface FunctionalMetadata<
Om extends OptionalOptionsMetadata = OptionalOptionsMetadata
> {
/** 主入口, 重新开启时不会再运行 */
entry: ComponentEntry
entry: ComponentEntry<Om>
/** 导出小组件 */
widget?: Omit<Widget, 'name'>
/** 首屏样式, 会尽快注入 (before DCL) */
@ -178,8 +212,16 @@ export interface FunctionalMetadata {
/** 设置不匹配的URL, 不匹配则不运行此组件, 优先级高于`urlInclude` */
urlExclude?: TestPattern
}
/** ComponentMetadata 的类型参数,用于表达其可选参数 options 的类型 */
export type OptionalOptionsMetadata<O extends UnknownOptions = UnknownOptions> = (
OptionsMetadata<O> | undefined
)
/** 组件基本信息 */
export interface ComponentMetadata extends FunctionalMetadata, FeatureBase {
export interface ComponentMetadata<
Om extends OptionalOptionsMetadata = OptionalOptionsMetadata
> extends FeatureBase, FunctionalMetadata<Om> {
/** 组件名称 */
name: string
/** 显示名称 */
@ -193,9 +235,10 @@ export interface ComponentMetadata extends FunctionalMetadata, FeatureBase {
/** 是否在设置界面中隐藏 (代码仍可操作) */
hidden?: boolean
/** 组件子选项 */
options?: ComponentOptions
options?: Om
/** i18n 数据 */
i18n?: Record<string, LanguagePack>
}
/** 用户组件的非函数基本信息, 用于直接保存为 JSON */
export type UserComponentMetadata = Omit<ComponentMetadata, keyof FunctionalMetadata>

View File

@ -1,4 +1,11 @@
import { ComponentMetadata, componentsMap, ComponentOptions } from '@/components/component'
import {
ComponentMetadata,
componentsMap,
OptionalOptionsMetadata,
OptionsFromOptionalMeta,
OptionsMetadata,
UnknownOptions,
} from '@/components/component'
import { PluginMetadata } from '@/plugins/plugin'
// import serialize from 'serialize-javascript'
import { settings, ComponentSettings } from '../settings'
@ -8,19 +15,26 @@ import { matchUrlPattern } from '../utils'
*
* @param options
*/
export const componentOptionsToSettings = (options: ComponentOptions) => (
lodash.fromPairs(Object.entries(options).map(([key, value]) => [key, value.defaultValue]))
)
export const metadataToOptions = <O extends UnknownOptions>(
options: OptionsMetadata<O>,
): O => lodash.mapValues(options, m => m.defaultValue) as O
/**
*
* @param component
*/
export const componentToSettings = (component: ComponentMetadata): ComponentSettings => (
{
export const componentToSettings = <
Om extends OptionalOptionsMetadata
>(component: ComponentMetadata<Om>): (
ComponentSettings<OptionsFromOptionalMeta<Om>>
) => {
const { options: meta } = component
return {
enabled: component.enabledByDefault ?? true,
options: component.options ? componentOptionsToSettings(component.options) : {},
options: (meta ? metadataToOptions(meta as any) : {}) as any,
}
)
}
/**
*
* @param component
@ -29,6 +43,7 @@ export const isUserComponent = (component: ComponentMetadata | string) => {
const name = typeof component === 'string' ? component : component.name
return Boolean(settings.userComponents[name])
}
/**
*
* @param plugin
@ -45,27 +60,42 @@ const emptySettings: ComponentSettings = {
},
}),
}
// TODO: 参考 discussion #3041。
// 当不兼容代码替换完成后将 R 的默认类型替换为 UnknownOptions
/**
*
*
*
* ComponentSettings
* {
* enabled: false,
* options new Proxy({}, { get: () => false })
* }
*
* @param component
*/
export const getComponentSettings = (component: ComponentMetadata | string): ComponentSettings => {
export const getComponentSettings = <R extends UnknownOptions = UnknownOptions>(
component: ComponentMetadata | string,
): ComponentSettings<R> => {
let componentMetadata: ComponentMetadata
if (typeof component === 'string') {
const componentMetadata = componentsMap[component]
if (componentMetadata === undefined) {
if (componentsMap[component] === undefined) {
if (settings.components.settingsPanel.options.devMode) {
console.warn('No settings found for component:', component)
}
return emptySettings
return emptySettings as any
}
component = componentMetadata
componentMetadata = componentsMap[component]
} else {
componentMetadata = component
}
if (isUserComponent(component)) {
const { name } = component
return settings.userComponents[name]?.settings ?? emptySettings
if (isUserComponent(componentMetadata)) {
const { name } = componentMetadata
return (settings.userComponents[name]?.settings ?? emptySettings) as any
}
return settings.components[component.name]
return settings.components[componentMetadata.name] as any
}
/**
* (`settingsPanel``options`)
*/

View File

@ -1,11 +1,15 @@
import { components, UserComponentMetadata } from '@/components/component'
import {
components,
UnknownOptions,
UserComponentMetadata,
} from '@/components/component'
import { UserStyle } from '@/plugins/style'
import { PluginMetadata, plugins } from '@/plugins/plugin'
import { componentToSettings, isUserComponent } from './helpers'
type Property = string | number | symbol
/** 表示一个组件的设置 */
export interface ComponentSettings<O = { [key: string]: any }> {
export interface ComponentSettings<O extends UnknownOptions = UnknownOptions> {
/** 是否启用此组件 */
enabled: boolean
/** 组件选项 */