diff --git a/packages/compiler/src/types/componentBlock.ts b/packages/compiler/src/types/componentBlock.ts index ff8f4b8..8653294 100644 --- a/packages/compiler/src/types/componentBlock.ts +++ b/packages/compiler/src/types/componentBlock.ts @@ -11,6 +11,12 @@ export type ComponentBlockPlugins = Record; */ export type ComponentBlockChildren = Record; +export type ComponentBlockPrimaryInput = { + name: string; + value: string; + slots: Record; +} | null; + export interface ComponentBlockOutput { type: "component"; id: number; @@ -43,4 +49,8 @@ export interface ComponentBlockOutput { * 子元素的block的id */ children: ComponentBlockChildren; + /** + * Primary input的信息 + */ + primaryInput: ComponentBlockPrimaryInput; } diff --git a/packages/compiler/src/types/specialBlock.ts b/packages/compiler/src/types/specialBlock.ts index fd0fc50..f6a7635 100644 --- a/packages/compiler/src/types/specialBlock.ts +++ b/packages/compiler/src/types/specialBlock.ts @@ -43,20 +43,7 @@ export interface FuncBlockOutput { type: Exclude; id: number; value: string; - inputs: { - /** - * slot name. i.e. function parameter name - */ - slot: string; - /** - * connected block id - */ - blockId: number; - /** - * connected line start end socket name - */ - socketName: string; - }[]; + slots: Record; output: ConnectTo[]; } diff --git a/packages/compiler/src/viewCompiler.ts b/packages/compiler/src/viewCompiler.ts index c27d53f..5969903 100644 --- a/packages/compiler/src/viewCompiler.ts +++ b/packages/compiler/src/viewCompiler.ts @@ -4,6 +4,7 @@ import { ComponentBlockChildren, ComponentBlockOutput, ComponentBlockPlugins, + ComponentBlockPrimaryInput, ComponentBlockProps, FuncBlockOutput, IfBlockOutput, @@ -103,13 +104,15 @@ const ${this.view.name}_view = ${ `; } - compileStringBlock(block: FuncBlockOutput): string { + compileStringBlock( + block: FuncBlockOutput | Exclude, + ): string { return `\`${block.value.replace(/\{([a-zA-Z0-9]+)\}/g, (_, name) => { - const input = block.inputs.find((i) => i.slot === name); - if (!input) { - throw new Error(`Cannot find input ${name} in block ${block.id}`); + const slot = Object.entries(block.slots).find(([k]) => k === name); + if (!slot) { + throw new Error(`Cannot find input ${name}`); } - return `\${${this.compileDataLineEnd(input)}}`; + return `\${${this.compileDataLineEnd(slot[1])}}`; })}\``; } @@ -117,11 +120,11 @@ const ${this.view.name}_view = ${ block: FuncBlockOutput | StateBlockOutput | StateSetterBlockOutput, ): string { return block.value.replace(/\$([a-zA-Z0-9]+)/g, (_, name) => { - const input = block.inputs.find((i) => i.slot === name); - if (!input) { + const slot = Object.entries(block.slots).find(([k]) => k === name); + if (!slot) { throw new Error(`Cannot find input ${name} in block ${block.id}`); } - return `(${this.compileDataLineEnd(input)})`; + return `(${this.compileDataLineEnd(slot[1])})`; }); } @@ -198,11 +201,11 @@ const ${this.view.name}_view = ${ const result = (() => {${block.value.replace( /\$([a-zA-Z0-9]+)/g, (_, name) => { - const input = block.inputs.find((i) => i.slot === name); - if (!input) { + const slot = Object.entries(block.slots).find(([k]) => k === name); + if (!slot) { throw new Error(`Cannot find input ${name} in block ${block.id}`); } - return `(${this.compileDataLineEnd(input)})`; + return `(${this.compileDataLineEnd(slot[1])})`; }, )}})(); ${this.compileEventLineStart(block.then)}; @@ -331,6 +334,13 @@ const ${this.view.name}_view = ${ return modelId; } + compileComponentPrimaryInput( + primaryInput: ComponentBlockPrimaryInput, + ): Record { + if (!primaryInput) return {}; + return { [primaryInput.name]: this.compileStringBlock(primaryInput) }; + } + compileComponentProps(props: ComponentBlockProps): Record { return Object.fromEntries( Object.entries(props).map(([k, v]) => { @@ -395,6 +405,7 @@ const ${this.view.name}_view = ${ : "" }{ ${Object.entries({ + ...this.compileComponentPrimaryInput(block.primaryInput), ...this.compileComponentProps(block.props), ...this.compileComponentCallbacks(block.callbacks), ...this.compileComponentPlugins(block.plugins), diff --git a/packages/northstar/src/blocks/component/block.ts b/packages/northstar/src/blocks/component/block.ts index b4d2b32..d9edd06 100644 --- a/packages/northstar/src/blocks/component/block.ts +++ b/packages/northstar/src/blocks/component/block.ts @@ -51,12 +51,37 @@ export class ComponentBlock extends RectBlock { info: ComponentInfo; props: Record = {}; + get primaryInputInfo() { + return ( + this.info.inputs.find( + (input) => + input.kind === "as-primary" || input.kind === "as-primary-and-socket", + ) ?? + this.info.contents.find( + (content) => + content.kind === "as-primary" || + content.kind === "as-primary-and-socket", + ) + ); + } + primaryValue = d(""); get primaryFilled() { return this.primaryValue.value !== ""; } getPrimaryDisabled = () => false; + slotsDirection = Direction.TOP; + get slots() { + const template = this.primaryValue.value; + const matches = template.matchAll(/\{[a-zA-Z0-9_]+\}/g); + return [...matches].map((match) => match[0].slice(1, -1)); + } + + get slotSockets() { + return this.getSocketsByPrefix("slot") as SingleInSocket[]; + } + socketUpdater(useSocket: UseSocket): void { const { contents, events, inputs, outputs, methods } = this.info; @@ -67,6 +92,17 @@ export class ComponentBlock extends RectBlock { direction: Direction.LEFT, }); + if (this.primaryInputInfo) { + for (const slot of this.slots) { + useSocket(`slot-${slot}`, SingleInSocket, { + label: slot, + type: "D", + path: PATH_IN_ELIPSE, + direction: this.slotsDirection, + }); + } + } + const shouldHideSocket = (socketInfo: { kind: string; name: string }) => { const prop = this.props[`[${socketInfo.name}]`]; return ( @@ -145,6 +181,7 @@ export class ComponentBlock extends RectBlock { componentType: this.componentType, props: this.props, primaryValue: this.primaryValue.value, + slotsDirection: this.slotsDirection, }; } protected importData(data: any, sockets: Record): void { @@ -153,6 +190,7 @@ export class ComponentBlock extends RectBlock { this.info = blocksObj[data.componentType as keyof typeof blocksObj]; this.props = data.props; this.primaryValue.value = data.primaryValue; + this.slotsDirection = data.slotsDirection; this.content = getContent(this); } } diff --git a/packages/northstar/src/blocks/component/getContent.r.ts b/packages/northstar/src/blocks/component/getContent.r.ts index 2157aee..89a7031 100644 --- a/packages/northstar/src/blocks/component/getContent.r.ts +++ b/packages/northstar/src/blocks/component/getContent.r.ts @@ -4,21 +4,13 @@ import { currentGraph } from "../../store"; import { ComponentBlock } from "./block"; export function getContent(block: ComponentBlock) { - const { - info: { inputs, contents, name }, - props, - } = block; - - const info = - inputs.find(input => input.kind === "as-primary" || input.kind === "as-primary-and-socket") ?? - contents.find(content => content.kind === "as-primary" || content.kind === "as-primary-and-socket"); - const title = (_: Context) => { _.$cls`mx-2 text-sm`; - _.span(name(props)); + _.span(block.info.name(block.props)); }; + const primaryInputInfo = block.primaryInputInfo; - if (!info) return title; + if (!primaryInputInfo) return title; return (_: Context) => { _.$cls`text-gray-600`; title(_); @@ -35,7 +27,7 @@ export function getContent(block: ComponentBlock) { _ => { const inputRef = ref(); _.$css`font-family: Consolas; max-width: 108px; padding-left:4px`; - _.$ref(inputRef) && _.fUnderlineTextInput(block.primaryValue, false, info.name); + _.$ref(inputRef) && _.fUnderlineTextInput(block.primaryValue, false, primaryInputInfo.name); inputRef.current!.inputRef.current!.node.onchange = () => { currentGraph.pushRecord(); }; diff --git a/packages/northstar/src/blocks/component/getProps.ts b/packages/northstar/src/blocks/component/getProps.ts index fcffddd..ecb5504 100644 --- a/packages/northstar/src/blocks/component/getProps.ts +++ b/packages/northstar/src/blocks/component/getProps.ts @@ -1,10 +1,28 @@ +import { directionNameMap, directionMap } from "@quasi-dev/visual-flow"; import { PropData, PropsData } from "../../utils/props"; import { ComponentBlock } from "./block"; export function getProps(block: ComponentBlock): PropsData { const { info } = block; + const slotPos: PropData[] = []; + const primaryInputInfo = block.primaryInputInfo; + if (primaryInputInfo) { + slotPos.push({ + name: "slots pos", + type: "dropdown", + options: ["TOP", "BOTTOM"], + getVal: () => { + return directionNameMap[block.slotsDirection]; + }, + setVal: (val) => { + block.slotsDirection = directionMap[val]; + }, + }); + } + return [ + ...slotPos, ...info.props.map( (v) => ({ @@ -37,8 +55,7 @@ export function getProps(block: ComponentBlock): PropsData { type: "switch", getVal: () => { return ( - block.props[`[${v.name}]`] ?? - v.kind === "as-hidable-socket" + block.props[`[${v.name}]`] ?? v.kind === "as-hidable-socket" ); }, setVal: (val: any) => { diff --git a/packages/northstar/src/blocks/component/toBlockOutput.ts b/packages/northstar/src/blocks/component/toBlockOutput.ts index f425455..08e9406 100644 --- a/packages/northstar/src/blocks/component/toBlockOutput.ts +++ b/packages/northstar/src/blocks/component/toBlockOutput.ts @@ -3,10 +3,15 @@ import type { ComponentBlockChildren, ComponentBlockOutput, ComponentBlockPlugins, + ComponentBlockPrimaryInput, ComponentBlockProps, + ConnectTo, } from "@quasi-dev/compiler"; import { Block, SingleOutSocket, Socket } from "@quasi-dev/visual-flow"; -import { singleOutSocketToOutput } from "../../utils/toOutpus"; +import { + singleInSocketToOutput, + singleOutSocketToOutput, +} from "../../utils/toOutpus"; import { ValidatorBlock } from "../special/validator"; import { ComponentBlock } from "./block"; @@ -28,15 +33,13 @@ export function toBlockOutput(block: ComponentBlock) { if ( input.kind === "as-primary" || (input.kind === "as-primary-and-socket" && block.primaryFilled) - ) { - props[input.name] = block.primaryValue.value; - } else { - const socket = block.getSocketByName(input.name)?.allConnectedLines[0]?.a; - props[input.name] = { - blockId: socket?.block.id ?? NaN, - socketName: socket?.label ?? "", - }; - } + ) + continue; + const socket = block.getSocketByName(input.name)?.allConnectedLines[0]?.a; + props[input.name] = { + blockId: socket?.block.id ?? NaN, + socketName: socket?.label ?? "", + }; } let children = {} as ComponentBlockChildren; @@ -44,16 +47,14 @@ export function toBlockOutput(block: ComponentBlock) { if ( content.kind === "as-primary" || (content.kind === "as-primary-and-socket" && block.primaryFilled) - ) { - children[content.name] = block.primaryValue.value; - } else { - children[content.name] = - block - .getSocketByName(content.name) - ?.allConnectedLines.map((l) => (l.b as Socket).block) - .sort((a, b) => a.boardY - b.boardY) - .map((b) => b.id) ?? []; - } + ) + continue; + children[content.name] = + block + .getSocketByName(content.name) + ?.allConnectedLines.map((l) => (l.b as Socket).block) + .sort((a, b) => a.boardY - b.boardY) + .map((b) => b.id) ?? []; } let plugins = {} as ComponentBlockPlugins; @@ -88,6 +89,20 @@ export function toBlockOutput(block: ComponentBlock) { }`; } + let primaryInput: ComponentBlockPrimaryInput = null; + const primaryInputInfo = block.primaryInputInfo; + if (primaryInputInfo) { + const slots: Record = {}; + for (const socket of block.slotSockets) { + slots[socket.label] = singleInSocketToOutput(socket); + } + primaryInput = { + name: primaryInputInfo.name, + value: block.primaryValue.value, + slots, + }; + } + return { type: "component", func: block.componentType, @@ -98,5 +113,6 @@ export function toBlockOutput(block: ComponentBlock) { props, plugins, children, + primaryInput, } satisfies ComponentBlockOutput; } diff --git a/packages/northstar/src/blocks/special/FuncBlockBase.r.ts b/packages/northstar/src/blocks/special/FuncBlockBase.r.ts index ee777cd..c028a80 100644 --- a/packages/northstar/src/blocks/special/FuncBlockBase.r.ts +++ b/packages/northstar/src/blocks/special/FuncBlockBase.r.ts @@ -1,4 +1,5 @@ import type { + ConnectTo, FuncBlockOutput, FuncBlockTypes, ImpBlockOutput, @@ -149,20 +150,19 @@ export abstract class FuncBlockBase extends RectBlock implements SpecialBlock { } toOutput(): FuncBlockOutput | ValidatorBlockOutput | ImpBlockOutput | StateBlockOutput | StateSetterBlockOutput { - const inputs = []; + const slots: Record = {}; for (const socket of this.inputSockets) { - inputs.push({ - slot: socket.label, + slots[socket.label] = { blockId: socket.connectedLine?.a.block.id ?? NaN, socketName: socket.connectedLine?.a.label ?? "", - }); + }; } return { type: this.type as any, id: this.id, value: this.inputValue.value, - inputs, + slots, output: multiOutSocketToOutput(this.outputSocket), }; }