diff --git a/libs/@hashintel/petrinaut/src/components/menu.tsx b/libs/@hashintel/petrinaut/src/components/menu.tsx index 2feda628a60..100b8dc38a8 100644 --- a/libs/@hashintel/petrinaut/src/components/menu.tsx +++ b/libs/@hashintel/petrinaut/src/components/menu.tsx @@ -2,19 +2,6 @@ import { Menu as ArkMenu } from "@ark-ui/react"; import { css } from "@hashintel/ds-helpers/css"; import type { ReactNode } from "react"; -export interface MenuItem { - id: string; - label: string | ReactNode; - onClick?: () => void; - disabled?: boolean; - submenu?: MenuItem[]; -} - -export interface MenuProps { - trigger: ReactNode; - items: MenuItem[]; -} - const menuContentStyle = css({ background: "[white]", borderRadius: "[6px]", @@ -52,6 +39,10 @@ const triggerItemStyle = css({ }, }); +const triggerItemArrowStyle = css({ + marginLeft: "[8px]", +}); + const itemStyle = css({ fontSize: "size.textsm", cursor: "pointer", @@ -67,6 +58,19 @@ const itemStyle = css({ }, }); +export interface MenuItem { + id: string; + label: string | ReactNode; + onClick?: () => void; + disabled?: boolean; + submenu?: MenuItem[]; +} + +export interface MenuProps { + trigger: ReactNode; + items: MenuItem[]; +} + export const Menu: React.FC = ({ trigger, items }) => { return ( @@ -81,7 +85,7 @@ export const Menu: React.FC = ({ trigger, items }) => { > {item.label} - + diff --git a/libs/@hashintel/petrinaut/src/components/segment-group.tsx b/libs/@hashintel/petrinaut/src/components/segment-group.tsx index 7a307b7599a..451da7efc20 100644 --- a/libs/@hashintel/petrinaut/src/components/segment-group.tsx +++ b/libs/@hashintel/petrinaut/src/components/segment-group.tsx @@ -1,5 +1,46 @@ import { SegmentGroup as ArkSegmentGroup } from "@ark-ui/react/segment-group"; -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; + +const containerStyle = css({ + display: "flex", + backgroundColor: "core.gray.20", + borderRadius: "radius.8", + gap: "spacing.1", + position: "relative", + padding: "[4px]", +}); + +const indicatorStyle = css({ + backgroundColor: "core.gray.90", + borderRadius: "radius.6", + position: "absolute", + transition: "[all 0.2s ease]", + width: "var(--width)", + height: "var(--height)", + left: "var(--left)", + top: "var(--top)", +}); + +const itemStyle = cva({ + base: { + flex: "1", + fontSize: "[13px]", + fontWeight: 500, + textAlign: "center", + cursor: "pointer", + borderRadius: "radius.6", + transition: "[all 0.2s ease]", + position: "relative", + zIndex: 1, + padding: "[4px 6px]", + }, + variants: { + isSelected: { + true: { color: "core.gray.10" }, + false: { color: "core.gray.70" }, + }, + }, +}); interface SegmentOption { value: string; @@ -26,45 +67,13 @@ export const SegmentGroup: React.FC = ({ } }} > -
- +
+ {options.map((option) => ( {option.label} diff --git a/libs/@hashintel/petrinaut/src/components/tooltip.tsx b/libs/@hashintel/petrinaut/src/components/tooltip.tsx index a537e5e570c..05e22ac9e0b 100644 --- a/libs/@hashintel/petrinaut/src/components/tooltip.tsx +++ b/libs/@hashintel/petrinaut/src/components/tooltip.tsx @@ -4,6 +4,16 @@ import type { SvgIconProps } from "@mui/material"; import { SvgIcon, Tooltip as MuiTooltip } from "@mui/material"; import type { FunctionComponent, ReactNode } from "react"; +const tooltipContentStyle = css({ + backgroundColor: "core.gray.90", + color: "core.gray.10", + borderRadius: "radius.6", + fontSize: "[13px]", + zIndex: "[10000]", + boxShadow: "[0 2px 8px rgba(0, 0, 0, 0.15)]", + padding: "[6px 10px]", +}); + interface TooltipProps { content: string; children: ReactNode; @@ -18,19 +28,7 @@ export const Tooltip: React.FC = ({ content, children }) => { > {children} - + {content} diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomPanel/bottom-panel.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomPanel/bottom-panel.tsx index 47f665c1f86..fcc99ce0708 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomPanel/bottom-panel.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomPanel/bottom-panel.tsx @@ -13,6 +13,12 @@ import { DiagnosticsContent } from "./diagnostics-content"; import { ParametersContent } from "./parameters-content"; import { SimulationSettingsContent } from "./simulation-settings-content"; +const glassPanelBaseStyle = css({ + position: "fixed", + zIndex: 999, + padding: "[4px]", +}); + const panelContainerStyle = css({ display: "flex", flexDirection: "column", @@ -136,14 +142,12 @@ export const BottomPanel: React.FC = () => { return ( { - if (typeof messageText === "string") { - return messageText; - } - return ts.flattenDiagnosticMessageText(messageText, "\n"); -}; - const emptyMessageStyle = css({ color: "core.gray.50", fontStyle: "italic", }); +const entityGroupStyle = css({ + marginBottom: "[8px]", +}); + const entityButtonStyle = css({ + display: "flex", + alignItems: "center", + gap: "[6px]", + width: "[100%]", + padding: "[4px 0]", + border: "none", + cursor: "pointer", + textAlign: "left", fontSize: "[12px]", fontWeight: "medium", color: "core.gray.80", - "&:hover": { + _hover: { color: "core.gray.90", }, }); @@ -38,18 +38,35 @@ const errorCountStyle = css({ fontWeight: "normal", }); +const expandedContentStyle = css({ + paddingLeft: "[16px]", + marginTop: "[4px]", +}); + +const itemGroupStyle = css({ + marginBottom: "[8px]", +}); + const subTypeStyle = css({ fontSize: "[11px]", fontWeight: "medium", color: "core.gray.60", + marginBottom: "[2px]", }); const diagnosticsListStyle = css({ margin: "[0]", + paddingLeft: "[12px]", listStyle: "none", }); +const diagnosticItemStyle = css({ + marginBottom: "[4px]", +}); + const diagnosticButtonStyle = css({ + marginLeft: "[8px]", + padding: "[2px 4px]", fontSize: "[11px]", fontFamily: "[ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace]", @@ -62,15 +79,36 @@ const diagnosticButtonStyle = css({ border: "none", textAlign: "left", width: "[100%]", - "&:hover": { + _hover: { backgroundColor: "[rgba(220, 38, 38, 0.08)]", }, }); +const bulletStyle = css({ + marginRight: "[4px]", +}); + const positionStyle = css({ color: "core.gray.50", + marginLeft: "[8px]", }); +// --- Helpers --- + +/** + * Formats a TypeScript diagnostic message to a readable string + */ +const formatDiagnosticMessage = ( + messageText: string | ts.DiagnosticMessageChain, +): string => { + if (typeof messageText === "string") { + return messageText; + } + return ts.flattenDiagnosticMessageText(messageText, "\n"); +}; + +// --- Types --- + type EntityType = "transition" | "differential-equation"; interface GroupedDiagnostics { @@ -183,22 +221,11 @@ export const DiagnosticsContent: React.FC = () => { : `Differential Equation: ${group.entityName}`; return ( -
+
{/* Collapsible entity header */}
-
+
{parameters.map((param) => { const isSelected = selectedResourceId === param.id; @@ -196,36 +231,15 @@ export const ParametersContent: React.FC = () => { setSelectedResourceId(param.id); } }} - style={{ - width: "100%", - display: "flex", - alignItems: "center", - justifyContent: "space-between", - padding: "4px 2px 4px 8px", - fontSize: 13, - borderRadius: 4, - backgroundColor: isSelected - ? "rgba(59, 130, 246, 0.15)" - : "#f9fafb", - cursor: "pointer", - }} - className={ - isSelected ? parameterRowSelectedStyle : parameterRowStyle - } + className={parameterRowStyle({ isSelected })} >
{param.name}
-
+                
                   {param.variableName}
                 
-
+
{isSimulationMode ? ( { const [isExpanded, setIsExpanded] = useState(true); @@ -31,42 +163,12 @@ export const DifferentialEquationsSection: React.FC = () => { simulationState === "Running" || simulationState === "Paused"; return ( -
-
+
+
{isExpanded && ( -
+
{differentialEquations.map((eq) => { const isSelected = selectedResourceId === eq.id; @@ -145,27 +227,9 @@ export const DifferentialEquationsSection: React.FC = () => { setSelectedResourceId(eq.id); } }} - style={{ - display: "flex", - alignItems: "center", - justifyContent: "space-between", - padding: "4px 2px 4px 8px", - fontSize: 13, - borderRadius: 4, - backgroundColor: isSelected - ? "rgba(59, 130, 246, 0.15)" - : "#f9fafb", - cursor: "pointer", - }} - className={css({ - _hover: { - backgroundColor: isSelected - ? "[rgba(59, 130, 246, 0.2)]" - : "[rgba(0, 0, 0, 0.05)]", - }, - })} + className={equationRowStyle({ isSelected })} > -
+
{eq.name}
)} diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/LeftSideBar/types-section.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/LeftSideBar/types-section.tsx index 1c7da6a059a..2cef5ab758f 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/LeftSideBar/types-section.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/LeftSideBar/types-section.tsx @@ -1,4 +1,4 @@ -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; import { useState } from "react"; import { FaChevronDown, FaChevronRight } from "react-icons/fa6"; @@ -7,6 +7,149 @@ import { useEditorStore } from "../../../../state/editor-provider"; import { useSDCPNContext } from "../../../../state/sdcpn-provider"; import { useSimulationStore } from "../../../../state/simulation-provider"; +const sectionContainerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[8px]", + paddingBottom: "[16px]", + borderBottom: "[1px solid rgba(0, 0, 0, 0.1)]", +}); + +const headerRowStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", +}); + +const sectionToggleButtonStyle = css({ + display: "flex", + alignItems: "center", + gap: "[6px]", + fontWeight: 600, + fontSize: "[13px]", + color: "[#333]", + cursor: "pointer", + background: "[transparent]", + border: "none", + padding: "spacing.1", + borderRadius: "radius.4", + _hover: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + }, +}); + +const addButtonStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "spacing.1", + borderRadius: "radius.2", + cursor: "pointer", + fontSize: "[18px]", + color: "core.gray.60", + background: "[transparent]", + border: "none", + width: "[24px]", + height: "[24px]", + _hover: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + color: "core.gray.90", + }, + _disabled: { + cursor: "not-allowed", + opacity: "[0.4]", + _hover: { + backgroundColor: "[transparent]", + color: "core.gray.60", + }, + }, +}); + +const listContainerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[2px]", + maxHeight: "[200px]", + overflowY: "auto", +}); + +const typeRowStyle = cva({ + base: { + display: "flex", + alignItems: "center", + gap: "[8px]", + padding: "[4px 2px 4px 8px]", + borderRadius: "[4px]", + cursor: "pointer", + }, + variants: { + isSelected: { + true: { + backgroundColor: "[rgba(59, 130, 246, 0.15)]", + _hover: { + backgroundColor: "[rgba(59, 130, 246, 0.2)]", + }, + }, + false: { + backgroundColor: "[transparent]", + _hover: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + }, + }, + }, + }, +}); + +const colorDotStyle = css({ + width: "[12px]", + height: "[12px]", + borderRadius: "[50%]", + flexShrink: 0, +}); + +const typeNameStyle = css({ + flex: "[1]", + fontSize: "[13px]", + color: "[#374151]", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", +}); + +const deleteButtonStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "spacing.1", + borderRadius: "radius.2", + cursor: "pointer", + fontSize: "[14px]", + color: "core.gray.40", + background: "[transparent]", + border: "none", + width: "[20px]", + height: "[20px]", + _hover: { + backgroundColor: "[rgba(239, 68, 68, 0.1)]", + color: "core.red.60", + }, + _disabled: { + cursor: "not-allowed", + opacity: "[0.3]", + _hover: { + backgroundColor: "[transparent]", + color: "core.gray.40", + }, + }, +}); + +const emptyMessageStyle = css({ + fontSize: "[13px]", + color: "[#9ca3af]", + padding: "spacing.4", + textAlign: "center", +}); + // Pool of 10 well-differentiated colors for types const TYPE_COLOR_POOL = [ "#3b82f6", // Blue @@ -73,41 +216,12 @@ export const TypesSection: React.FC = () => { simulationState === "Running" || simulationState === "Paused"; return ( -
-
+
+
{isExpanded && ( -
+
{types.map((type) => { const isSelected = selectedResourceId === type.id; @@ -205,46 +289,13 @@ export const TypesSection: React.FC = () => { setSelectedResourceId(type.id); } }} - style={{ - display: "flex", - alignItems: "center", - gap: 8, - padding: "4px 2px 4px 8px", - borderRadius: 4, - backgroundColor: isSelected - ? "rgba(59, 130, 246, 0.15)" - : "transparent", - cursor: "pointer", - }} - className={css({ - _hover: { - backgroundColor: isSelected - ? "[rgba(59, 130, 246, 0.2)]" - : "[rgba(0, 0, 0, 0.05)]", - }, - })} + className={typeRowStyle({ isSelected })} >
- - {type.name} - + {type.name}
)} diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/color-select.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/color-select.tsx index 3365d1f36ec..45fe2f6e244 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/color-select.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/color-select.tsx @@ -1,7 +1,92 @@ import { Portal } from "@ark-ui/react/portal"; import { createListCollection, Select } from "@ark-ui/react/select"; +import { css, cva } from "@hashintel/ds-helpers/css"; import { TbChevronDown } from "react-icons/tb"; +const triggerStyle = cva({ + base: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + gap: "[8px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + fontSize: "[14px]", + width: "[100%]", + }, + variants: { + isDisabled: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + opacity: "[0.5]", + }, + false: { + backgroundColor: "[white]", + cursor: "pointer", + opacity: "[1]", + }, + }, + }, +}); + +const triggerValueContainerStyle = css({ + display: "flex", + alignItems: "center", + gap: "[8px]", +}); + +const colorSwatchStyle = css({ + width: "[20px]", + height: "[20px]", + borderRadius: "[3px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + flexShrink: 0, +}); + +const colorCodeStyle = css({ + fontSize: "[12px]", + fontFamily: "[monospace]", +}); + +const indicatorIconStyle = css({ + fontSize: "[16px]", + color: "[#666]", +}); + +const contentStyle = css({ + backgroundColor: "[white]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + boxShadow: "[0 4px 12px rgba(0, 0, 0, 0.15)]", + padding: "[4px]", + zIndex: 1000, +}); + +const itemStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + gap: "[8px]", + padding: "[8px 10px]", + cursor: "pointer", + borderRadius: "[3px]", + fontSize: "[13px]", + transition: "[background-color 0.15s ease]", +}); + +const itemValueContainerStyle = css({ + display: "flex", + alignItems: "center", + gap: "[8px]", +}); + +const checkmarkStyle = css({ + fontSize: "[14px]", + color: "[#3b82f6]", +}); + // Pool of 10 well-differentiated colors for types const TYPE_COLOR_POOL = [ { value: "#3b82f6", label: "Blue" }, @@ -43,90 +128,36 @@ export const ColorSelect: React.FC = ({ positioning={{ sameWidth: true }} > - -
+ +
-
{value}
+
{value}
- + - + {collection.items.map((item) => ( - -
+ +
-
- {item.value} -
+
{item.value}
- + ))} diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/differential-equation-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/differential-equation-properties.tsx index 0f3f02fa1db..91b2700b072 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/differential-equation-properties.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/differential-equation-properties.tsx @@ -1,5 +1,6 @@ /* eslint-disable id-length */ +import { css, cva } from "@hashintel/ds-helpers/css"; import MonacoEditor from "@monaco-editor/react"; import { useState } from "react"; import { TbDotsVertical, TbSparkles } from "react-icons/tb"; @@ -18,6 +19,241 @@ import type { } from "../../../../core/types/sdcpn"; import { useSimulationStore } from "../../../../state/simulation-provider"; +const containerStyle = css({ + display: "flex", + flexDirection: "column", + height: "[100%]", + gap: "[12px]", +}); + +const headerTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", + marginBottom: "[8px]", +}); + +const fieldLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", +}); + +const inputStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + width: "[100%]", + boxSizing: "border-box", + }, + variants: { + isReadOnly: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + }, +}); + +const typeDropdownButtonStyle = cva({ + base: { + width: "[100%]", + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + display: "flex", + alignItems: "center", + gap: "[8px]", + textAlign: "left", + }, + variants: { + isReadOnly: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "pointer", + }, + }, + }, +}); + +const colorDotStyle = css({ + width: "[12px]", + height: "[12px]", + borderRadius: "[50%]", + flexShrink: 0, +}); + +const dropdownMenuStyle = css({ + position: "absolute", + top: "[100%]", + left: "[0]", + right: "[0]", + marginTop: "[4px]", + backgroundColor: "[white]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + boxShadow: "[0 4px 16px rgba(0, 0, 0, 0.15)]", + maxHeight: "[300px]", + overflowY: "auto", + zIndex: 1000, +}); + +const dropdownItemStyle = css({ + width: "[100%]", + padding: "[8px 12px]", + border: "none", + cursor: "pointer", + display: "flex", + alignItems: "center", + gap: "[8px]", + fontSize: "[14px]", + textAlign: "left", +}); + +const confirmDialogOverlayStyle = css({ + position: "fixed", + top: "[0]", + left: "[0]", + right: "[0]", + bottom: "[0]", + backgroundColor: "[rgba(0, 0, 0, 0.5)]", + display: "flex", + alignItems: "center", + justifyContent: "center", + zIndex: 10000, +}); + +const confirmDialogStyle = css({ + backgroundColor: "[white]", + borderRadius: "[8px]", + padding: "[24px]", + maxWidth: "[400px]", + boxShadow: "[0 4px 16px rgba(0, 0, 0, 0.2)]", +}); + +const confirmDialogTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", + marginBottom: "[12px]", +}); + +const confirmDialogTextStyle = css({ + fontSize: "[14px]", + color: "[#666]", + marginBottom: "[16px]", +}); + +const confirmDialogListStyle = css({ + fontSize: "[13px]", + color: "[#666]", + marginBottom: "[16px]", + paddingLeft: "[20px]", +}); + +const confirmDialogHintStyle = css({ + fontSize: "[13px]", + color: "[#999]", + marginBottom: "[20px]", +}); + +const confirmDialogButtonsStyle = css({ + display: "flex", + gap: "[8px]", + justifyContent: "flex-end", +}); + +const cancelButtonStyle = css({ + padding: "[8px 16px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + backgroundColor: "[white]", + cursor: "pointer", + fontSize: "[14px]", +}); + +const confirmButtonStyle = css({ + padding: "[8px 16px]", + border: "none", + borderRadius: "[4px]", + backgroundColor: "[#2563eb]", + color: "[white]", + cursor: "pointer", + fontSize: "[14px]", +}); + +const codeContainerStyle = css({ + display: "flex", + flexDirection: "column", + flex: "[1]", + minHeight: "[0]", +}); + +const codeHeaderStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "[8px]", +}); + +const codeHeaderLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", +}); + +const menuButtonStyle = css({ + background: "[transparent]", + border: "none", + cursor: "pointer", + padding: "[4px]", + display: "flex", + alignItems: "center", + fontSize: "[18px]", + color: "[rgba(0, 0, 0, 0.6)]", +}); + +const editorContainerStyle = cva({ + base: { + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + overflow: "hidden", + flex: "[1]", + minHeight: "[0]", + }, + variants: { + isReadOnly: { + true: { + filter: "[grayscale(20%) brightness(98%)]", + pointerEvents: "none", + }, + false: { + filter: "[none]", + pointerEvents: "auto", + }, + }, + }, +}); + +const aiMenuItemStyle = css({ + display: "flex", + alignItems: "center", + gap: "[6px]", +}); + +const aiIconStyle = css({ + fontSize: "[16px]", +}); + interface DifferentialEquationPropertiesProps { differentialEquation: DifferentialEquation; types: Color[]; @@ -98,24 +334,13 @@ export const DifferentialEquationProperties: React.FC< }; return ( -
+
-
- Differential Equation -
+
Differential Equation
-
- Name -
+
Name
-
- Associated Type -
+
Associated Type
{showTypeDropdown && !isReadOnly && ( -
+
{types.map((type) => ( @@ -254,36 +422,16 @@ export const DifferentialEquationProperties: React.FC< {/* Confirmation Dialog */} {showConfirmDialog && ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -
+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
ev.stopPropagation()} > -
+
Change Associated Type?
-
+
{placesUsingEquation.length === 1 ? ( <> 1 place is currently using this differential @@ -296,51 +444,27 @@ export const DifferentialEquationProperties: React.FC< )}
-
    +
      {placesUsingEquation.map((place) => (
    • {place.name}
    • ))}
    -
    +
    Changing the type may affect how these places behave. Are you sure you want to continue?
    -
    +
    @@ -349,39 +473,13 @@ export const DifferentialEquationProperties: React.FC<
    )} -
    -
    -
    Code
    +
    +
    +
    Code
    {!isReadOnly && ( + } @@ -411,14 +509,8 @@ export const DifferentialEquationProperties: React.FC< id: "generate-ai", label: ( -
    - +
    + Generate with AI
    @@ -432,17 +524,7 @@ export const DifferentialEquationProperties: React.FC< /> )}
    -
    +
    = ({ return (
    -
    -
    +
    +
    {isSimulationNotRun ? "Initial State" : "State"} {isSimulationNotRun && ( @@ -578,18 +754,7 @@ export const InitialStateEditor: React.FC = ({
    - +
    - @@ -714,27 +825,11 @@ export const InitialStateEditor: React.FC = ({
    + {placeType.elements.map((element) => ( {element.name} @@ -671,33 +797,18 @@ export const InitialStateEditor: React.FC = ({
    handleRowClick(rowIndex)} onKeyDown={(event) => handleRowKeyDown(event, rowIndex)} tabIndex={0} - style={{ - borderRight: "1px solid rgba(0, 0, 0, 0.1)", - borderBottom: "1px solid rgba(0, 0, 0, 0.05)", - padding: "4px 8px", - textAlign: "center", - cursor: hasSimulation ? "default" : "pointer", - backgroundColor: - selectedRow === rowIndex - ? "rgba(59, 130, 246, 0.2)" - : "#fafafa", - fontWeight: 500, - color: rowIndex === tableData.length ? "#ccc" : "#666", - outline: "none", - }} + className={rowNumberCellStyle({ + isSelected: selectedRow === rowIndex, + isPhantom: rowIndex === tableData.length, + hasSimulation, + })} > {rowIndex === tableData.length ? "" : rowIndex + 1} {hasSimulation ? ( -
    +
    {isPhantomRow ? "" : value}
    ) : isEditing ? ( @@ -755,18 +850,7 @@ export const InitialStateEditor: React.FC = ({ setEditingCell(null); setEditingValue(""); }} - style={{ - width: "100%", - height: "28px", - border: "none", - padding: "4px 8px", - fontFamily: "monospace", - fontSize: 12, - backgroundColor: "rgba(59, 130, 246, 0.05)", - outline: "2px solid #3b82f6", - outlineOffset: -2, - boxSizing: "border-box", - }} + className={editingInputStyle} /> ) : (
    = ({ onKeyDown={(event) => handleKeyDown(event, rowIndex, colIndex) } - style={{ - width: "100%", - height: "28px", - padding: "4px 8px", - fontFamily: "monospace", - fontSize: 12, - backgroundColor: "transparent", - outline: isFocused ? "2px solid #3b82f6" : "none", - outlineOffset: -2, - cursor: "default", - boxSizing: "border-box", - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - display: "flex", - alignItems: "center", - }} + className={cellButtonStyle({ isFocused })} > {isPhantomRow ? "" : value}
    @@ -824,18 +892,7 @@ export const InitialStateEditor: React.FC = ({ type="button" aria-label="Resize table" onMouseDown={startResize} - style={{ - position: "absolute", - bottom: 0, - left: 0, - right: 0, - height: 8, - cursor: "ns-resize", - backgroundColor: isResizing ? "rgba(0, 0, 0, 0.1)" : "transparent", - border: "none", - padding: 0, - zIndex: 10, - }} + className={resizeHandleStyle({ isResizing })} />
    diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/multiple-selection.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/multiple-selection.tsx index 745cff3452e..c3b23ba4409 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/multiple-selection.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/multiple-selection.tsx @@ -1,3 +1,10 @@ +import { css } from "@hashintel/ds-helpers/css"; + +const titleStyle = css({ + fontWeight: 600, + fontSize: "[14px]", +}); + /** * MultipleSelection - Displays info when multiple items are selected */ @@ -10,9 +17,7 @@ export const MultipleSelection: React.FC = ({ }) => { return (
    -
    - Multiple Items Selected ({count}) -
    +
    Multiple Items Selected ({count})
    ); }; diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/parameter-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/parameter-properties.tsx index eed4631959e..49184886124 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/parameter-properties.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/parameter-properties.tsx @@ -1,6 +1,58 @@ +import { css, cva } from "@hashintel/ds-helpers/css"; + import type { Parameter } from "../../../../core/types/sdcpn"; import { useSimulationStore } from "../../../../state/simulation-provider"; +const containerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[12px]", +}); + +const headerTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", + marginBottom: "[8px]", +}); + +const fieldLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", +}); + +const inputStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.15)]", + borderRadius: "[4px]", + width: "[100%]", + }, + variants: { + isDisabled: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.02)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + isMonospace: { + true: { + fontFamily: "[monospace]", + }, + false: {}, + }, + }, + defaultVariants: { + isDisabled: false, + isMonospace: false, + }, +}); + /** * Slugifies a string to a valid JavaScript identifier. * - Converts to lowercase @@ -8,7 +60,7 @@ import { useSimulationStore } from "../../../../state/simulation-provider"; * - Removes leading/trailing underscores * - Ensures it doesn't start with a number */ -function slugifyToIdentifier(str: string): string { +const slugifyToIdentifier = (str: string): string => { return ( str .toLowerCase() @@ -21,7 +73,7 @@ function slugifyToIdentifier(str: string): string { // Ensure it doesn't start with a number .replace(/^(\d)/, "_$1") ); -} +}; interface ParameterPropertiesProps { parameter: Parameter; @@ -88,56 +140,33 @@ export const ParameterProperties: React.FC = ({ }; return ( -
    +
    -
    - Parameter -
    +
    Parameter
    {/* Name field */}
    -
    - Name -
    +
    Name
    {/* Variable Name field */}
    -
    - Variable Name -
    +
    Variable Name
    @@ -145,24 +174,13 @@ export const ParameterProperties: React.FC = ({ {/* Default Value field */}
    -
    - Default Value -
    +
    Default Value
    diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/place-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/place-properties.tsx index e80465afd75..1ec92d324fd 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/place-properties.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/place-properties.tsx @@ -1,5 +1,5 @@ /* eslint-disable id-length */ -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; import MonacoEditor from "@monaco-editor/react"; import { useEffect, useMemo, useRef, useState } from "react"; import { @@ -33,6 +33,226 @@ import { useSimulationStore } from "../../../../state/simulation-provider"; import { InitialStateEditor } from "./initial-state-editor"; import { VisualizerErrorBoundary } from "./visualizer-error-boundary"; +const containerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[12px]", +}); + +const headerContainerStyle = css({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "[8px]", +}); + +const headerTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", +}); + +const deleteButtonStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + width: "[24px]", + height: "[24px]", + padding: "spacing.0", + border: "none", + background: "[transparent]", + cursor: "pointer", + color: "core.gray.60", + borderRadius: "radius.4", + _hover: { + color: "core.red.60", + backgroundColor: "core.red.10", + }, +}); + +const fieldLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", +}); + +const fieldLabelWithTooltipStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", + display: "flex", + alignItems: "center", +}); + +const inputStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + borderRadius: "[4px]", + width: "[100%]", + boxSizing: "border-box", + }, + variants: { + isReadOnly: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + hasError: { + true: { + border: "[1px solid #ef4444]", + }, + false: { + border: "[1px solid rgba(0, 0, 0, 0.1)]", + }, + }, + }, + defaultVariants: { + isReadOnly: false, + hasError: false, + }, +}); + +const errorMessageStyle = css({ + fontSize: "[12px]", + color: "[#ef4444]", + marginTop: "[4px]", +}); + +const selectStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + width: "[100%]", + boxSizing: "border-box", + }, + variants: { + isReadOnly: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "pointer", + }, + }, + hasMarginBottom: { + true: { + marginBottom: "[8px]", + }, + false: {}, + }, + }, +}); + +const jumpButtonContainerStyle = css({ + textAlign: "right", +}); + +const jumpButtonStyle = css({ + fontSize: "[12px]", + padding: "[4px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.2)]", + borderRadius: "[4px]", + backgroundColor: "[white]", + cursor: "pointer", + color: "[#333]", + display: "inline-flex", + alignItems: "center", + gap: "[6px]", +}); + +const jumpIconStyle = css({ + fontSize: "[14px]", +}); + +const sectionContainerStyle = css({ + marginTop: "[10px]", +}); + +const switchRowStyle = css({ + display: "flex", + alignItems: "center", + gap: "[8px]", + marginBottom: "[8px]", +}); + +const switchContainerStyle = css({ + display: "flex", + alignItems: "center", +}); + +const hintTextStyle = css({ + fontSize: "[11px]", + color: "[#999]", + fontStyle: "italic", + marginTop: "[4px]", +}); + +const diffEqContainerStyle = css({ + marginBottom: "[25px]", +}); + +const menuButtonStyle = css({ + background: "[transparent]", + border: "none", + cursor: "pointer", + padding: "[4px]", + display: "flex", + alignItems: "center", + fontSize: "[18px]", + color: "[rgba(0, 0, 0, 0.6)]", +}); + +const codeHeaderStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "[4px]", +}); + +const codeHeaderLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", +}); + +const editorBorderStyle = css({ + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + overflow: "hidden", +}); + +const aiMenuItemStyle = css({ + display: "flex", + alignItems: "center", + gap: "[6px]", +}); + +const aiIconStyle = css({ + fontSize: "[16px]", +}); + +const visualizerMessageStyle = css({ + padding: "[12px]", + color: "[#666]", +}); + +const visualizerErrorStyle = css({ + padding: "[12px]", + color: "[#d32f2f]", +}); + +const spacerStyle = css({ + height: "[40px]", +}); + interface PlacePropertiesProps { place: Place; types: Color[]; @@ -173,20 +393,10 @@ export const PlaceProperties: React.FC = ({ const { removePlace } = useSDCPNContext(); return ( -
    +
    -
    -
    Place
    +
    +
    Place
    @@ -225,9 +419,7 @@ export const PlaceProperties: React.FC = ({
    -
    - Name -
    +
    Name
    = ({ handleNameBlur(); }} disabled={isReadOnly} - style={{ - fontSize: 14, - padding: "6px 8px", - border: `1px solid ${nameError ? "#ef4444" : "rgba(0, 0, 0, 0.1)"}`, - borderRadius: 4, - width: "100%", - boxSizing: "border-box", - backgroundColor: isReadOnly ? "rgba(0, 0, 0, 0.05)" : "white", - cursor: isReadOnly ? "not-allowed" : "text", - }} + className={inputStyle({ isReadOnly, hasError: !!nameError })} /> - {nameError && ( -
    - {nameError} -
    - )} + {nameError &&
    {nameError}
    }
    -
    +
    Accepted token type = ({ }); }} disabled={isReadOnly} - style={{ - fontSize: 14, - padding: "6px 8px", - border: "1px solid rgba(0, 0, 0, 0.1)", - borderRadius: 4, - width: "100%", - boxSizing: "border-box", - backgroundColor: isReadOnly ? "rgba(0, 0, 0, 0.05)" : "white", - cursor: isReadOnly ? "not-allowed" : "pointer", - marginBottom: place.colorId ? 8 : 0, - }} + className={selectStyle({ + isReadOnly, + hasMarginBottom: !!place.colorId, + })} > {types.map((type) => ( @@ -315,42 +481,24 @@ export const PlaceProperties: React.FC = ({ {place.colorId && ( -
    +
    )}
    -
    -
    -
    +
    +
    +
    = ({ }} />
    -
    +
    Dynamics
    {(place.colorId === null || availableDiffEqs.length === 0) && ( -
    +
    {place.colorId !== null ? "Create a differential equation for the selected type in the left-hand sidebar first" : availableTypes.length === 0 @@ -394,10 +528,8 @@ export const PlaceProperties: React.FC = ({ {place.colorId && place.dynamicsEnabled && availableDiffEqs.length > 0 && ( -
    -
    - Differential Equation -
    +
    +
    Differential Equation
    {place.differentialEquationId && ( -
    +
    )} @@ -483,19 +594,11 @@ export const PlaceProperties: React.FC = ({ return (
    -
    +
    {hasSimulationFrames ? "State" : "Initial State"}
    -
    - Token count -
    +
    Token count
    = ({ }); }} disabled={hasSimulationFrames} - style={{ - fontSize: 14, - padding: "6px 8px", - border: "1px solid rgba(0, 0, 0, 0.1)", - borderRadius: 4, - width: "100%", - boxSizing: "border-box", - backgroundColor: hasSimulationFrames - ? "rgba(0, 0, 0, 0.05)" - : "white", - cursor: hasSimulationFrames ? "not-allowed" : "text", - }} + className={inputStyle({ isReadOnly: hasSimulationFrames })} />
    @@ -541,16 +633,9 @@ export const PlaceProperties: React.FC = ({ {/* Visualizer section */} {globalMode === "edit" && ( -
    -
    -
    +
    +
    +
    { @@ -572,14 +657,7 @@ export const PlaceProperties: React.FC = ({ }} />
    -
    +
    Visualizer
    @@ -597,15 +675,8 @@ export const PlaceProperties: React.FC = ({ return ( <> -
    -
    +
    +
    {showVisualization ? "Visualizer Output" : "Visualizer Code"} @@ -613,19 +684,7 @@ export const PlaceProperties: React.FC = ({ {!showVisualization && ( + } @@ -652,14 +711,8 @@ export const PlaceProperties: React.FC = ({ -
    - +
    + Generate with AI
    @@ -673,13 +726,7 @@ export const PlaceProperties: React.FC = ({ /> )}
    -
    +
    {showVisualization ? ( // Show live token values and parameters during simulation (() => { @@ -690,7 +737,7 @@ export const PlaceProperties: React.FC = ({ if (!placeType) { return ( -
    +
    Place has no type set
    ); @@ -707,7 +754,7 @@ export const PlaceProperties: React.FC = ({ simulation.frames[currentlyViewedFrame]; if (!currentFrame) { return ( -
    +
    No frame data available
    ); @@ -716,7 +763,7 @@ export const PlaceProperties: React.FC = ({ const placeState = currentFrame.places.get(place.id); if (!placeState) { return ( -
    +
    Place not found in frame
    ); @@ -779,7 +826,7 @@ export const PlaceProperties: React.FC = ({ // Render the compiled visualizer component if (!VisualizerComponent) { return ( -
    +
    Failed to compile visualizer code. Check console for errors.
    @@ -830,7 +877,7 @@ export const PlaceProperties: React.FC = ({
    )} -
    +
    ); }; diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/properties-panel.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/properties-panel.tsx index 517042e3f69..e4b0eb6fbc1 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/properties-panel.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/properties-panel.tsx @@ -16,6 +16,31 @@ import { PlaceProperties } from "./place-properties"; import { TransitionProperties } from "./transition-properties"; import { TypeProperties } from "./type-properties"; +const unknownItemStyle = css({ + padding: "[16px]", + textAlign: "center", + color: "[#999]", + fontSize: "[14px]", +}); + +const positionContainerStyle = css({ + display: "flex", + position: "fixed", + top: "[0]", + right: "[0]", + zIndex: 1000, + pointerEvents: "none", +}); + +const glassPanelHeightStyle = css({ + height: "[100%]", +}); + +const glassPanelContentStyle = css({ + padding: "[16px]", + overflowY: "auto", +}); + /** * PropertiesPanel displays properties and controls for the selected node/edge. */ @@ -167,18 +192,7 @@ export const PropertiesPanel: React.FC = () => { default: // Unknown item type - content = ( -
    - Unknown item selected -
    - ); + content =
    Unknown item selected
    ; } // Calculate bottom offset based on bottom panel visibility @@ -187,29 +201,19 @@ export const PropertiesPanel: React.FC = () => { return (
    = ({ isDragging, } = useSortable({ id, disabled }); - const style = { + const transformStyle = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; return ( -
    +
    {FEATURE_FLAGS.REORDER_TRANSITION_ARCS && (
    )} -
    - {placeName} -
    -
    - - weight - +
    {placeName}
    +
    + weight = ({ onWeightChange(newWeight); } }} - style={{ - width: 60, - fontSize: 14, - padding: "4px 8px", - border: "1px solid rgba(0, 0, 0, 0.1)", - borderRadius: 4, - boxSizing: "border-box", - backgroundColor: disabled ? "rgba(0, 0, 0, 0.05)" : "white", - cursor: disabled ? "not-allowed" : "text", - }} + className={weightInputStyle({ isDisabled: disabled })} />
    {onDelete && !disabled && ( - )} diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/transition-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/transition-properties.tsx index a3cd84e63b0..c76b2536e24 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/transition-properties.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/transition-properties.tsx @@ -15,7 +15,7 @@ import { sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable"; -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; import MonacoEditor from "@monaco-editor/react"; import { TbDotsVertical, TbSparkles, TbTrash } from "react-icons/tb"; @@ -32,6 +32,203 @@ import { useSDCPNContext } from "../../../../state/sdcpn-provider"; import { useSimulationStore } from "../../../../state/simulation-provider"; import { SortableArcItem } from "./sortable-arc-item"; +const containerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[12px]", +}); + +const headerContainerStyle = css({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "[8px]", +}); + +const headerTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", +}); + +const deleteButtonStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + width: "[24px]", + height: "[24px]", + padding: "spacing.0", + border: "none", + background: "[transparent]", + cursor: "pointer", + color: "core.gray.60", + borderRadius: "radius.4", + _hover: { + color: "core.red.60", + backgroundColor: "core.red.10", + }, +}); + +const fieldLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", +}); + +const inputStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + width: "[100%]", + boxSizing: "border-box", + }, + variants: { + isReadOnly: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + }, +}); + +const sectionContainerStyle = css({ + marginTop: "[20px]", +}); + +const emptyArcMessageStyle = css({ + fontSize: "[12px]", + color: "[#999]", +}); + +const arcListContainerStyle = css({ + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[6px]", + overflow: "hidden", +}); + +const segmentGroupWrapperStyle = cva({ + base: {}, + variants: { + isReadOnly: { + true: { + opacity: "[0.6]", + pointerEvents: "none", + }, + false: { + opacity: "[1]", + pointerEvents: "auto", + }, + }, + }, +}); + +const infoBoxStyle = css({ + fontSize: "[12px]", + color: "[#666]", + backgroundColor: "[rgba(0, 0, 0, 0.03)]", + padding: "[8px]", + borderRadius: "[4px]", + lineHeight: "[1.5]", +}); + +const codeHeaderStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "[4px]", + height: "[30px]", +}); + +const codeHeaderLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", +}); + +const menuButtonStyle = css({ + background: "[transparent]", + border: "none", + cursor: "pointer", + padding: "[4px]", + display: "flex", + alignItems: "center", + fontSize: "[18px]", + color: "[rgba(0, 0, 0, 0.6)]", +}); + +const editorContainerStyle = cva({ + base: { + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + overflow: "hidden", + }, + variants: { + size: { + lambda: { height: "[340px]" }, + kernel: { height: "[400px]" }, + }, + isReadOnly: { + true: { + filter: "[grayscale(20%) brightness(98%)]", + pointerEvents: "none", + }, + false: { + filter: "none", + pointerEvents: "auto", + }, + }, + }, +}); + +const aiMenuItemStyle = css({ + display: "flex", + alignItems: "center", + gap: "[6px]", +}); + +const aiIconStyle = css({ + fontSize: "[16px]", +}); + +const sectionTitleStyle = css({ + fontWeight: 500, + fontSize: "[13px]", +}); + +const resultsHeaderStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "[4px]", + marginTop: "[20px]", + height: "[30px]", +}); + +const noOutputTypesBoxStyle = css({ + backgroundColor: "[rgba(0, 0, 0, 0.03)]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + padding: "[12px]", + fontSize: "[12px]", + color: "[#666]", + lineHeight: "[1.5]", + marginTop: "[20px]", +}); + +const noOutputTitleStyle = css({ + fontWeight: 500, + marginBottom: "[4px]", +}); + +const spacerStyle = css({ + height: "[40px]", +}); + interface TransitionPropertiesProps { transition: Transition; places: Place[]; @@ -143,17 +340,10 @@ export const TransitionProperties: React.FC = ({ const { removeTransition } = useSDCPNContext(); return ( -
    +
    -
    -
    Transition
    +
    +
    Transition
    @@ -192,9 +366,7 @@ export const TransitionProperties: React.FC = ({
    -
    - Name -
    +
    Name
    = ({ }); }} disabled={isReadOnly} - style={{ - fontSize: 14, - padding: "6px 8px", - border: "1px solid rgba(0, 0, 0, 0.1)", - borderRadius: 4, - width: "100%", - boxSizing: "border-box", - backgroundColor: isReadOnly ? "rgba(0, 0, 0, 0.05)" : "white", - cursor: isReadOnly ? "not-allowed" : "text", - }} + className={inputStyle({ isReadOnly })} />
    -
    -
    - Input Arcs -
    +
    +
    Input Arcs
    {transition.inputArcs.length === 0 ? ( -
    +
    Connect inputs to the transition's left side.
    ) : ( -
    +
    = ({
    -
    - Output Arcs -
    +
    Output Arcs
    {transition.outputArcs.length === 0 ? ( -
    +
    Connect outputs to the transition's right side.
    ) : ( -
    +
    = ({ )}
    -
    -
    +
    +
    Firing time
    -
    +
    = ({
    -
    +
    {transition.lambdaType === "predicate" ? "For a simple predicate firing check, define a boolean guard condition that must be satisfied. The transition will fire when the function returns true, enabling discrete control flow." : "For a stochastic firing rate, return a value that represents the average rate per second at which the transition will fire."} @@ -373,21 +506,8 @@ export const TransitionProperties: React.FC = ({
    -
    -
    +
    +
    {transition.lambdaType === "predicate" ? "Predicate Firing Code" : "Stochastic Firing Rate Code"} @@ -395,19 +515,7 @@ export const TransitionProperties: React.FC = ({ {globalMode === "edit" && ( + } @@ -427,14 +535,8 @@ export const TransitionProperties: React.FC = ({ id: "generate-ai", label: ( -
    - +
    + Generate with AI
    @@ -448,16 +550,7 @@ export const TransitionProperties: React.FC = ({ /> )}
    -
    +
    `${a.placeId}:${a.weight}`) @@ -491,36 +584,15 @@ export const TransitionProperties: React.FC = ({ {/* Only show Transition Results if at least one output place has a type */} {hasOutputPlaceWithType ? (
    -
    -
    +
    +
    Transition Results
    {globalMode === "edit" && ( + } @@ -576,14 +648,8 @@ export const TransitionProperties: React.FC = ({ id: "generate-ai", label: ( -
    - +
    + Generate with AI
    @@ -597,16 +663,7 @@ export const TransitionProperties: React.FC = ({ /> )}
    -
    +
    `${a.placeId}:${a.weight}`) @@ -639,21 +696,8 @@ export const TransitionProperties: React.FC = ({
    ) : ( -
    -
    - Transition Results -
    +
    +
    Transition Results
    The Transition Results section is not available because none of the output places have a type defined. To enable this feature, assign a @@ -662,7 +706,7 @@ export const TransitionProperties: React.FC = ({
    )} -
    +
    ); }; diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/type-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/type-properties.tsx index 48cd563ebf1..998b02a5b61 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/type-properties.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/type-properties.tsx @@ -1,4 +1,4 @@ -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; import { useState } from "react"; import { v4 as uuidv4 } from "uuid"; @@ -6,6 +6,228 @@ import type { Color } from "../../../../core/types/sdcpn"; import { useSimulationStore } from "../../../../state/simulation-provider"; import { ColorSelect } from "./color-select"; +const containerStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[12px]", +}); + +const headerTitleStyle = css({ + fontWeight: 600, + fontSize: "[16px]", + marginBottom: "[8px]", +}); + +const fieldLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", + marginBottom: "[4px]", +}); + +const inputStyle = cva({ + base: { + fontSize: "[14px]", + padding: "[6px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + borderRadius: "[4px]", + width: "[100%]", + boxSizing: "border-box", + }, + variants: { + isDisabled: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + }, +}); + +const dimensionsHeaderStyle = css({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "[8px]", +}); + +const dimensionsLabelStyle = css({ + fontWeight: 500, + fontSize: "[12px]", +}); + +const dimensionsHintStyle = css({ + marginLeft: "[6px]", + fontSize: "[11px]", + color: "[#666]", + fontWeight: 400, +}); + +const addDimensionButtonStyle = cva({ + base: { + fontSize: "[16px]", + padding: "[2px 8px]", + borderRadius: "[4px]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + fontWeight: 600, + cursor: "pointer", + }, + variants: { + isDisabled: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.05)]", + color: "[#999]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[rgba(59, 130, 246, 0.1)]", + color: "[#3b82f6]", + cursor: "pointer", + }, + }, + }, +}); + +const emptyDimensionsStyle = css({ + fontSize: "[12px]", + color: "[#999]", + fontStyle: "italic", + padding: "[8px]", + backgroundColor: "[rgba(0, 0, 0, 0.02)]", + borderRadius: "[4px]", + textAlign: "center", +}); + +const dimensionsListStyle = css({ + display: "flex", + flexDirection: "column", + gap: "[6px]", +}); + +const dimensionRowStyle = cva({ + base: { + display: "flex", + alignItems: "center", + gap: "[6px]", + padding: "[6px]", + borderRadius: "[3px]", + transition: "[all 0.15s ease]", + }, + variants: { + isDragged: { + true: { + backgroundColor: "[rgba(59, 130, 246, 0.1)]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + }, + false: { + backgroundColor: "[rgba(0, 0, 0, 0.03)]", + border: "[1px solid rgba(0, 0, 0, 0.1)]", + }, + }, + isDragOver: { + true: { + backgroundColor: "[rgba(59, 130, 246, 0.05)]", + border: "[1px dashed #3b82f6]", + }, + false: {}, + }, + }, +}); + +const dragHandleStyle = cva({ + base: { + display: "flex", + flexDirection: "column", + gap: "[1.5px]", + opacity: "[0.4]", + flexShrink: 0, + }, + variants: { + isDisabled: { + true: { + cursor: "default", + }, + false: { + cursor: "grab", + }, + }, + }, +}); + +const dragHandleLineStyle = css({ + width: "[10px]", + height: "[1.5px]", + backgroundColor: "[#666]", + borderRadius: "[1px]", +}); + +const indexChipStyle = css({ + fontSize: "[11px]", + fontWeight: 600, + color: "[#666]", + backgroundColor: "[rgba(0, 0, 0, 0.08)]", + borderRadius: "[3px]", + width: "[24px]", + height: "[24px]", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexShrink: 0, +}); + +const dimensionNameInputStyle = cva({ + base: { + fontSize: "[13px]", + padding: "[5px 8px]", + border: "[1px solid rgba(0, 0, 0, 0.15)]", + borderRadius: "[3px]", + flex: 1, + }, + variants: { + isDisabled: { + true: { + backgroundColor: "[rgba(0, 0, 0, 0.02)]", + cursor: "not-allowed", + }, + false: { + backgroundColor: "[white]", + cursor: "text", + }, + }, + }, +}); + +const deleteDimensionButtonStyle = css({ + fontSize: "[16px]", + width: "[28px]", + height: "[28px]", + borderRadius: "[3px]", + border: "[1px solid rgba(239, 68, 68, 0.2)]", + backgroundColor: "[rgba(239, 68, 68, 0.08)]", + color: "[#ef4444]", + cursor: "pointer", + fontWeight: 600, + lineHeight: "[1]", + transition: "[all 0.15s ease]", + flexShrink: 0, + display: "flex", + alignItems: "center", + justifyContent: "center", + _hover: { + backgroundColor: "[rgba(239, 68, 68, 0.15)]", + }, + _disabled: { + backgroundColor: "[rgba(0, 0, 0, 0.02)]", + color: "[#ccc]", + cursor: "not-allowed", + }, +}); + +// --- Helpers --- + /** * Slugify a string to make it a valid JavaScript identifier * - Converts to lowercase @@ -180,285 +402,130 @@ export const TypeProperties: React.FC = ({ }; return ( - <> - -
    -
    -
    - Type -
    -
    +
    +
    +
    Type
    +
    -
    -
    - Name -
    - { - updateType(type.id, (existingType) => { - existingType.name = event.target.value; - }); - }} - disabled={isDisabled} - style={{ - fontSize: 14, - padding: "6px 8px", - border: "1px solid rgba(0, 0, 0, 0.1)", - borderRadius: 4, - width: "100%", - boxSizing: "border-box", - backgroundColor: isDisabled ? "rgba(0, 0, 0, 0.05)" : "white", - cursor: isDisabled ? "not-allowed" : "text", - }} - /> -
    +
    +
    Name
    + { + updateType(type.id, (existingType) => { + existingType.name = event.target.value; + }); + }} + disabled={isDisabled} + className={inputStyle({ isDisabled })} + /> +
    -
    -
    - Color +
    +
    Color
    + { + updateType(type.id, (existingType) => { + existingType.displayColor = color; + }); + }} + disabled={isDisabled} + /> +
    + + {/* Dimensions Section - Editable with drag-to-reorder */} +
    +
    +
    + Dimensions + (order matters)
    - { - updateType(type.id, (existingType) => { - existingType.displayColor = color; - }); - }} +
    - {/* Dimensions Section - Editable with drag-to-reorder */} -
    -
    -
    - Dimensions - + No dimensions defined. Click + to add. +
    + ) : ( +
    + {type.elements.map((element, index) => ( +
    { + handleDragStart(index); }} + onDragOver={(event) => { + handleDragOver(event, index); + }} + onDrop={(event) => { + handleDrop(event, index); + }} + onDragEnd={handleDragEnd} + className={dimensionRowStyle({ + isDragged: draggedIndex === index, + isDragOver: dragOverIndex === index && draggedIndex !== index, + })} > - (order matters) - -
    - -
    + {/* Drag handle */} +
    +
    +
    +
    +
    - {type.elements.length === 0 ? ( -
    - No dimensions defined. Click + to add. -
    - ) : ( -
    - {type.elements.map((element, index) => ( -
    { - handleDragStart(index); - }} - onDragOver={(event) => { - handleDragOver(event, index); + {/* Index chip */} +
    {index}
    + + {/* Name input */} + { + handleUpdateElementName( + element.elementId, + event.target.value, + ); }} - onDrop={(event) => { - handleDrop(event, index); + onBlur={(event) => { + handleBlurElementName( + element.elementId, + event.target.value, + ); }} - onDragEnd={handleDragEnd} - style={{ - display: "flex", - alignItems: "center", - gap: 6, - padding: 6, - backgroundColor: - draggedIndex === index - ? "rgba(59, 130, 246, 0.1)" - : dragOverIndex === index - ? "rgba(59, 130, 246, 0.05)" - : "rgba(0, 0, 0, 0.03)", - borderRadius: 3, - border: - dragOverIndex === index - ? "1px dashed #3b82f6" - : "1px solid rgba(0, 0, 0, 0.1)", - transition: "all 0.15s ease", + disabled={isDisabled} + placeholder="dimension_name" + className={dimensionNameInputStyle({ isDisabled })} + /> + + {/* Delete button */} + -
    - ))} -
    - )} -
    + × + +
    + ))} +
    + )}
    - +
    ); }; diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/visualizer-error-boundary.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/visualizer-error-boundary.tsx index 7702dd431c2..e535d9b5042 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/visualizer-error-boundary.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/PropertiesPanel/visualizer-error-boundary.tsx @@ -1,5 +1,37 @@ +import { css } from "@hashintel/ds-helpers/css"; import { Component, type ErrorInfo, type ReactNode } from "react"; +const errorContainerStyle = css({ + padding: "[16px]", + backgroundColor: "[#ffebee]", + color: "[#c62828]", + borderRadius: "[4px]", + fontSize: "[12px]", + fontFamily: "[monospace]", + maxHeight: "[400px]", + overflow: "auto", +}); + +const errorTitleStyle = css({ + fontWeight: 600, + fontSize: "[14px]", + marginBottom: "[8px]", +}); + +const errorSectionStyle = css({ + marginBottom: "[8px]", +}); + +const stackPreStyle = css({ + margin: "[4px 0 0 0]", + padding: "[8px]", + backgroundColor: "[#ffcdd2]", + borderRadius: "[2px]", + fontSize: "[11px]", + whiteSpace: "pre-wrap", + wordBreak: "break-word", +}); + interface Props { children: ReactNode; } @@ -32,58 +64,21 @@ export class VisualizerErrorBoundary extends Component { if (hasError) { return ( -
    -
    - Visualizer Runtime Error -
    -
    +
    +
    Visualizer Runtime Error
    +
    Error: {error?.message}
    {error?.stack && ( -
    +
    Stack: -
    -                {error.stack}
    -              
    +
    {error.stack}
    )} {errorInfo?.componentStack && (
    Component Stack: -
    -                {errorInfo.componentStack}
    -              
    +
    {errorInfo.componentStack}
    )}
    diff --git a/libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx b/libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx index 440fc0b209c..4b4ba77e23c 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx @@ -1,3 +1,5 @@ +import { css } from "@hashintel/ds-helpers/css"; + import { Box } from "../../components/box"; import { Stack } from "../../components/stack"; import { productionMachines } from "../../examples/broken-machines"; @@ -17,6 +19,29 @@ import { PropertiesPanel } from "./components/PropertiesPanel/properties-panel"; import { exportSDCPN } from "./lib/export-sdcpn"; import { importSDCPN } from "./lib/import-sdcpn"; +const fullHeightStyle = css({ + height: "[100%]", +}); + +const rowContainerStyle = css({ + height: "[100%]", + userSelect: "none", +}); + +const canvasContainerStyle = css({ + width: "[100%]", + position: "relative", + flexGrow: 1, +}); + +const modeSelectorPositionStyle = css({ + position: "absolute", + top: "[24px]", + left: "[50%]", + transform: "translateX(-50%)", + zIndex: 1000, +}); + /** * EditorView is responsible for the overall editor UI layout and controls. * It relies on sdcpn-store and editor-store for state, and uses SDCPNView for visualization. @@ -95,25 +120,11 @@ export const EditorView = ({ } return ( - - - + + + {/* Floating Mode Selector - Top Center */} -
    +
    diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/components/arc.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/components/arc.tsx index 57e3fdc662d..6da29152b0f 100644 --- a/libs/@hashintel/petrinaut/src/views/SDCPN/components/arc.tsx +++ b/libs/@hashintel/petrinaut/src/views/SDCPN/components/arc.tsx @@ -1,7 +1,28 @@ +import { css } from "@hashintel/ds-helpers/css"; +import type { CSSProperties } from "react"; import { BaseEdge, type EdgeProps, getBezierPath } from "reactflow"; import { useEditorStore } from "../../../state/editor-provider"; +const selectionIndicatorStyle: CSSProperties = { + stroke: "rgba(249, 115, 22, 0.4)", + strokeWidth: 8, +}; + +const symbolTextStyle = css({ + fontSize: "[13px]", + fontWeight: "[400]", + fill: "[#999]", + pointerEvents: "none", +}); + +const weightTextStyle = css({ + fontSize: "[14px]", + fontWeight: "[600]", + fill: "[#333]", + pointerEvents: "none", +}); + interface ArcData { tokenWeights: { [tokenTypeId: string]: number; @@ -42,10 +63,7 @@ export const Arc: React.FC> = ({ )} @@ -78,12 +96,7 @@ export const Arc: React.FC> = ({ y="0" textAnchor="middle" dominantBaseline="middle" - style={{ - fontSize: 13, - fontWeight: 400, - fill: "#999", - pointerEvents: "none", - }} + className={symbolTextStyle} > × @@ -93,12 +106,7 @@ export const Arc: React.FC> = ({ y="0" textAnchor="middle" dominantBaseline="middle" - style={{ - fontSize: 14, - fontWeight: 600, - fill: "#333", - pointerEvents: "none", - }} + className={weightTextStyle} > {weight} diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/components/transition-node.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/components/transition-node.tsx index 0d8b73d04eb..3425112ae20 100644 --- a/libs/@hashintel/petrinaut/src/views/SDCPN/components/transition-node.tsx +++ b/libs/@hashintel/petrinaut/src/views/SDCPN/components/transition-node.tsx @@ -1,4 +1,4 @@ -import { css } from "@hashintel/ds-helpers/css"; +import { css, cva } from "@hashintel/ds-helpers/css"; import { TbBolt, TbLambda } from "react-icons/tb"; import { Handle, type NodeProps, Position } from "reactflow"; @@ -7,6 +7,82 @@ import { useSimulationStore } from "../../../state/simulation-provider"; import type { TransitionNodeData } from "../../../state/types-for-editor-to-remove"; import { handleStyling } from "../styles/styling"; +const containerStyle = css({ + position: "relative", + background: "[transparent]", +}); + +const transitionBoxStyle = cva({ + base: { + padding: "spacing.4", + borderRadius: "radius.8", + width: "[160px]", + height: "[80px]", + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + background: "core.gray.20", + border: "2px solid", + borderColor: "core.gray.50", + fontSize: "[15px]", + boxSizing: "border-box", + position: "relative", + cursor: "default", + transition: "[all 0.2s ease]", + _hover: { + borderColor: "core.gray.70", + boxShadow: "0 0 0 4px rgba(59, 130, 246, 0.1)", + }, + }, + variants: { + selection: { + resource: { + boxShadow: + "0 0 0 3px rgba(59, 178, 246, 0.4), 0 0 0 5px rgba(59, 190, 246, 0.2)", + }, + reactflow: { + boxShadow: + "0 0 0 4px rgba(249, 115, 22, 0.4), 0 0 0 6px rgba(249, 115, 22, 0.2)", + }, + none: {}, + }, + }, + defaultVariants: { + selection: "none", + }, +}); + +const stochasticIconStyle = css({ + position: "absolute", + top: "[8px]", + left: "[0px]", + width: "[100%]", + display: "flex", + alignItems: "center", + justifyContent: "center", + color: "core.blue.60", + fontSize: "[18px]", +}); + +const contentWrapperStyle = css({ + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: "spacing.2", +}); + +const labelStyle = css({ + textAlign: "center", +}); + +const firingIndicatorStyle = css({ + fontSize: "[16px]", + color: "core.yellow.60", + display: "flex", + alignItems: "center", +}); + export const TransitionNode: React.FC> = ({ id, data, @@ -35,95 +111,30 @@ export const TransitionNode: React.FC> = ({ // Determine selection state const isSelectedByResource = selectedResourceId === id; + const selectionVariant = isSelectedByResource + ? "resource" + : selected + ? "reactflow" + : "none"; return ( -
    +
    -
    +
    {data.lambdaType === "stochastic" && ( -
    +
    )} -
    -
    - {label} -
    +
    +
    {label}
    {justFired && ( -
    +
    )} diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx index 814f058c47b..7e40301fb16 100644 --- a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx +++ b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx @@ -32,6 +32,15 @@ const REACTFLOW_EDGE_TYPES = { default: Arc, }; +const canvasContainerStyle = css({ + width: "[100%]", + height: "[100%]", + position: "relative", + "& .react-flow__pane": { + cursor: `var(--pane-cursor) !important`, + }, +}); + /** * SDCPNView is responsible for rendering the SDCPN using ReactFlow. * It reads from sdcpn-store and editor-store, and handles all ReactFlow interactions. @@ -295,18 +304,11 @@ export const SDCPNView: React.FC = () => { // eslint-disable-next-line jsx-a11y/no-static-element-interactions
    { // Quick-and-dirty way to delete selected items with keyboard // with two different keys (Delete and Backspace), not possible with ReactFlow `deleteKeyCode` prop