You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

189 lines
4.8 KiB

import type { Component, CSSProperties, Prop, VNodeChild } from 'vue'
import type { Axis, Block, DataRefer, DynamicTheme, DynamicValue, ToStatic, ViewChildren } from './types'
import type { EngineContext } from './context'
import { defineComponent, h } from 'vue'
import { provideBlockId, useBlockId, useContext } from './context'
import {
align,
background,
bordering,
clip,
dynamic,
gap,
insets,
isRefer,
radii,
shadowing,
size,
stack,
unit,
} from './utils'
import DddView from './views/View.vue'
// /**
// * 相关框架组件渲染函数
// */
// export type WidgetRenderFunction = (widget: Widget, view: View, axis?: Axis) => VNodeChild
// /**
// * 注册的外部组件渲染函数
// */
// const widgets: Record<string, WidgetRenderFunction> = {}
const views: Record<string, Component> = {}
export function registerView(v: Record<string, Component>): void {
for (const [key, comp] of Object.entries(v)) {
views[key] = comp
}
}
// export function registerWidget(some: Record<string, WidgetRenderFunction>): void {
// for (const [key, func] of Object.entries(some)) {
// widgets[key] = func
// }
// }
//
// function renderWidget(widget: Widget, view: View, axis?: Axis): VNodeChild {
// const func = widgets[widget.name]
// if (typeof func !== 'function') {
// throw new TypeError(`unknown widget: ${widget.name}`)
// }
// return func(widget, view, axis)
// }
//
// const isWidget = (s: any): s is Widget => s != null && 'name' in s
export function renderRefer(refer: DataRefer, axis?: Axis): VNodeChild {
const { key, type, ...attrs } = refer
const component = views[type]
if (component) {
return h(component, {
source: key,
axis,
...attrs,
})
}
return h('b', {
style: {
color: 'red',
fontSize: '36px',
},
}, 'unimplemented')
}
export function isFlexible(theme: DynamicTheme, value: ToStatic) {
return value(theme.axis) != null
|| value(theme.mainAlign) != null
|| value(theme.crossAlign) != null
|| value(theme.wrap) != null
|| value(theme.gap) != null
}
export function render(
ctx: EngineContext,
blockId: string,
view?: ViewChildren,
axis?: Axis,
): VNodeChild {
if (view == null) {
return null
}
if (typeof view === 'string' || typeof view === 'number') {
return view
}
if (Array.isArray(view)) {
return view.map(child => render(ctx, blockId, child, axis))
}
if (isRefer(view)) {
return renderRefer(view)
}
// if (isWidget(view.theme)) {
// return renderWidget(view.theme, view, axis)
// }
const theme = view.theme
const value = <T>(v: DynamicValue<T>): T => dynamic<T>(ctx, blockId, v)
const css: CSSProperties = {
// boxed & spatial
...insets('padding', value(theme?.padding)),
...insets('margin', value(theme?.margin)),
...size(axis, theme, value), // todo
// decoration
...background(value(theme?.color), value(theme?.image), value(theme?.gradient)),
...bordering(value(theme?.border)),
...radii(value(theme?.radius)),
...shadowing(value(theme?.shadow)),
// textual
color: value(theme?.textColor),
fontSize: unit(value(theme?.fontSize)),
textAlign: value(theme?.textAlign),
lineHeight: value(theme?.lineHeight),
// clip
...clip(value(theme?.clip)),
// stack
...stack(theme, value),
}
// note: 在前面初始化 css 时使用了参数 axis,
// 所以在这里复用它用于子视图构建
axis = value(theme?.axis)
const flexible = value(theme?.flexible)
if (flexible || (theme && flexible == null && isFlexible(theme, value))) {
Object.assign(css, {
display: 'flex',
flexDirection: axis === 'y' ? 'column' : undefined,
justifyContent: align(value(theme?.mainAlign)),
alignItems: align(value(theme?.crossAlign)),
flexWrap: value(theme?.wrap) ? 'wrap' : undefined,
...gap(value(theme?.gap)),
})
}
return h(DddView, {
vid: view.vid,
class: 'ddd-view',
style: css,
}, () => render(ctx, blockId, view.children, axis))
}
export const RenderView = defineComponent({
name: 'RenderView',
props: {
view: {
// type: [Object | Array],
required: true,
} as Prop<ViewChildren>,
},
setup(props) {
const ctx = useContext()
const blockId = useBlockId()
return () => render(ctx, blockId, props.view)
}
})
export const RenderBlock = defineComponent({
name: 'RenderBlock',
props: {
block: {
type: Object,
required: true,
} as Prop<Block>,
},
setup(props) {
// const ctx = useContext()
provideBlockId(props.block!.vid)
return () => {
if (props.block == null) {
return null
}
// return render(ctx, props.block.vid, props.block)
return h(RenderView, {
view: props.block,
})
}
},
})