From ef46cecbe3f3fcf6b00720f148e710a5d1fc56c0 Mon Sep 17 00:00:00 2001 From: Bobby Powers Date: Thu, 29 Jan 2026 18:48:16 -0800 Subject: [PATCH 1/5] format --- src/diagram/ErrorDetails.tsx | 4 +- src/diagram/LineChart.tsx | 47 ++-- src/diagram/ModelPropertiesDrawer.tsx | 6 +- src/diagram/VariableDetails.tsx | 11 +- src/diagram/components/Autocomplete.tsx | 260 +++++++++-------- src/diagram/components/Button.tsx | 109 ++++---- src/diagram/components/Drawer.tsx | 206 +++++++------- src/diagram/components/IconButton.tsx | 86 +++--- src/diagram/components/Snackbar.tsx | 198 +++++++------ src/diagram/components/SpeedDial.tsx | 274 +++++++++--------- src/diagram/components/SvgIcon.tsx | 58 ++-- src/diagram/components/Tabs.tsx | 154 +++++------ src/diagram/components/TextField.tsx | 352 ++++++++++++------------ src/diagram/components/icons.tsx | 192 ++++++------- src/diagram/tests/chart-utils.test.ts | 4 +- src/diagram/tests/drawer.test.tsx | 53 +++- src/diagram/tests/snackbar.test.tsx | 11 +- src/diagram/tests/speed-dial.test.tsx | 89 ++---- src/diagram/tests/text-field.test.tsx | 22 +- src/engine2/tests/api.test.ts | 5 +- src/server/tests/project-route.test.ts | 7 +- 21 files changed, 1040 insertions(+), 1108 deletions(-) diff --git a/src/diagram/ErrorDetails.tsx b/src/diagram/ErrorDetails.tsx index 0e02a532..f225a99a 100644 --- a/src/diagram/ErrorDetails.tsx +++ b/src/diagram/ErrorDetails.tsx @@ -30,9 +30,7 @@ export class ErrorDetails extends React.PureComponent { !modelErrors.isEmpty() ) ) { - errors.push( -
simulation error: {errorCodeDescription(simError.code)}
, - ); + errors.push(
simulation error: {errorCodeDescription(simError.code)}
); } if (!modelErrors.isEmpty()) { for (const err of modelErrors) { diff --git a/src/diagram/LineChart.tsx b/src/diagram/LineChart.tsx index 647241ef..1e9b08cf 100644 --- a/src/diagram/LineChart.tsx +++ b/src/diagram/LineChart.tsx @@ -128,7 +128,11 @@ export class LineChart extends React.PureComponent, xScale: (v: number) => number, yScale: (v: number) => number): string { + private buildPath( + points: ReadonlyArray<{ x: number; y: number }>, + xScale: (v: number) => number, + yScale: (v: number) => number, + ): string { const parts: string[] = []; let started = false; for (const p of points) { @@ -320,13 +324,7 @@ export class LineChart extends React.PureComponent - + {formatTickLabel(tick)} @@ -348,22 +346,8 @@ export class LineChart extends React.PureComponent - - + + {formatTickLabel(tick)} @@ -372,7 +356,11 @@ export class LineChart extends React.PureComponent {/* Series lines */} - + {series.map((s, i) => ( {formatTickLabel(tooltip.dataX)} {tooltip.seriesValues.map((sv, i) => (
- - {sv.name}: {fmt(sv.value)} + + + {sv.name}: {fmt(sv.value)} +
))} diff --git a/src/diagram/ModelPropertiesDrawer.tsx b/src/diagram/ModelPropertiesDrawer.tsx index 3056c306..a04b0eee 100644 --- a/src/diagram/ModelPropertiesDrawer.tsx +++ b/src/diagram/ModelPropertiesDrawer.tsx @@ -42,11 +42,7 @@ export class ModelPropertiesDrawer extends React.PureComponent +
diff --git a/src/diagram/VariableDetails.tsx b/src/diagram/VariableDetails.tsx index faf54a7e..a6a53b9a 100644 --- a/src/diagram/VariableDetails.tsx +++ b/src/diagram/VariableDetails.tsx @@ -269,9 +269,7 @@ export class VariableDetails extends React.PureComponent = []; if (errors) { errors.forEach((error) => { - errorList.push( -
error: {errorCodeDescription(error.code)}
, - ); + errorList.push(
error: {errorCodeDescription(error.code)}
); }); } if (unitErrors) { @@ -288,12 +286,7 @@ export class VariableDetails extends React.PureComponent + ); } diff --git a/src/diagram/components/Autocomplete.tsx b/src/diagram/components/Autocomplete.tsx index e7b675a3..3af224e4 100644 --- a/src/diagram/components/Autocomplete.tsx +++ b/src/diagram/components/Autocomplete.tsx @@ -1,133 +1,127 @@ -// Copyright 2025 The Simlin Authors. All rights reserved. -// Use of this source code is governed by the Apache License, -// Version 2.0, that can be found in the LICENSE file. - -import * as React from 'react'; -import ReactDOM from 'react-dom'; - -import { useCombobox } from 'downshift'; -import clsx from 'clsx'; - -import styles from './Autocomplete.module.css'; - -interface AutocompleteProps { - key?: string; - value?: string | null; - defaultValue?: string; - onChange: (event: any, newValue: string | null) => void; - clearOnEscape?: boolean; - options: string[]; - renderInput: (params: any) => React.ReactNode; -} - -function itemToString(item: string | null): string { - return item || ''; -} - -export default function Autocomplete(props: AutocompleteProps) { - const { value, onChange, clearOnEscape, options, renderInput } = props; - - const [inputValue, setInputValue] = React.useState(value || ''); - const wrapperRef = React.useRef(null); - const [dropdownPosition, setDropdownPosition] = React.useState<{ - top: number; - left: number; - width: number; - } | null>(null); - - // Sync inputValue when value prop changes externally - React.useEffect(() => { - setInputValue(value || ''); - }, [value]); - - const filteredOptions = React.useMemo(() => { - if (!inputValue) return options; - const lower = inputValue.toLowerCase(); - return options.filter((opt) => opt.toLowerCase().includes(lower)); - }, [options, inputValue]); - - const { - isOpen, - getInputProps, - getMenuProps, - getItemProps, - highlightedIndex, - } = useCombobox({ - items: filteredOptions, - itemToString, - inputValue, - selectedItem: value || null, - onInputValueChange: ({ inputValue: newInputValue }) => { - setInputValue(newInputValue || ''); - }, - onSelectedItemChange: ({ selectedItem }) => { - onChange(null, selectedItem || null); - }, - stateReducer: (state, actionAndChanges) => { - const { type, changes } = actionAndChanges; - if (clearOnEscape && type === useCombobox.stateChangeTypes.InputKeyDownEscape) { - return { - ...changes, - selectedItem: null, - inputValue: '', - }; - } - return changes; - }, - }); - - React.useEffect(() => { - if (isOpen && wrapperRef.current) { - const rect = wrapperRef.current.getBoundingClientRect(); - setDropdownPosition({ - top: rect.bottom + window.scrollY, - left: rect.left + window.scrollX, - width: rect.width, - }); - } - }, [isOpen]); - - const inputProps = getInputProps(); - const params = { - InputProps: { - disableUnderline: false, - ref: wrapperRef, - }, - inputProps, - }; - - const menuProps = getMenuProps(); - - const listbox = - isOpen && filteredOptions.length > 0 && dropdownPosition ? ( -
    - {filteredOptions.map((item, index) => ( -
  • - {item} -
  • - ))} -
- ) : ( -
    - ); - - return ( -
    - {renderInput(params)} - {ReactDOM.createPortal(listbox, document.body)} -
    - ); -} +// Copyright 2025 The Simlin Authors. All rights reserved. +// Use of this source code is governed by the Apache License, +// Version 2.0, that can be found in the LICENSE file. + +import * as React from 'react'; +import ReactDOM from 'react-dom'; + +import { useCombobox } from 'downshift'; +import clsx from 'clsx'; + +import styles from './Autocomplete.module.css'; + +interface AutocompleteProps { + key?: string; + value?: string | null; + defaultValue?: string; + onChange: (event: any, newValue: string | null) => void; + clearOnEscape?: boolean; + options: string[]; + renderInput: (params: any) => React.ReactNode; +} + +function itemToString(item: string | null): string { + return item || ''; +} + +export default function Autocomplete(props: AutocompleteProps) { + const { value, onChange, clearOnEscape, options, renderInput } = props; + + const [inputValue, setInputValue] = React.useState(value || ''); + const wrapperRef = React.useRef(null); + const [dropdownPosition, setDropdownPosition] = React.useState<{ + top: number; + left: number; + width: number; + } | null>(null); + + // Sync inputValue when value prop changes externally + React.useEffect(() => { + setInputValue(value || ''); + }, [value]); + + const filteredOptions = React.useMemo(() => { + if (!inputValue) return options; + const lower = inputValue.toLowerCase(); + return options.filter((opt) => opt.toLowerCase().includes(lower)); + }, [options, inputValue]); + + const { isOpen, getInputProps, getMenuProps, getItemProps, highlightedIndex } = useCombobox({ + items: filteredOptions, + itemToString, + inputValue, + selectedItem: value || null, + onInputValueChange: ({ inputValue: newInputValue }) => { + setInputValue(newInputValue || ''); + }, + onSelectedItemChange: ({ selectedItem }) => { + onChange(null, selectedItem || null); + }, + stateReducer: (state, actionAndChanges) => { + const { type, changes } = actionAndChanges; + if (clearOnEscape && type === useCombobox.stateChangeTypes.InputKeyDownEscape) { + return { + ...changes, + selectedItem: null, + inputValue: '', + }; + } + return changes; + }, + }); + + React.useEffect(() => { + if (isOpen && wrapperRef.current) { + const rect = wrapperRef.current.getBoundingClientRect(); + setDropdownPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX, + width: rect.width, + }); + } + }, [isOpen]); + + const inputProps = getInputProps(); + const params = { + InputProps: { + disableUnderline: false, + ref: wrapperRef, + }, + inputProps, + }; + + const menuProps = getMenuProps(); + + const listbox = + isOpen && filteredOptions.length > 0 && dropdownPosition ? ( +
      + {filteredOptions.map((item, index) => ( +
    • + {item} +
    • + ))} +
    + ) : ( +
      + ); + + return ( +
      + {renderInput(params)} + {ReactDOM.createPortal(listbox, document.body)} +
      + ); +} diff --git a/src/diagram/components/Button.tsx b/src/diagram/components/Button.tsx index 7eb0a4d9..3282ef44 100644 --- a/src/diagram/components/Button.tsx +++ b/src/diagram/components/Button.tsx @@ -1,50 +1,59 @@ -// Copyright 2025 The Simlin Authors. All rights reserved. -// Use of this source code is governed by the Apache License, -// Version 2.0, that can be found in the LICENSE file. - -import * as React from 'react'; - -import clsx from 'clsx'; - -import styles from './Button.module.css'; - -interface ButtonProps { - variant?: 'text' | 'contained'; - color?: 'primary' | 'secondary'; - size?: 'small' | 'medium' | 'large'; - disabled?: boolean; - onClick?: (event: React.MouseEvent) => void; - className?: string; - startIcon?: React.ReactNode; - children?: React.ReactNode; -} - -export default class Button extends React.PureComponent { - render() { - const { variant = 'text', color = 'primary', size = 'medium', disabled, onClick, className, startIcon, children } = this.props; - - const sizeClass = size === 'small' ? styles.sizeSmall : size === 'large' ? styles.sizeLarge : styles.sizeMedium; - - let variantColorClass: string; - let disabledClass: string | undefined; - if (variant === 'contained') { - variantColorClass = color === 'secondary' ? styles.containedSecondary : styles.containedPrimary; - disabledClass = disabled ? styles.disabledContained : undefined; - } else { - variantColorClass = color === 'secondary' ? styles.textSecondary : styles.textPrimary; - disabledClass = disabled ? styles.disabledText : undefined; - } - - return ( - - ); - } -} +// Copyright 2025 The Simlin Authors. All rights reserved. +// Use of this source code is governed by the Apache License, +// Version 2.0, that can be found in the LICENSE file. + +import * as React from 'react'; + +import clsx from 'clsx'; + +import styles from './Button.module.css'; + +interface ButtonProps { + variant?: 'text' | 'contained'; + color?: 'primary' | 'secondary'; + size?: 'small' | 'medium' | 'large'; + disabled?: boolean; + onClick?: (event: React.MouseEvent) => void; + className?: string; + startIcon?: React.ReactNode; + children?: React.ReactNode; +} + +export default class Button extends React.PureComponent { + render() { + const { + variant = 'text', + color = 'primary', + size = 'medium', + disabled, + onClick, + className, + startIcon, + children, + } = this.props; + + const sizeClass = size === 'small' ? styles.sizeSmall : size === 'large' ? styles.sizeLarge : styles.sizeMedium; + + let variantColorClass: string; + let disabledClass: string | undefined; + if (variant === 'contained') { + variantColorClass = color === 'secondary' ? styles.containedSecondary : styles.containedPrimary; + disabledClass = disabled ? styles.disabledContained : undefined; + } else { + variantColorClass = color === 'secondary' ? styles.textSecondary : styles.textPrimary; + disabledClass = disabled ? styles.disabledText : undefined; + } + + return ( + + ); + } +} diff --git a/src/diagram/components/Drawer.tsx b/src/diagram/components/Drawer.tsx index 823798c9..4df9ca92 100644 --- a/src/diagram/components/Drawer.tsx +++ b/src/diagram/components/Drawer.tsx @@ -1,103 +1,103 @@ -// Copyright 2025 The Simlin Authors. All rights reserved. -// Use of this source code is governed by the Apache License, -// Version 2.0, that can be found in the LICENSE file. - -import * as React from 'react'; -import ReactDOM from 'react-dom'; - -import clsx from 'clsx'; - -import styles from './Drawer.module.css'; - -interface DrawerProps { - open: boolean; - onOpen?: () => void; - onClose: () => void; - children?: React.ReactNode; -} - -export default class Drawer extends React.PureComponent { - private panelRef = React.createRef(); - private previousActiveElement: Element | null = null; - - componentDidMount() { - document.addEventListener('keydown', this.handleKeyDown); - } - - componentDidUpdate(prevProps: DrawerProps) { - if (this.props.open && !prevProps.open) { - // Drawer just opened - save current focus and focus the panel - this.previousActiveElement = document.activeElement; - this.panelRef.current?.focus(); - } else if (!this.props.open && prevProps.open) { - // Drawer just closed - restore focus - if (this.previousActiveElement instanceof HTMLElement) { - this.previousActiveElement.focus(); - } - this.previousActiveElement = null; - } - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.handleKeyDown); - } - - handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape' && this.props.open) { - this.props.onClose(); - } - - // Focus trap: when Tab is pressed and drawer is open, keep focus within the panel - if (event.key === 'Tab' && this.props.open && this.panelRef.current) { - const panel = this.panelRef.current; - const focusableElements = panel.querySelectorAll( - 'a, button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable]', - ); - - if (focusableElements.length === 0) { - event.preventDefault(); - return; - } - - const firstElement = focusableElements[0]; - const lastElement = focusableElements[focusableElements.length - 1]; - - if (event.shiftKey && document.activeElement === firstElement) { - event.preventDefault(); - lastElement.focus(); - } else if (!event.shiftKey && document.activeElement === lastElement) { - event.preventDefault(); - firstElement.focus(); - } - } - }; - - handleBackdropClick = () => { - this.props.onClose(); - }; - - render() { - const { open, children } = this.props; - - const content = ( - <> -