Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useDebugCompositeKey } from '@root/renderer/hooks/use-debug-composite-key'
import { useOpenPLCStore } from '@root/renderer/store'

import { DebugValueBadge } from './debug-value-badge'

type BlockOutputDebugBadgesProps = {
blockType: string
blockName: string
blockVariableName: string
numericId: string
outputVariables: Array<{ name: string; class: string; type: { definition: string; value: string } }>
connectorStartY: number
connectorOffsetY: number
blockWidth: number
/** Output names that already have a variable node connected showing its own badge. */
connectedOutputNames?: Set<string>
}

/**
* Renders debug value badges next to each output connector of a block node.
* Works for both function blocks (using instance-qualified names) and
* functions (using _TMP_ variable names). Shared between FBD and LD.
*
* Outputs that are connected to a variable node are skipped since the
* variable node already displays its own badge (avoids double badges).
*/
const BlockOutputDebugBadges = ({
blockType,
blockName,
blockVariableName,
numericId,
outputVariables,
connectorStartY,
connectorOffsetY,
blockWidth,
connectedOutputNames,
}: BlockOutputDebugBadgesProps) => {
const {
workspace: { isDebuggerVisible },
} = useOpenPLCStore()
const getCompositeKey = useDebugCompositeKey()

if (!isDebuggerVisible || blockType === 'generic') {
return null
}

const outputs = outputVariables.filter((v) => v.class === 'output' || v.class === 'inOut')

return (
<>
{outputs.map((outputVar, index) => {
if (connectedOutputNames?.has(outputVar.name)) {
return null
}

let compositeKey: string
if (blockType === 'function-block') {
compositeKey = getCompositeKey(`${blockVariableName}.${outputVar.name}`)
} else {
compositeKey = getCompositeKey(`_TMP_${blockName.toUpperCase()}${numericId}_${outputVar.name}`)
}

return (
<div
key={outputVar.name}
className='pointer-events-none absolute'
style={{
top: connectorStartY + index * connectorOffsetY - 10,
left: blockWidth,
}}
>
<DebugValueBadge compositeKey={compositeKey} variableType={outputVar.type.value} position='right' />
</div>
)
})}
</>
)
}

export { BlockOutputDebugBadges }
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useOpenPLCStore } from '@root/renderer/store'
import { cn } from '@root/utils'

type DebugValueBadgeProps = {
compositeKey: string
variableType: string | undefined
position?: 'right' | 'left' | 'below'
}

/**
* Displays a real-time debug value badge next to graphical editor nodes.
* Shows the current polled value for non-BOOL variables when the debugger is active.
* BOOL variables are skipped since they already have dedicated color indicators.
*
* Designed to be used by both FBD and LD variable/block nodes.
*/
const DebugValueBadge = ({ compositeKey, variableType, position = 'right' }: DebugValueBadgeProps) => {
const {
workspace: { debugVariableValues },
} = useOpenPLCStore()

if (!variableType || variableType.toUpperCase() === 'BOOL') {
return null
}

const value = debugVariableValues.get(compositeKey)
if (value === undefined) {
return null
}

const positionClasses: Record<string, string> = {
right: 'left-full ml-1 top-1/2 -translate-y-1/2',
left: 'right-full mr-1 top-1/2 -translate-y-1/2',
below: 'top-full mt-0.5 left-1/2 -translate-x-1/2',
}

return (
<div
className={cn(
'pointer-events-none absolute z-20 flex items-center whitespace-nowrap rounded px-1.5 py-0.5',
'bg-[#2E7D32] text-[10px] font-semibold leading-tight text-white',
'dark:bg-[#388E3C]',
positionClasses[position],
)}
>
{value}
</div>
)
}

export { DebugValueBadge }
export type { DebugValueBadgeProps }
30 changes: 28 additions & 2 deletions src/renderer/components/_atoms/graphical-editor/fbd/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import type { PLCVariable } from '@root/types/PLC'
import { cn, generateNumericUUID } from '@root/utils'
import { newGraphicalEditorNodeID } from '@root/utils/new-graphical-editor-node-id'
import { Node, NodeProps, Position } from '@xyflow/react'
import { FocusEvent, useEffect, useRef, useState } from 'react'
import { FocusEvent, useEffect, useMemo, useRef, useState } from 'react'

