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
14 changes: 8 additions & 6 deletions calm-plugins/vscode/src/webview/canvas/edges/TooltipEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useRef, useCallback } from 'react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, getStraightPath, type EdgeProps } from 'reactflow';
import { theme } from '../theme-colors';

export type EdgeDirection = 'source-to-target' | 'bidirectional' | 'none';
export type EdgeLineStyle = 'solid' | 'dashed' | 'dotted';
Expand Down Expand Up @@ -56,8 +57,9 @@ export function TooltipEdge({
markerStart={markerStart}
style={{
...(style as React.CSSProperties),
stroke: hovered ? theme.link : theme.fgMuted,
...(dashArray ? { strokeDasharray: dashArray } : {}),
...(hovered ? { stroke: '#60a5fa', strokeWidth: 2.5 } : {}),
...(hovered ? { strokeWidth: 2.5 } : {}),
}}
/>

Expand All @@ -82,11 +84,11 @@ export function TooltipEdge({
pointerEvents: 'none',
fontSize: '9px',
fontWeight: 500,
color: '#64748b',
background: 'rgba(248,250,252,0.9)',
color: theme.fgMuted,
background: theme.bgWidget,
padding: '1px 5px',
borderRadius: '3px',
border: '1px solid rgba(148,163,184,0.3)',
border: `1px solid ${theme.border}`,
}}
>
{label}
Expand Down Expand Up @@ -115,7 +117,7 @@ export function TooltipEdge({
{protocol && (
<div style={tooltipRow}>
<span style={tooltipLabel}>Protocol</span>
<span style={{ ...tooltipValue, color: '#60a5fa' }}>{protocol}</span>
<span style={{ ...tooltipValue, color: theme.link }}>{protocol}</span>
</div>
)}
{description && (
Expand All @@ -137,7 +139,7 @@ const tooltipStyle: React.CSSProperties = {
border: '1px solid var(--calm-border-input)',
borderRadius: '6px',
padding: '8px 10px',
boxShadow: '0 4px 12px rgba(0,0,0,0.6)',
boxShadow: `0 4px 12px ${theme.shadow}`,
minWidth: '150px',
maxWidth: '300px',
};
Expand Down
12 changes: 7 additions & 5 deletions calm-plugins/vscode/src/webview/canvas/nodes/ActorNode.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React from 'react';
import { Handle, Position, NodeResizer, type NodeProps } from 'reactflow';
import { getNodeStyleOverride } from '../../utils/building-block-style';
import { theme } from '../theme-colors';
import { ValidationBadge } from './ValidationBadge';

export function ActorNode({ id, data, selected }: NodeProps) {
const override = getNodeStyleOverride(data as Record<string, unknown>);
const fill = override.background ?? '#f5f5f5';
const fill = override.background ?? theme.bgWidget;
const stroke = selected ? theme.focus : theme.fg;
const errorCount = (data as any).validationErrors ?? 0;
const warnCount = (data as any).validationWarnings ?? 0;

return (
<>
<NodeResizer isVisible={selected} minWidth={60} minHeight={50} lineStyle={{ borderColor: '#6366f1' }} />
<NodeResizer isVisible={selected} minWidth={60} minHeight={50} lineStyle={{ borderColor: theme.focus }} />
<Handle type="target" position={Position.Top} style={handleStyle} />
<Handle type="source" position={Position.Bottom} style={handleStyle} />
<Handle type="target" position={Position.Left} id="left-target" style={handleStyle} />
Expand All @@ -20,8 +22,8 @@ export function ActorNode({ id, data, selected }: NodeProps) {
<div style={{ ...nodeStyle, ...(selected ? selectedStyle : {}) }}>
<ValidationBadge errorCount={errorCount} warnCount={warnCount} nodeId={(data as any).calmId ?? id} />
<svg width="32" height="40" viewBox="0 0 32 40" fill="none" aria-hidden="true">
<circle cx="16" cy="9" r="7" fill={fill} stroke={selected ? '#6366f1' : '#333333'} strokeWidth={selected ? '2' : '1.5'} />
<path d="M6 36 Q6 22 16 22 Q26 22 26 36" fill={fill} stroke={selected ? '#6366f1' : '#333333'} strokeWidth={selected ? '2' : '1.5'} />
<circle cx="16" cy="9" r="7" fill={fill} stroke={stroke} strokeWidth={selected ? '2' : '1.5'} />
<path d="M6 36 Q6 22 16 22 Q26 22 26 36" fill={fill} stroke={stroke} strokeWidth={selected ? '2' : '1.5'} />
</svg>
<span style={{ ...labelStyle, ...(override.text ? { color: override.text } : {}) }}>{(data as any).label ?? (data as any).calmId}</span>
</div>
Expand Down Expand Up @@ -53,4 +55,4 @@ const labelStyle: React.CSSProperties = {
whiteSpace: 'nowrap',
};

const handleStyle: React.CSSProperties = { width: '8px', height: '8px', background: '#94a3b8', border: '1px solid #64748b' };
const handleStyle: React.CSSProperties = { width: '8px', height: '8px', background: theme.fgMuted, border: `1px solid ${theme.borderWidget}` };
55 changes: 28 additions & 27 deletions calm-plugins/vscode/src/webview/canvas/nodes/ContainerNode.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { Handle, Position, NodeResizer, type NodeProps } from 'reactflow';
import { getNodeStyleOverride } from '../../utils/building-block-style';
import { theme, tint } from '../theme-colors';

export function ContainerNode({ id, data, selected }: NodeProps) {

Check warning on line 6 in calm-plugins/vscode/src/webview/canvas/nodes/ContainerNode.tsx

View workflow job for this annotation

GitHub Actions / Build and Test VS Code Extension

'id' is defined but never used. Allowed unused args must match /^_/u
const role = ((data as any).containerRole as string) || 'default';
const override = getNodeStyleOverride(data as Record<string, unknown>);

Expand All @@ -12,23 +13,23 @@
isVisible={selected}
minWidth={180}
minHeight={120}
lineStyle={{ borderColor: '#6366f1' }}
lineStyle={{ borderColor: theme.focus }}
/>
<Handle type="target" position={Position.Top} style={handleStyle} />
<Handle type="source" position={Position.Bottom} style={handleStyle} />
<Handle type="target" position={Position.Left} id="left-target" style={handleStyle} />
<Handle type="source" position={Position.Right} id="right-source" style={handleStyle} />

<div style={{ ...containerStyle, ...containerRoleStyle(role), ...(selected ? { borderColor: '#6366f1', borderStyle: 'solid' } : {}), ...(override.background ? { background: override.background } : {}) }}>
<div style={{ ...containerStyle, ...containerRoleStyle(role), ...(selected ? { borderColor: theme.focus, borderStyle: 'solid' } : {}), ...(override.background ? { background: override.background } : {}) }}>
<div style={{ ...headerStyle, ...headerRoleStyle(role) }}>
<div style={{ ...dotStyle, ...dotRoleStyle(role) }} />
<span style={{ ...labelBaseStyle, ...(role === 'solution' ? { color: '#2d6a4f', fontWeight: 700 } : {}), ...(override.text ? { color: override.text } : {}) }}>
<span style={{ ...labelBaseStyle, ...(role === 'solution' ? { color: theme.green, fontWeight: 700 } : {}), ...(override.text ? { color: override.text } : {}) }}>
{(data as any).label ?? (data as any).calmId}
</span>
{role === 'region-primary' && <span style={{ ...tagStyle, background: 'rgba(26,77,143,0.15)', color: '#1a4d8f' }}>ACTIVE</span>}
{role === 'region-secondary' && <span style={{ ...tagStyle, background: 'rgba(26,77,143,0.1)', color: '#1a4d8f' }}>STANDBY</span>}
{role === 'az' && <span style={{ ...tagStyle, background: 'rgba(100,116,139,0.12)', color: '#475569' }}>AZ</span>}
{role === 'solution' && <span style={{ ...tagStyle, background: 'rgba(45,106,79,0.15)', color: '#2d6a4f' }}>SOLUTION</span>}
{role === 'region-primary' && <span style={{ ...tagStyle, background: tint(theme.blue, 22), color: theme.blue }}>ACTIVE</span>}
{role === 'region-secondary' && <span style={{ ...tagStyle, background: tint(theme.blue, 16), color: theme.blue }}>STANDBY</span>}
{role === 'az' && <span style={{ ...tagStyle, background: tint(theme.fgMuted, 18), color: theme.fgMuted }}>AZ</span>}
{role === 'solution' && <span style={{ ...tagStyle, background: tint(theme.green, 22), color: theme.green }}>SOLUTION</span>}
</div>
<div style={{ flex: 1 }} />
</div>
Expand All @@ -38,56 +39,56 @@

const containerStyle: React.CSSProperties = {
width: '100%', height: '100%', display: 'flex', flexDirection: 'column',
border: '1.5px dashed #94a3b8', borderRadius: '8px',
border: `1.5px dashed ${theme.fgMuted}`, borderRadius: '8px',
minWidth: '180px', minHeight: '120px', overflow: 'hidden', cursor: 'default', userSelect: 'none',
};

function containerRoleStyle(role: string): React.CSSProperties {
switch (role) {
case 'region-primary': return { borderColor: '#1a4d8f', borderStyle: 'solid', borderWidth: '2px', background: 'rgba(26,77,143,0.03)' };
case 'region-secondary': return { borderColor: '#1a4d8f', borderStyle: 'dashed', borderWidth: '2px', background: 'rgba(26,77,143,0.02)' };
case 'cluster': return { borderColor: '#1a4d8f', borderStyle: 'dashed', borderWidth: '1.5px', background: 'rgba(26,77,143,0.04)' };
case 'az': return { borderColor: '#64748b', borderStyle: 'dotted', borderWidth: '1.5px', background: 'rgba(100,116,139,0.04)' };
case 'solution': return { borderColor: '#2d6a4f', borderStyle: 'solid', borderWidth: '3px', borderRadius: '12px', background: 'rgba(45,106,79,0.06)' };
case 'region-primary': return { borderColor: theme.blue, borderStyle: 'solid', borderWidth: '2px', background: tint(theme.blue, 12) };
case 'region-secondary': return { borderColor: theme.blue, borderStyle: 'dashed', borderWidth: '2px', background: tint(theme.blue, 8) };
case 'cluster': return { borderColor: theme.blue, borderStyle: 'dashed', borderWidth: '1.5px', background: tint(theme.blue, 12) };
case 'az': return { borderColor: theme.fgMuted, borderStyle: 'dotted', borderWidth: '1.5px', background: tint(theme.fgMuted, 10) };
case 'solution': return { borderColor: theme.green, borderStyle: 'solid', borderWidth: '3px', borderRadius: '12px', background: tint(theme.green, 14) };
default: return {};
}
}

const headerStyle: React.CSSProperties = {
display: 'flex', alignItems: 'center', gap: '6px', padding: '5px 10px',
background: '#f1f5f9', borderBottom: '1px solid #e2e8f0',
background: theme.bgWidget, borderBottom: `1px solid ${theme.border}`,
};

function headerRoleStyle(role: string): React.CSSProperties {
switch (role) {
case 'region-primary': return { background: 'rgba(26,77,143,0.08)', borderBottomColor: 'rgba(26,77,143,0.2)' };
case 'region-secondary': return { background: 'rgba(26,77,143,0.06)', borderBottomColor: 'rgba(26,77,143,0.15)' };
case 'cluster': return { background: 'rgba(26,77,143,0.08)', borderBottomColor: 'rgba(26,77,143,0.2)' };
case 'az': return { background: 'rgba(100,116,139,0.06)', borderBottomColor: 'rgba(100,116,139,0.15)' };
case 'solution': return { background: 'rgba(45,106,79,0.1)', borderBottomColor: 'rgba(45,106,79,0.25)' };
case 'region-primary': return { background: tint(theme.blue, 18), borderBottomColor: tint(theme.blue, 35) };
case 'region-secondary': return { background: tint(theme.blue, 14), borderBottomColor: tint(theme.blue, 28) };
case 'cluster': return { background: tint(theme.blue, 18), borderBottomColor: tint(theme.blue, 35) };
case 'az': return { background: tint(theme.fgMuted, 14), borderBottomColor: tint(theme.fgMuted, 28) };
case 'solution': return { background: tint(theme.green, 18), borderBottomColor: tint(theme.green, 35) };
default: return {};
}
}

const dotStyle: React.CSSProperties = { width: '6px', height: '6px', borderRadius: '50%', background: '#64748b', opacity: 0.5, flexShrink: 0 };
const dotStyle: React.CSSProperties = { width: '6px', height: '6px', borderRadius: '50%', background: theme.fgMuted, opacity: 0.5, flexShrink: 0 };

function dotRoleStyle(role: string): React.CSSProperties {
switch (role) {
case 'region-primary': return { background: '#1a4d8f', opacity: 1 };
case 'region-secondary': return { background: '#1a4d8f', opacity: 0.7 };
case 'cluster': return { background: '#1a4d8f', opacity: 1 };
case 'az': return { background: '#64748b', opacity: 0.7 };
case 'solution': return { background: '#2d6a4f', opacity: 1 };
case 'region-primary': return { background: theme.blue, opacity: 1 };
case 'region-secondary': return { background: theme.blue, opacity: 0.7 };
case 'cluster': return { background: theme.blue, opacity: 1 };
case 'az': return { background: theme.fgMuted, opacity: 0.7 };
case 'solution': return { background: theme.green, opacity: 1 };
default: return {};
}
}

const labelBaseStyle: React.CSSProperties = {
fontSize: '10px', fontWeight: 600, color: '#1e293b',
fontSize: '10px', fontWeight: 600, color: theme.fg,
maxWidth: '140px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1,
};

const tagStyle: React.CSSProperties = {
fontSize: '8px', fontWeight: 700, letterSpacing: '0.5px', padding: '1px 5px', borderRadius: '3px', flexShrink: 0,
};
const handleStyle: React.CSSProperties = { width: '6px', height: '6px', background: '#94a3b8', border: '1px solid #64748b' };
const handleStyle: React.CSSProperties = { width: '6px', height: '6px', background: theme.fgMuted, border: `1px solid ${theme.borderWidget}` };
14 changes: 8 additions & 6 deletions calm-plugins/vscode/src/webview/canvas/nodes/DatabaseNode.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React from 'react';
import { Handle, Position, NodeResizer, type NodeProps } from 'reactflow';
import { getNodeStyleOverride } from '../../utils/building-block-style';
import { onSurface, theme } from '../theme-colors';
import { ValidationBadge } from './ValidationBadge';

export function DatabaseNode({ id, data, selected }: NodeProps) {
const override = getNodeStyleOverride(data as Record<string, unknown>);
const fill = override.background ?? '#e6f4ea';
const fill = override.background ?? onSurface(theme.green, 28);
const stroke = selected ? theme.focus : theme.green;
const errorCount = (data as any).validationErrors ?? 0;
const warnCount = (data as any).validationWarnings ?? 0;

return (
<>
<NodeResizer isVisible={selected} minWidth={60} minHeight={50} lineStyle={{ borderColor: '#6366f1' }} />
<NodeResizer isVisible={selected} minWidth={60} minHeight={50} lineStyle={{ borderColor: theme.focus }} />
<Handle type="target" position={Position.Top} style={handleStyle} />
<Handle type="source" position={Position.Bottom} style={handleStyle} />
<Handle type="target" position={Position.Left} id="left-target" style={handleStyle} />
Expand All @@ -20,9 +22,9 @@ export function DatabaseNode({ id, data, selected }: NodeProps) {
<div style={nodeStyle}>
<ValidationBadge errorCount={errorCount} warnCount={warnCount} nodeId={(data as any).calmId ?? id} />
<svg width="48" height="44" viewBox="0 0 48 44" fill="none" aria-hidden="true">
<ellipse cx="24" cy="8" rx="20" ry="6" fill={fill} stroke={selected ? '#6366f1' : '#1b7340'} strokeWidth="1.5" />
<path d="M4 8v28c0 3.3 9 6 20 6s20-2.7 20-6V8" fill={fill} stroke={selected ? '#6366f1' : '#1b7340'} strokeWidth="1.5" />
<ellipse cx="24" cy="36" rx="20" ry="6" fill="none" stroke={selected ? '#6366f1' : '#1b7340'} strokeWidth="1.5" />
<ellipse cx="24" cy="8" rx="20" ry="6" fill={fill} stroke={stroke} strokeWidth="1.5" />
<path d="M4 8v28c0 3.3 9 6 20 6s20-2.7 20-6V8" fill={fill} stroke={stroke} strokeWidth="1.5" />
<ellipse cx="24" cy="36" rx="20" ry="6" fill="none" stroke={stroke} strokeWidth="1.5" />
</svg>
<span style={{ ...labelStyle, ...(override.text ? { color: override.text } : {}) }}>{(data as any).label ?? (data as any).calmId}</span>
</div>
Expand Down Expand Up @@ -52,4 +54,4 @@ const labelStyle: React.CSSProperties = {
whiteSpace: 'nowrap',
};

const handleStyle: React.CSSProperties = { width: '8px', height: '8px', background: '#94a3b8', border: '1px solid #64748b' };
const handleStyle: React.CSSProperties = { width: '8px', height: '8px', background: theme.fgMuted, border: `1px solid ${theme.borderWidget}` };
Loading
Loading