|
|
@@ -1,149 +1,570 @@
|
|
|
+import type { IProject } from '@/store/modules/project'
|
|
|
+import type { ComponentSchema } from '@/lvgl-widgets/type'
|
|
|
+import type {
|
|
|
+ WidgetEventActionData,
|
|
|
+ WidgetEventTargetData,
|
|
|
+ WidgetEventTriggerData
|
|
|
+} from '@/types/event'
|
|
|
+import type { OptionType } from './type'
|
|
|
+
|
|
|
+import { klona } from 'klona'
|
|
|
+import { bfsWalk } from 'simple-mind-map/src/utils'
|
|
|
+import LvglWidgets from '@/lvgl-widgets'
|
|
|
import { flagOptions, stateOptions } from '@/constants'
|
|
|
|
|
|
-/**
|
|
|
- * @description: 组件事件选项
|
|
|
- */
|
|
|
-export const widgetEventOptions = [
|
|
|
- { label: 'Clicked', value: 'LV_EVENT_SINGLE_CLICKED' },
|
|
|
- { label: 'Short Clicked', value: 'LV_EVENT_SHORT_CLICKED' },
|
|
|
- { label: 'Key', value: 'LV_EVENT_KEY' },
|
|
|
- { label: 'Pressed', value: 'LV_EVENT_PRESSED' },
|
|
|
- { label: 'Pressing', value: 'LV_EVENT_PRESSING' },
|
|
|
- { label: 'Press Lost', value: 'LV_EVENT_PRESS_LOST' },
|
|
|
- { label: 'Long Pressed', value: 'LV_EVENT_LONG_PRESSED' },
|
|
|
- { label: 'Long Pressed Repeat', value: 'LV_EVENT_LONG_PRESSED_REPEAT' },
|
|
|
- { label: 'Released', value: 'LV_EVENT_RELEASED' },
|
|
|
- { label: 'Value Changed', value: 'LV_EVENT_VALUE_CHANGED' },
|
|
|
- { label: 'Scroll', value: 'LV_EVENT_SCROLL' },
|
|
|
- { label: 'Scroll Begin', value: 'LV_EVENT_SCROLL_BEGIN' },
|
|
|
- { label: 'Scroll End', value: 'LV_EVENT_SCROLL_END' },
|
|
|
- { label: 'Focused', value: 'LV_EVENT_FOCUSED' },
|
|
|
- { label: 'Defocused', value: 'LV_EVENT_DEFOCUSED' },
|
|
|
- { label: 'Leave', value: 'LV_EVENT_LEAVE' },
|
|
|
- { label: 'Hit Test', value: 'LV_EVENT_HIT_TEST' }
|
|
|
+type SchemaActionDescriptor = {
|
|
|
+ actionKey: string
|
|
|
+ defaultValue: any
|
|
|
+ label: string
|
|
|
+ schemas: ComponentSchema[]
|
|
|
+}
|
|
|
+
|
|
|
+const pageLoadAnimationOptions = [
|
|
|
+ { label: 'None', value: 'none' },
|
|
|
+ { label: 'Over Left', value: 'over_left' },
|
|
|
+ { label: 'Over Right', value: 'over_right' },
|
|
|
+ { label: 'Over Top', value: 'over_top' },
|
|
|
+ { label: 'Over Bottom', value: 'over_bottom' },
|
|
|
+ { label: 'Move Left', value: 'move_left' },
|
|
|
+ { label: 'Move Right', value: 'move_right' },
|
|
|
+ { label: 'Move Top', value: 'move_top' },
|
|
|
+ { label: 'Move Bottom', value: 'move_bottom' },
|
|
|
+ { label: 'Fade In', value: 'fade_in' },
|
|
|
+ { label: 'Fade On', value: 'fade_on' },
|
|
|
+ { label: 'Fade Out', value: 'fade_out' },
|
|
|
+ { label: 'Out Left', value: 'out_left' },
|
|
|
+ { label: 'Out Right', value: 'out_right' },
|
|
|
+ { label: 'Out Top', value: 'out_top' },
|
|
|
+ { label: 'Out Bottom', value: 'out_bottom' }
|
|
|
+]
|
|
|
+
|
|
|
+const eventOption = (
|
|
|
+ label: string,
|
|
|
+ eventCode: string,
|
|
|
+ extra: Omit<WidgetEventTriggerData, 'eventCode'> = {}
|
|
|
+) =>
|
|
|
+ ({
|
|
|
+ label,
|
|
|
+ value:
|
|
|
+ extra.key || extra.direction ? `${eventCode}:${extra.key || extra.direction}` : eventCode,
|
|
|
+ data: {
|
|
|
+ eventCode,
|
|
|
+ ...extra
|
|
|
+ } satisfies WidgetEventTriggerData
|
|
|
+ }) satisfies OptionType
|
|
|
+
|
|
|
+const commonEventOptions = [
|
|
|
+ eventOption('Clicked', 'LV_EVENT_CLICKED'),
|
|
|
+ eventOption('Short Clicked', 'LV_EVENT_SHORT_CLICKED'),
|
|
|
+ eventOption('Single Clicked', 'LV_EVENT_SINGLE_CLICKED'),
|
|
|
+ eventOption('Double Clicked', 'LV_EVENT_DOUBLE_CLICKED'),
|
|
|
+ eventOption('Triple Clicked', 'LV_EVENT_TRIPLE_CLICKED'),
|
|
|
+ eventOption('Pressed', 'LV_EVENT_PRESSED'),
|
|
|
+ eventOption('Pressing', 'LV_EVENT_PRESSING'),
|
|
|
+ eventOption('Press Lost', 'LV_EVENT_PRESS_LOST'),
|
|
|
+ eventOption('Long Pressed', 'LV_EVENT_LONG_PRESSED'),
|
|
|
+ eventOption('Long Pressed Repeat', 'LV_EVENT_LONG_PRESSED_REPEAT'),
|
|
|
+ eventOption('Released', 'LV_EVENT_RELEASED'),
|
|
|
+ eventOption('Value Changed', 'LV_EVENT_VALUE_CHANGED'),
|
|
|
+ eventOption('Scroll', 'LV_EVENT_SCROLL'),
|
|
|
+ eventOption('Scroll Begin', 'LV_EVENT_SCROLL_BEGIN'),
|
|
|
+ eventOption('Scroll End', 'LV_EVENT_SCROLL_END'),
|
|
|
+ eventOption('Gesture Top', 'LV_EVENT_GESTURE', { direction: 'LV_DIR_TOP' }),
|
|
|
+ eventOption('Gesture Bottom', 'LV_EVENT_GESTURE', { direction: 'LV_DIR_BOTTOM' }),
|
|
|
+ eventOption('Gesture Left', 'LV_EVENT_GESTURE', { direction: 'LV_DIR_LEFT' }),
|
|
|
+ eventOption('Gesture Right', 'LV_EVENT_GESTURE', { direction: 'LV_DIR_RIGHT' }),
|
|
|
+ eventOption('Key', 'LV_EVENT_KEY'),
|
|
|
+ eventOption('Key Up', 'LV_EVENT_KEY', { key: 'LV_KEY_UP' }),
|
|
|
+ eventOption('Key Down', 'LV_EVENT_KEY', { key: 'LV_KEY_DOWN' }),
|
|
|
+ eventOption('Key Right', 'LV_EVENT_KEY', { key: 'LV_KEY_RIGHT' }),
|
|
|
+ eventOption('Key Left', 'LV_EVENT_KEY', { key: 'LV_KEY_LEFT' }),
|
|
|
+ eventOption('Key Esc', 'LV_EVENT_KEY', { key: 'LV_KEY_ESC' }),
|
|
|
+ eventOption('Key Del', 'LV_EVENT_KEY', { key: 'LV_KEY_DEL' }),
|
|
|
+ eventOption('Key Backspace', 'LV_EVENT_KEY', { key: 'LV_KEY_BACKSPACE' }),
|
|
|
+ eventOption('Key Enter', 'LV_EVENT_KEY', { key: 'LV_KEY_ENTER' }),
|
|
|
+ eventOption('Key Next', 'LV_EVENT_KEY', { key: 'LV_KEY_NEXT' }),
|
|
|
+ eventOption('Key Prev', 'LV_EVENT_KEY', { key: 'LV_KEY_PREV' }),
|
|
|
+ eventOption('Key Home', 'LV_EVENT_KEY', { key: 'LV_KEY_HOME' }),
|
|
|
+ eventOption('Key End', 'LV_EVENT_KEY', { key: 'LV_KEY_END' }),
|
|
|
+ eventOption('Focused', 'LV_EVENT_FOCUSED'),
|
|
|
+ eventOption('Defocused', 'LV_EVENT_DEFOCUSED'),
|
|
|
+ eventOption('Leave', 'LV_EVENT_LEAVE'),
|
|
|
+ eventOption('Hit Test', 'LV_EVENT_HIT_TEST'),
|
|
|
+ eventOption('Insert', 'LV_EVENT_INSERT'),
|
|
|
+ eventOption('Ready', 'LV_EVENT_READY'),
|
|
|
+ eventOption('Cancel', 'LV_EVENT_CANCEL')
|
|
|
]
|
|
|
|
|
|
-/**
|
|
|
- * 页面事件选项
|
|
|
- */
|
|
|
+export const widgetEventOptions = [...commonEventOptions]
|
|
|
+
|
|
|
export const pageEventOptions = [
|
|
|
- { label: 'Clicked', value: 'LV_EVENT_SINGLE_CLICKED' },
|
|
|
- { label: 'Short Clicked', value: 'LV_EVENT_SHORT_CLICKED' },
|
|
|
- { label: 'Key', value: 'LV_EVENT_KEY' },
|
|
|
- { label: 'Pressed', value: 'LV_EVENT_PRESSED' },
|
|
|
- { label: 'Pressing', value: 'LV_EVENT_PRESSING' },
|
|
|
- { label: 'Press Lost', value: 'LV_EVENT_PRESS_LOST' },
|
|
|
- { label: 'Long Pressed', value: 'LV_EVENT_LONG_PRESSED' },
|
|
|
- { label: 'Long Pressed Repeat', value: 'LV_EVENT_LONG_PRESSED_REPEAT' },
|
|
|
- { label: 'Released', value: 'LV_EVENT_RELEASED' },
|
|
|
- { label: 'Value Changed', value: 'LV_EVENT_VALUE_CHANGED' },
|
|
|
- { label: 'Scroll', value: 'LV_EVENT_SCROLL' },
|
|
|
- { label: 'Scroll Begin', value: 'LV_EVENT_SCROLL_BEGIN' },
|
|
|
- { label: 'Scroll End', value: 'LV_EVENT_SCROLL_END' },
|
|
|
- { label: 'Focused', value: 'LV_EVENT_FOCUSED' },
|
|
|
- { label: 'Defocused', value: 'LV_EVENT_DEFOCUSED' },
|
|
|
- { label: 'Leave', value: 'LV_EVENT_LEAVE' },
|
|
|
- { label: 'Hit Test', value: 'LV_EVENT_HIT_TEST' },
|
|
|
- { label: 'Unload Start', value: 'LV_EVENT_SCREEN_UNLOAD_START' },
|
|
|
- { label: 'Load Start', value: 'LV_EVENT_SCREEN_LOAD_START' },
|
|
|
- { label: 'Loaded', value: 'LV_EVENT_SCREEN_LOADED' },
|
|
|
- { label: 'Unloaded', value: 'LV_EVENT_SCREEN_UNLOADED' },
|
|
|
- { label: 'Created', value: 'LV_EVENT_CHILD_CREATED' }
|
|
|
+ ...commonEventOptions,
|
|
|
+ eventOption('Load Start', 'LV_EVENT_SCREEN_LOAD_START'),
|
|
|
+ eventOption('Loaded', 'LV_EVENT_SCREEN_LOADED'),
|
|
|
+ eventOption('Unload Start', 'LV_EVENT_SCREEN_UNLOAD_START'),
|
|
|
+ eventOption('Unloaded', 'LV_EVENT_SCREEN_UNLOADED')
|
|
|
]
|
|
|
|
|
|
-/**
|
|
|
- * @description: 动作选项
|
|
|
- */
|
|
|
-export const actionOptions = [
|
|
|
- { label: 'X', value: 'x', valueType: 'number', defaultValue: 0 },
|
|
|
- { label: 'Y', value: 'y', valueType: 'number', defaultValue: 0 },
|
|
|
- { label: 'Width', value: 'width', valueType: 'number', defaultValue: 100 },
|
|
|
- { label: 'Height', value: 'height', valueType: 'number', defaultValue: 100 },
|
|
|
- { label: 'Text', value: 'text', valueType: 'string', defaultValue: 'default' },
|
|
|
- { label: 'Font Size', value: 'font_size', valueType: 'number', defaultValue: 12 },
|
|
|
- {
|
|
|
- label: 'Add Flag',
|
|
|
- value: 'add_flag',
|
|
|
- valueType: 'select',
|
|
|
- multiple: true
|
|
|
- },
|
|
|
- {
|
|
|
- label: 'Remove Flag',
|
|
|
- value: 'remove_flag',
|
|
|
- valueType: 'select',
|
|
|
- multiple: true
|
|
|
- },
|
|
|
- { label: 'Background Color', value: 'bg_color', valueType: 'color' },
|
|
|
- { label: 'Background Alpha', value: 'bg_alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Gradient Color', value: 'bg_grad_color', valueType: 'color' },
|
|
|
- {
|
|
|
- label: 'Gradient Direction',
|
|
|
- value: 'grad_dir',
|
|
|
- valueType: 'select'
|
|
|
- },
|
|
|
- { label: 'Background Image Alpha', value: 'bg_img_alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Background Image Render Color', value: 'bg_img_render_color', valueType: 'color' },
|
|
|
- { label: 'Image Alpha', value: 'img_alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Image Render Color', value: 'img_render_color', valueType: 'color' },
|
|
|
- { label: 'Image Render Alpha', value: 'img_render_alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Alpha', value: 'alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- {
|
|
|
- label: 'Border',
|
|
|
- value: 'border_type',
|
|
|
- valueType: 'select'
|
|
|
- },
|
|
|
- { label: 'Border Size', value: 'border_width', valueType: 'number', defaultValue: 1 },
|
|
|
- { label: 'Border Alpha', value: 'border_alpha', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Border Color', value: 'border_color', valueType: 'color' },
|
|
|
- { label: 'Light', value: 'led_set_light', valueType: 'number', min: 0, max: 255 },
|
|
|
- { label: 'Color', value: 'led_color', valueType: 'color' },
|
|
|
- { label: 'Radius', value: 'radius', valueType: 'number' },
|
|
|
- { label: 'Visible', value: 'visible', valueType: 'boolean' },
|
|
|
- { label: 'Add State', value: 'add_state', valueType: 'select' },
|
|
|
- { label: 'Remove State', value: 'remove_state', valueType: 'select' },
|
|
|
- {
|
|
|
- label: 'Rotate',
|
|
|
- value: 'rotate',
|
|
|
- valueType: 'rotate',
|
|
|
- defaultValue: {
|
|
|
- x: 0,
|
|
|
- y: 0,
|
|
|
- angle: 0
|
|
|
+const builtinVisibleAction: SchemaActionDescriptor = {
|
|
|
+ actionKey: 'builtin.visible',
|
|
|
+ label: '显示',
|
|
|
+ defaultValue: true,
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ label: '显示',
|
|
|
+ field: 'payload',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'switch'
|
|
|
}
|
|
|
- },
|
|
|
- { label: 'Zoom', value: 'zoom', valueType: 'number' },
|
|
|
- { label: 'Widget Value', value: 'widget_value', valueType: 'string' },
|
|
|
- {
|
|
|
- label: 'Play Animation',
|
|
|
- value: 'play_animation',
|
|
|
- valueType: 'animation',
|
|
|
- defaultValue: {
|
|
|
- animation: undefined,
|
|
|
- before: undefined,
|
|
|
- after: undefined
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+function getSchemaLabel(schema: ComponentSchema): string {
|
|
|
+ if (schema.label) return schema.label
|
|
|
+
|
|
|
+ const prefix = typeof schema.slots === 'object' ? schema.slots?.prefix : undefined
|
|
|
+ if (typeof prefix === 'string' && prefix.trim()) return prefix
|
|
|
+
|
|
|
+ return schema.field?.split('.').pop() || '动作'
|
|
|
+}
|
|
|
+
|
|
|
+function isSchemaActionable(schema?: ComponentSchema): boolean {
|
|
|
+ if (!schema?.field) return false
|
|
|
+ if (schema.valueType === 'dependency') return false
|
|
|
+ if (schema.valueType === 'part') return false
|
|
|
+ if (schema.valueType === 'code') return false
|
|
|
+ if (!schema.valueType) return false
|
|
|
+ if (schema.field === 'name') return false
|
|
|
+ if (schema.field === 'props.head_code') return false
|
|
|
+ if (schema.field === 'props.feature_code') return false
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+function createDefaultValue(schema: ComponentSchema): any {
|
|
|
+ if ((schema as any).defaultValue !== undefined) return klona((schema as any).defaultValue)
|
|
|
+
|
|
|
+ switch (schema.valueType) {
|
|
|
+ case 'number':
|
|
|
+ case 'slider':
|
|
|
+ return 0
|
|
|
+ case 'switch':
|
|
|
+ return false
|
|
|
+ case 'checkbox':
|
|
|
+ return []
|
|
|
+ case 'select':
|
|
|
+ case 'text':
|
|
|
+ case 'textarea':
|
|
|
+ case 'image':
|
|
|
+ case 'file':
|
|
|
+ case 'symbol':
|
|
|
+ case 'date':
|
|
|
+ case 'time':
|
|
|
+ case 'font':
|
|
|
+ return ''
|
|
|
+ case 'color':
|
|
|
+ return '#000000ff'
|
|
|
+ case 'group':
|
|
|
+ return {}
|
|
|
+ case 'animation':
|
|
|
+ return {
|
|
|
+ animation: undefined,
|
|
|
+ before: undefined,
|
|
|
+ after: undefined
|
|
|
+ }
|
|
|
+ case 'rotate':
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ angle: 0
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function cloneActionSchema(schema: ComponentSchema): ComponentSchema {
|
|
|
+ const cloned = klona(schema)
|
|
|
+ cloned.field = 'payload'
|
|
|
+ return cloned
|
|
|
+}
|
|
|
+
|
|
|
+function flattenComponentSchemas(items: ComponentSchema[] = []): SchemaActionDescriptor[] {
|
|
|
+ const result: SchemaActionDescriptor[] = []
|
|
|
+
|
|
|
+ items.forEach((item) => {
|
|
|
+ if (!item) return
|
|
|
+
|
|
|
+ if (item.valueType === 'group') {
|
|
|
+ if (!isSchemaActionable(item)) {
|
|
|
+ result.push(...flattenComponentSchemas(item.children || []))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ result.push({
|
|
|
+ actionKey: item.field!,
|
|
|
+ label: getSchemaLabel(item),
|
|
|
+ schemas: [cloneActionSchema(item)],
|
|
|
+ defaultValue: createDefaultValue(item)
|
|
|
+ })
|
|
|
+ return
|
|
|
}
|
|
|
+
|
|
|
+ if (!isSchemaActionable(item)) return
|
|
|
+
|
|
|
+ result.push({
|
|
|
+ actionKey: item.field!,
|
|
|
+ label: getSchemaLabel(item),
|
|
|
+ schemas: [cloneActionSchema(item)],
|
|
|
+ defaultValue: createDefaultValue(item)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+function getStyleActionDescriptors(items: ComponentSchema[] = []): SchemaActionDescriptor[] {
|
|
|
+ return items
|
|
|
+ .filter((item) => !!item && isSchemaActionable(item))
|
|
|
+ .map((item) => ({
|
|
|
+ actionKey: `style.${item.field}`,
|
|
|
+ label: item.label || item.field || '样式',
|
|
|
+ schemas: [cloneActionSchema(item)],
|
|
|
+ defaultValue: createDefaultValue(item)
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+function getTargetModel(targetData?: WidgetEventTargetData) {
|
|
|
+ if (!targetData) return
|
|
|
+ if (targetData.kind === 'page' || targetData.kind === 'load_page') return LvglWidgets.page
|
|
|
+ if (!targetData.targetType) return
|
|
|
+ return LvglWidgets[targetData.targetType]
|
|
|
+}
|
|
|
+
|
|
|
+function getSchemaActionDescriptors(targetData?: WidgetEventTargetData): SchemaActionDescriptor[] {
|
|
|
+ const model = getTargetModel(targetData)
|
|
|
+ if (!model) return []
|
|
|
+
|
|
|
+ return [
|
|
|
+ builtinVisibleAction,
|
|
|
+ ...flattenComponentSchemas(model.config.props || []),
|
|
|
+ ...flattenComponentSchemas(model.config.coreProps || []),
|
|
|
+ ...getStyleActionDescriptors(model.config.styles || [])
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+function getLoadPageActionDescriptor(
|
|
|
+ project?: IProject,
|
|
|
+ currentPageId?: string
|
|
|
+): SchemaActionDescriptor {
|
|
|
+ const pageOptions =
|
|
|
+ project?.screens.flatMap((screen) =>
|
|
|
+ screen.pages
|
|
|
+ .filter((page) => page.id !== currentPageId)
|
|
|
+ .map((page) => ({
|
|
|
+ label: `[${screen.name}] ${page.name}`,
|
|
|
+ value: page.id
|
|
|
+ }))
|
|
|
+ ) || []
|
|
|
+
|
|
|
+ return {
|
|
|
+ actionKey: 'builtin.load_page',
|
|
|
+ label: '加载页面',
|
|
|
+ defaultValue: {
|
|
|
+ pageId: pageOptions[0]?.value || '',
|
|
|
+ animationType: 'none',
|
|
|
+ duration: 0,
|
|
|
+ delay: 0,
|
|
|
+ autoDeletePrevious: false,
|
|
|
+ cleanupCurrent: false
|
|
|
+ },
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ label: '目标页面',
|
|
|
+ field: 'payload.pageId',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'select',
|
|
|
+ componentProps: {
|
|
|
+ options: pageOptions
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '动画类型',
|
|
|
+ field: 'payload.animationType',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'select',
|
|
|
+ componentProps: {
|
|
|
+ options: pageLoadAnimationOptions
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '动画时长(ms)',
|
|
|
+ field: 'payload.duration',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'number',
|
|
|
+ componentProps: {
|
|
|
+ min: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '动画延时(ms)',
|
|
|
+ field: 'payload.delay',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'number',
|
|
|
+ componentProps: {
|
|
|
+ min: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '自动删除旧页面',
|
|
|
+ field: 'payload.autoDeletePrevious',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'switch'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '清理当前页面',
|
|
|
+ field: 'payload.cleanupCurrent',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'switch'
|
|
|
+ }
|
|
|
+ ]
|
|
|
}
|
|
|
-]
|
|
|
+}
|
|
|
+
|
|
|
+function getLanguageSwitchActionDescriptor(project?: IProject): SchemaActionDescriptor {
|
|
|
+ const languageOptions =
|
|
|
+ project?.languages.map((item) => ({
|
|
|
+ label: item.key,
|
|
|
+ value: item.key
|
|
|
+ })) || []
|
|
|
+
|
|
|
+ return {
|
|
|
+ actionKey: 'builtin.language_switch',
|
|
|
+ label: '语言切换',
|
|
|
+ defaultValue: {
|
|
|
+ languageKey: languageOptions[0]?.value || '',
|
|
|
+ refreshPage: true
|
|
|
+ },
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ label: '语言',
|
|
|
+ field: 'payload.languageKey',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'select',
|
|
|
+ componentProps: {
|
|
|
+ options: languageOptions
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '刷新页面',
|
|
|
+ field: 'payload.refreshPage',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'switch'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getThemeSwitchActionDescriptor(project?: IProject): SchemaActionDescriptor {
|
|
|
+ const themeOptions =
|
|
|
+ project?.themes.map((item) => ({
|
|
|
+ label: item.key,
|
|
|
+ value: item.key
|
|
|
+ })) || []
|
|
|
+
|
|
|
+ const preferredTheme =
|
|
|
+ themeOptions.find((item) => item.value !== project?.currentTheme)?.value ||
|
|
|
+ themeOptions[0]?.value ||
|
|
|
+ ''
|
|
|
+
|
|
|
+ const isDualScreen = project?.meta?.screenType === 'double'
|
|
|
+
|
|
|
+ return {
|
|
|
+ actionKey: 'builtin.theme_switch',
|
|
|
+ label: '主题切换',
|
|
|
+ defaultValue: {
|
|
|
+ screen1Theme: preferredTheme,
|
|
|
+ ...(isDualScreen ? { screen2Theme: preferredTheme } : {})
|
|
|
+ },
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ label: '屏幕1主题',
|
|
|
+ field: 'payload.screen1Theme',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'select',
|
|
|
+ componentProps: {
|
|
|
+ options: themeOptions
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ...(isDualScreen
|
|
|
+ ? [
|
|
|
+ {
|
|
|
+ label: '屏幕2主题',
|
|
|
+ field: 'payload.screen2Theme',
|
|
|
+ labelWidth: '120px',
|
|
|
+ valueType: 'select',
|
|
|
+ componentProps: {
|
|
|
+ options: themeOptions
|
|
|
+ }
|
|
|
+ } satisfies ComponentSchema
|
|
|
+ ]
|
|
|
+ : [])
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getBuiltinActionDescriptors(
|
|
|
+ targetData?: WidgetEventTargetData,
|
|
|
+ project?: IProject,
|
|
|
+ currentPageId?: string
|
|
|
+) {
|
|
|
+ if (!targetData) return []
|
|
|
+
|
|
|
+ switch (targetData.kind) {
|
|
|
+ case 'load_page':
|
|
|
+ return [getLoadPageActionDescriptor(project, currentPageId)]
|
|
|
+ case 'language_switch':
|
|
|
+ return [getLanguageSwitchActionDescriptor(project)]
|
|
|
+ case 'theme_switch':
|
|
|
+ return [getThemeSwitchActionDescriptor(project)]
|
|
|
+ case 'custom_code':
|
|
|
+ return []
|
|
|
+ default:
|
|
|
+ return getSchemaActionDescriptors(targetData)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function isBuiltinTarget(targetData?: WidgetEventTargetData) {
|
|
|
+ return (
|
|
|
+ targetData?.kind === 'custom_code' ||
|
|
|
+ targetData?.kind === 'load_page' ||
|
|
|
+ targetData?.kind === 'language_switch' ||
|
|
|
+ targetData?.kind === 'theme_switch'
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export function getBuiltinActionData(
|
|
|
+ targetData?: WidgetEventTargetData
|
|
|
+): WidgetEventActionData | null {
|
|
|
+ if (!targetData) return null
|
|
|
+
|
|
|
+ switch (targetData.kind) {
|
|
|
+ case 'load_page':
|
|
|
+ return { actionKey: 'builtin.load_page' }
|
|
|
+ case 'language_switch':
|
|
|
+ return { actionKey: 'builtin.language_switch' }
|
|
|
+ case 'theme_switch':
|
|
|
+ return { actionKey: 'builtin.theme_switch' }
|
|
|
+ default:
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function getEventOptions(widgetType?: string) {
|
|
|
+ return widgetType === 'page' ? pageEventOptions : widgetEventOptions
|
|
|
+}
|
|
|
+
|
|
|
+export function getTargetOptions(project?: IProject): OptionType[] {
|
|
|
+ const options: OptionType[] = [
|
|
|
+ {
|
|
|
+ label: '加载页面',
|
|
|
+ value: 'builtin:load_page',
|
|
|
+ data: {
|
|
|
+ kind: 'load_page'
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '自定义代码',
|
|
|
+ value: 'builtin:custom_code',
|
|
|
+ data: {
|
|
|
+ kind: 'custom_code'
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '语言切换',
|
|
|
+ value: 'builtin:language_switch',
|
|
|
+ data: {
|
|
|
+ kind: 'language_switch'
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '主题切换',
|
|
|
+ value: 'builtin:theme_switch',
|
|
|
+ data: {
|
|
|
+ kind: 'theme_switch'
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ project?.screens.forEach((screen) => {
|
|
|
+ screen.pages.forEach((page) => {
|
|
|
+ options.push({
|
|
|
+ label: `[${screen.name}] [Page] ${page.name}`,
|
|
|
+ value: `page:${page.id}`,
|
|
|
+ data: {
|
|
|
+ kind: 'page',
|
|
|
+ pageId: page.id,
|
|
|
+ screenId: screen.id,
|
|
|
+ targetId: page.id,
|
|
|
+ targetType: 'page'
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ })
|
|
|
+
|
|
|
+ bfsWalk(page, (child) => {
|
|
|
+ if (child.id === page.id) return
|
|
|
+
|
|
|
+ options.push({
|
|
|
+ label: `[${screen.name}] [${page.name}] ${child.name}`,
|
|
|
+ value: `widget:${child.id}`,
|
|
|
+ data: {
|
|
|
+ kind: 'widget',
|
|
|
+ pageId: page.id,
|
|
|
+ screenId: screen.id,
|
|
|
+ targetId: child.id,
|
|
|
+ targetType: child.type
|
|
|
+ } satisfies WidgetEventTargetData
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return options
|
|
|
+}
|
|
|
+
|
|
|
+export function getActionOptions(
|
|
|
+ targetData?: WidgetEventTargetData,
|
|
|
+ project?: IProject,
|
|
|
+ currentPageId?: string
|
|
|
+): OptionType[] {
|
|
|
+ const descriptors = getBuiltinActionDescriptors(targetData, project, currentPageId)
|
|
|
+
|
|
|
+ return descriptors.map((item) => ({
|
|
|
+ label: item.label,
|
|
|
+ value: item.actionKey,
|
|
|
+ data: {
|
|
|
+ actionKey: item.actionKey
|
|
|
+ } satisfies WidgetEventActionData,
|
|
|
+ defaultValue: klona(item.defaultValue)
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+export function resolveActionDescriptor(
|
|
|
+ targetData?: WidgetEventTargetData,
|
|
|
+ actionData?: WidgetEventActionData,
|
|
|
+ project?: IProject,
|
|
|
+ currentPageId?: string
|
|
|
+) {
|
|
|
+ if (!targetData || !actionData?.actionKey) return
|
|
|
+
|
|
|
+ return getBuiltinActionDescriptors(targetData, project, currentPageId).find(
|
|
|
+ (item) => item.actionKey === actionData.actionKey
|
|
|
+ )
|
|
|
+}
|
|
|
|
|
|
-/**
|
|
|
- * @description: 选项map
|
|
|
- */
|
|
|
export const optionMap = {
|
|
|
- add_flag: flagOptions,
|
|
|
- remove_flag: flagOptions,
|
|
|
- add_state: stateOptions,
|
|
|
- remove_state: stateOptions,
|
|
|
- grid_dir: [
|
|
|
- { label: 'None', value: 'none' },
|
|
|
- { label: 'Vertical', value: 'vertical' },
|
|
|
- { label: 'Horizontal', value: 'horizontal' }
|
|
|
+ visible: [
|
|
|
+ { label: '显示', value: true },
|
|
|
+ { label: '隐藏', value: false }
|
|
|
],
|
|
|
- border_type: [
|
|
|
- { label: 'None', value: 'none' },
|
|
|
- { label: 'All', value: 'all' },
|
|
|
- { label: 'Top', value: 'top' },
|
|
|
- { label: 'Bottom', value: 'bottom' },
|
|
|
- { label: 'Left', value: 'left' },
|
|
|
- { label: 'Right', value: 'right' }
|
|
|
- ]
|
|
|
+ flags: flagOptions,
|
|
|
+ states: stateOptions
|
|
|
}
|