import { HighlightedTextArea } from '../../highlighted-textarea'
import { InputWithRef } from '../../input'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip'
import { BlockOutputDebugBadges } from '../block-output-debug-badges'
import { BlockVariant } from '../types/block'
import { getBlockDocumentation, getVariableRestrictionType } from '../utils'
import { buildHandle, CustomHandle } from './handle'
Expand Down Expand Up @@ -352,10 +353,24 @@ export const Block = <T extends object>(block: BlockProps<T>) => {
const [wrongVariable, setWrongVariable] = useState<boolean>(false)
const [hoveringBlock, setHoveringBlock] = useState(false)

const { variables } = getFBDPouVariablesRungNodeAndEdges(editor, pous, fbdFlows, {
const { variables, rung } = getFBDPouVariablesRungNodeAndEdges(editor, pous, fbdFlows, {
nodeId: id ?? '',
})

// Outputs connected to variable nodes already show their own badge — skip those
const connectedOutputNames = useMemo(() => {
const names = new Set<string>()
if (!rung) return names
const outgoingEdges = rung.edges.filter((e) => e.source === id)
for (const edge of outgoingEdges) {
const targetNode = rung.nodes.find((n) => n.id === edge.target)
if (targetNode && typeof targetNode.type === 'string' && targetNode.type.includes('variable')) {
if (edge.sourceHandle) names.add(edge.sourceHandle)
}
}
return names
}, [rung, id])

const inputVariableRef = useRef<
HTMLTextAreaElement & {
blur: ({ submit }: { submit?: boolean }) => void
Expand Down Expand Up @@ -807,6 +822,17 @@ export const Block = <T extends object>(block: BlockProps<T>) => {
{data.handles.map((handle, index) => (
<CustomHandle key={index} {...handle} />
))}
<BlockOutputDebugBadges
blockType={(data.variant as BlockVariant).type}
blockName={(data.variant as BlockVariant).name}
blockVariableName={data.variable?.name ?? ''}
numericId={data.numericId}
outputVariables={(data.variant as BlockVariant).variables}
connectorStartY={DEFAULT_BLOCK_CONNECTOR_Y}
connectorOffsetY={DEFAULT_BLOCK_CONNECTOR_Y_OFFSET}
blockWidth={width ?? DEFAULT_BLOCK_WIDTH}
connectedOutputNames={connectedOutputNames}
/>
</div>
)
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/components/_atoms/graphical-editor/fbd/variable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Modal, ModalContent, ModalTitle } from '../../../_molecules/modal'
import { HighlightedTextArea } from '../../highlighted-textarea'
import { Label } from '../../label'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip'
import { DebugValueBadge } from '../debug-value-badge'
import { BlockVariant } from '../types/block'
import { validateVariableType } from '../utils'
import { FBDBlockAutoComplete } from './autocomplete'
Expand Down Expand Up @@ -656,6 +657,16 @@ const VariableElement = (block: VariableProps) => {
</div>
)}

{isDebuggerVisible && isAVariable && (
<DebugValueBadge
compositeKey={compositeKeyForForced}
variableType={variableType}
position={
data.variant === 'output-variable' ? 'left' : data.variant === 'inout-variable' ? 'below' : 'right'
}
/>
)}

{isDebuggerVisible && contextMenuPosition && (
<Popover.Root open={isContextMenuOpen} onOpenChange={setIsContextMenuOpen}>
<Popover.Portal>
Expand Down
26 changes: 25 additions & 1 deletion src/renderer/components/_atoms/graphical-editor/ladder/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import type { VariableReference } from '@root/types/PLC/variable-reference'
import { cn, generateNumericUUID } from '@root/utils'
import { newGraphicalEditorNodeID } from '@root/utils/new-graphical-editor-node-id'
import { Node, NodeProps, Position } from '@xyflow/react'
import { FocusEvent, useEffect, useRef, useState } from 'react'
import { FocusEvent, useEffect, useMemo, useRef, useState } from 'react'

import { HighlightedTextArea } from '../../highlighted-textarea'
import { InputWithRef } from '../../input'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../tooltip'
import { BlockOutputDebugBadges } from '../block-output-debug-badges'
import { BlockVariant as newBlockVariant } from '../types/block'
import { getBlockDocumentation, getVariableRestrictionType, validateVariableType } from '../utils'
import { buildHandle, CustomHandle } from './handle'
Expand Down Expand Up @@ -433,6 +434,18 @@ export const Block = <T extends object>(block: BlockProps<T>) => {
nodeId: id,
})

const connectedOutputNames = useMemo(() => {
const names = new Set<string>()
if (data.connectedVariables) {
for (const cv of data.connectedVariables) {
if (cv.type === 'output' && cv.variable) {
names.add(cv.handleId)
}
}
}
return names
}, [data.connectedVariables])

const inputVariableRef = useRef<
HTMLTextAreaElement & {
blur: ({ submit }: { submit?: boolean }) => void
Expand Down Expand Up @@ -896,6 +909,17 @@ export const Block = <T extends object>(block: BlockProps<T>) => {
{data.handles.map((handle, index) => (
<CustomHandle key={index} {...handle} />
))}
<BlockOutputDebugBadges
blockType={(data.variant as BlockVariant).type}
blockName={(data.variant as BlockVariant).name}
blockVariableName={data.variable?.name ?? ''}
numericId={data.numericId}
outputVariables={(data.variant as BlockVariant).variables}
connectorStartY={DEFAULT_BLOCK_CONNECTOR_Y}
connectorOffsetY={DEFAULT_BLOCK_CONNECTOR_Y_OFFSET}
blockWidth={width ?? DEFAULT_BLOCK_WIDTH}
connectedOutputNames={connectedOutputNames}
/>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useEffect, useRef, useState } from 'react'
import { Modal, ModalContent, ModalTitle } from '../../../_molecules/modal'
import { HighlightedTextArea } from '../../highlighted-textarea'
import { Label } from '../../label'
import { DebugValueBadge } from '../debug-value-badge'
import { getVariableByName, validateVariableType } from '../utils'
import { VariablesBlockAutoComplete } from './autocomplete'
import { BlockNodeData, BlockVariant, LadderBlockConnectedVariables } from './block'
Expand Down Expand Up @@ -455,6 +456,7 @@ const VariableElement = (block: VariableProps) => {
return (
<>
<div
className='relative'
style={{ width: DEFAULT_VARIABLE_WIDTH, height: DEFAULT_VARIABLE_HEIGHT }}
onClick={isDebuggerVisible ? handleClick : undefined}
>
Expand Down Expand Up @@ -519,6 +521,14 @@ const VariableElement = (block: VariableProps) => {
</div>
)}

{isDebuggerVisible && isAVariable && (
<DebugValueBadge
compositeKey={compositeKey}
variableType={variableType}
position={data.variant === 'output' ? 'left' : 'right'}
/>
)}

{isDebuggerVisible && contextMenuPosition && (
<Popover.Root open={isContextMenuOpen} onOpenChange={setIsContextMenuOpen}>
<Popover.Portal>
Expand Down
Loading