Files
IOT_SIM/web/src/designer/preview.ts
T

125 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 把设计器文档转换成与运行时相同格式的控件树,直接用运行时的 ControlView 渲染,
// 保证设计时看到的样子和运行时一致。
import type { ControlNode } from '@iot/shared'
import { controlDef } from './catalog'
import { CHART_TYPE, previewChart, readChart } from './chart'
import type { DesignerDocument } from './document'
import { childrenOf, findBlock, getItems, listControls } from './edit'
import { resourceNameOf, resourceUrl } from './resources'
import { parseValue, type Value } from './values'
/** 窗体在预览树里的 id;控件的 id 就是控件名 */
export const FORM_ID = '__form'
/** 运行时控件树里的类型名:System.Windows.Forms.Button → Button,教材控件库保留完整类名 */
function clientType(type: string) {
if (type === 'AxWMPLib.AxWindowsMediaPlayer') return 'AxWindowsMediaPlayer'
if (type === 'System.Windows.Forms.DataVisualization.Charting.Chart') return 'Chart'
return type.startsWith('System.Windows.Forms.') ? type.slice('System.Windows.Forms.'.length) : type
}
/** 把 Value 转换成运行时消息里的属性值格式(见 web/src/winforms/format.ts) */
export function runtimeValue(value: Value): unknown {
switch (value.kind) {
case 'string':
case 'char':
return value.value
case 'bool':
case 'int':
case 'float':
return value.value
case 'point':
return { x: value.x, y: value.y }
case 'size':
return { width: value.width, height: value.height }
case 'color':
return value.css
case 'font':
return { family: value.family, size: value.size, bold: value.bold, italic: value.italic, underline: value.underline, strikeout: value.strikeout }
case 'enum':
return value.member
case 'raw':
return undefined
}
}
function propsOf(doc: DesignerDocument, target: string): Record<string, unknown> {
const props: Record<string, unknown> = {}
for (const stmt of findBlock(doc, target)?.stmts ?? []) {
if (stmt.kind !== 'prop' || stmt.path.includes('.')) continue
// 项目资源里的图片:预览时直接显示素材文件
const resource = resourceNameOf(stmt.value)
if (resource) {
props[stmt.path] = { key: '', url: resourceUrl(resource), width: 0, height: 0 }
continue
}
const value = runtimeValue(parseValue(stmt.value))
if (value !== undefined) props[stmt.path] = value
}
return props
}
/** 教材控件在 Designer.cs 里不写、但运行时构造函数会设置的外观 */
const DEVICE_DEFAULTS: Record<string, Record<string, unknown>> = {
'WinFormsDeviceControlLibrary.LED': {
BackColor: '#000000',
ForeColor: '#ff0000',
Font: { family: '宋体', size: 70, bold: false, italic: false, underline: false, strikeout: false },
},
'WinFormsDeviceControlLibrary.Lamp': { BackColor: '#ffffff' },
'WinFormsDeviceControlLibrary.Alarm': { BackColor: '#ffffff' },
}
export function previewTree(doc: DesignerDocument): ControlNode {
const types = new Map(listControls(doc).map((c) => [c.name, c.type]))
const build = (name: string): ControlNode => {
const type = types.get(name)!
const props: Record<string, unknown> = { ...DEVICE_DEFAULTS[type], ...propsOf(doc, name) }
// 设计时即使 Visible = false 也显示出来、Enabled = false 也不画成灰色(和 VS 一样,运行后才生效),方便选中和编辑
props.Visible = true
props.Enabled = true
if (type === 'System.Windows.Forms.ComboBox' || type === 'System.Windows.Forms.ListBox')
props.Items = getItems(doc, name).map((item) => {
const v = parseValue(item)
return v.kind === 'string' ? v.value : item
})
// 设计器里不需要滚动:LED 只显示黑底
if (type === 'WinFormsDeviceControlLibrary.LED') props.LedText = ''
// 图表和 VS 一样用示例数据画出各个系列
if (type === CHART_TYPE) props.Chart = previewChart(readChart(doc, name))
// 和 VS 一样显示今天的日期
if (type === 'System.Windows.Forms.DateTimePicker') props.Text = previewDate(String(props.Format ?? 'Long'))
return {
id: name,
type: clientType(type),
name,
props,
children: childrenOf(doc, name)
.filter((c) => types.has(c))
.map(build),
}
}
const formProps = propsOf(doc, '')
return {
id: FORM_ID,
type: 'Form',
name: doc.formName,
props: { ClientSize: { width: 300, height: 300 }, ...formProps, Visible: true },
children: childrenOf(doc, '')
.filter((c) => types.has(c))
.map(build),
}
}
function previewDate(format: string) {
const now = new Date()
const [y, m, d] = [now.getFullYear(), now.getMonth() + 1, now.getDate()]
const time = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
return format === 'Short' ? `${y}/${m}/${d}` : format === 'Time' ? time : `${y}年${m}月${d}日`
}
/** 非可视组件(Timer 等),显示在窗体下方的组件栏 */
export function componentsOf(doc: DesignerDocument) {
return listControls(doc).filter((c) => c.parent === null && controlDef(c.type).component === true)
}