import BaseComponent, {register} from './BaseComponent.mjs'; import {mergeSets, removeAll} from '../../../core/ArrayUtilities.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', 'fill': 'transparent', }; export class BlockSplit extends BaseComponent { prepareMeasurements({left, tag, label}, env) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type).section; env.textSizer.expectMeasure(config.tag.labelAttrs, tag); env.textSizer.expectMeasure(config.label.labelAttrs, label); } separation({left, right, tag, label}, env) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type).section; const width = ( env.textSizer.measure(config.tag.labelAttrs, tag).width + config.tag.padding.left + config.tag.padding.right + env.textSizer.measure(config.label.labelAttrs, label).width + config.label.padding.left + config.label.padding.right ); env.addSeparation(left, right, width); } renderPre({left, right}) { return { agentIDs: [left, right], }; } render({left, right, tag, label}, env, first = false) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type); const agentInfoL = env.agentInfos.get(left); const agentInfoR = env.agentInfos.get(right); let y = env.primaryY; if(!first) { y += config.section.padding.bottom; } const clickable = env.makeRegion(); const tagRender = env.svg.boxedText({ boxAttrs: config.section.tag.boxAttrs, boxRenderer: config.section.tag.boxRenderer, labelAttrs: config.section.tag.labelAttrs, padding: config.section.tag.padding, }, tag, {x: agentInfoL.x, y}); const labelRender = env.svg.boxedText({ boxAttrs: {'fill': '#000000'}, labelAttrs: config.section.label.labelAttrs, padding: config.section.label.padding, }, label, {x: agentInfoL.x + tagRender.width, y}); const labelHeight = Math.max( Math.max(tagRender.height, labelRender.height), config.section.label.minHeight ); blockInfo.hold.add(tagRender.box); env.lineMaskLayer.add(labelRender.box); clickable.add( env.svg.box(OUTLINE_ATTRS, { height: labelHeight, width: agentInfoR.x - agentInfoL.x, x: agentInfoL.x, y, }), tagRender.label, labelRender.label ); if(!first) { blockInfo.hold.add(config.sepRenderer({ 'x1': agentInfoL.x, 'x2': agentInfoR.x, 'y1': y, 'y2': y, })); } else if(blockInfo.canHide) { clickable.addClass(blockInfo.hide ? 'collapsed' : 'expanded'); } return y + labelHeight + config.section.padding.top; } } export class BlockBegin extends BlockSplit { makeState(state) { state.blocks = new Map(); } resetState(state) { state.blocks.clear(); } storeBlockInfo(stage, env) { const {canHide} = stage; const blockInfo = { canHide, hide: canHide && env.renderer.isCollapsed(stage.ln), hold: null, startY: null, type: stage.blockType, }; env.state.blocks.set(stage.left, blockInfo); return blockInfo; } prepareMeasurements(stage, env) { this.storeBlockInfo(stage, env); super.prepareMeasurements(stage, env); } separationPre(stage, env) { this.storeBlockInfo(stage, env); super.separationPre(stage, env); } separation(stage, env) { mergeSets(env.visibleAgentIDs, [stage.left, stage.right]); super.separation(stage, env); } renderPre(stage, env) { const blockInfo = this.storeBlockInfo(stage, env); const config = env.theme.getBlock(blockInfo.type); return { agentIDs: [stage.left, stage.right], topShift: config.margin.top, }; } render(stage, env) { const hold = env.svg.el('g'); env.blockLayer.add(hold); const blockInfo = env.state.blocks.get(stage.left); blockInfo.hold = hold; blockInfo.startY = env.primaryY; return super.render(stage, env, true); } shouldHide({left}, env) { const blockInfo = env.state.blocks.get(left); return { nest: blockInfo.hide ? 1 : 0, self: false, }; } } export class BlockEnd extends BaseComponent { separation({left, right}, env) { removeAll(env.visibleAgentIDs, [left, right]); } renderPre({left, right}, env) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type); return { agentIDs: [left, right], topShift: config.section.padding.bottom, }; } render({left, right}, env) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type); const agentInfoL = env.agentInfos.get(left); const agentInfoR = env.agentInfos.get(right); let renderFn = config.boxRenderer; if(blockInfo.hide) { renderFn = config.collapsedBoxRenderer || renderFn; } let shapes = renderFn({ height: env.primaryY - blockInfo.startY, width: agentInfoR.x - agentInfoL.x, x: agentInfoL.x, y: blockInfo.startY, }); if(!shapes.shape) { shapes = {shape: shapes}; } blockInfo.hold.add(shapes.shape); env.fillLayer.add(shapes.fill); env.lineMaskLayer.add(shapes.mask); return env.primaryY + config.margin.bottom + env.theme.actionMargin; } shouldHide({left}, env) { const blockInfo = env.state.blocks.get(left); return { nest: blockInfo.hide ? -1 : 0, self: false, }; } } register('block begin', new BlockBegin()); register('block split', new BlockSplit()); register('block end', new BlockEnd());