graphwalker-studio 1.0.2

Web-based visual model editor and test runner for GraphWalker
import {
  FilePlus,
  FolderOpen,
  Save,
  Undo2,
  Redo2,
  Play,
  Pause,
  SkipForward,
  Square,
  PanelRight,
  Sun,
  Moon,
} from 'lucide-react';
import { useEditorStore } from '@/store/editor-store';
import { useModelStore } from '@/store/model-store';

interface Props {
  onNewModel: () => void;
  onOpenFile: () => void;
  onSaveFile: () => void;
  onPlay: () => void;
  onPause: () => void;
  onStep: () => void;
  onStop: () => void;
  running: boolean;
  paused: boolean;
  observing?: boolean;
}

function IconButton({
  icon: Icon,
  title,
  onClick,
  disabled,
  active,
}: {
  icon: React.ElementType;
  title: string;
  onClick: () => void;
  disabled?: boolean;
  active?: boolean;
}) {
  return (
    <button
      title={title}
      onClick={onClick}
      disabled={disabled}
      className={`
        flex items-center justify-center w-10 h-10 rounded-lg transition-colors
        ${active ? 'bg-primary/20 text-primary' : 'text-text-muted hover:text-text hover:bg-surface-alt'}
        ${disabled ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer'}
      `}
    >
      <Icon size={18} />
    </button>
  );
}

export default function Sidebar(props: Props) {
  const { showProperties, toggleProperties, theme, setTheme } = useEditorStore();
  const canUndo = useModelStore((s) => s._past.length > 0);
  const canRedo = useModelStore((s) => s._future.length > 0);
  const undo = useModelStore((s) => s.undo);
  const redo = useModelStore((s) => s.redo);

  return (
    <div className="flex flex-col items-center w-12 bg-surface border-r border-border py-3 gap-1 shrink-0">
      <div className="flex flex-col gap-1 pb-3 border-b border-border mb-1">
        <IconButton icon={FilePlus} title="New model" onClick={props.onNewModel} />
        <IconButton icon={FolderOpen} title="Open file" onClick={props.onOpenFile} />
        <IconButton icon={Save} title="Save" onClick={props.onSaveFile} />
      </div>

      <div className="flex flex-col gap-1 pb-3 border-b border-border mb-1">
        <IconButton icon={Undo2} title="Undo (Ctrl+Z)" onClick={undo} disabled={!canUndo} />
        <IconButton icon={Redo2} title="Redo (Ctrl+Shift+Z)" onClick={redo} disabled={!canRedo} />
      </div>

      <div className="flex flex-col gap-1 pb-3 border-b border-border mb-1">
        {props.running && !props.paused ? (
          <IconButton icon={Pause} title="Pause" onClick={props.onPause} />
        ) : (
          <IconButton icon={Play} title="Play" onClick={props.onPlay} />
        )}
        <IconButton
          icon={SkipForward}
          title="Step"
          onClick={props.onStep}
          disabled={props.running && !props.paused && !props.observing}
        />
        <IconButton
          icon={Square}
          title="Stop"
          onClick={props.onStop}
          disabled={!props.running && !props.paused}
        />
      </div>

      <div className="flex flex-col gap-1">
        <IconButton
          icon={PanelRight}
          title="Toggle properties"
          onClick={toggleProperties}
          active={showProperties}
        />
        <IconButton
          icon={theme === 'dark' ? Sun : Moon}
          title="Toggle theme"
          onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
        />
      </div>
    </div>
  );
}