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
109 changes: 103 additions & 6 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ button.icon-btn {
height: 32px;
}


/* Form elements */
input,
select,
Expand Down Expand Up @@ -337,6 +338,105 @@ input[type="checkbox"]:focus-visible {
color: var(--accent);
}

/* ─── Component library ─────────────────────────────────────────────────────
Shared building blocks — prefer these over per-component re-definitions. */

/* Section label — small uppercase heading used across panels, dialogs and pages */
.section-label,
.properties-dialog .section-title {
font-size: 10px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}

/* App nav bar — same chrome on the editor and the landing page */
.nav {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--header-height);
padding: 0 var(--space-md);
background: var(--surface-raised);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}

.nav .nav-side {
display: flex;
align-items: center;
gap: var(--space-xs);
}

/* Header strip atop panels and dialogs */
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
min-height: var(--header-height);
padding: 0 var(--space-md);
background: var(--surface-raised);
border-bottom: 1px solid var(--border);
font-weight: 500;
font-size: var(--font-base);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
}

/* Card — raised tile; `.interactive` adds the accent-ring hover signature */
.card {
background: var(--surface-raised);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.card.interactive {
cursor: pointer;
}

.card.interactive:hover {
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}

/* Action card — vertical icon-over-label link/button (quick actions) */
.action-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: transparent;
border: none;
border-radius: var(--radius-md);
color: var(--text-muted);
cursor: pointer;
text-decoration: none;
font-family: inherit;
font-size: 11px;
font-weight: 500;
}

.action-card svg {
color: var(--accent);
transition: transform 0.15s ease;
}

.action-card:hover svg {
transform: scale(1.2);
}

/* Separator — hairline between page sections */
.separator {
height: 1px;
background: var(--border);
}

/* Dialog backdrop */
.dialog-backdrop {
position: fixed;
Expand All @@ -354,7 +454,7 @@ input[type="checkbox"]:focus-visible {
background: transparent;
}

/* Dialog header */
/* Dialog header — the panel-header strip with dialog corner rounding */
.dialog-header {
display: flex;
align-items: center;
Expand All @@ -369,6 +469,7 @@ input[type="checkbox"]:focus-visible {
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
}

/* Properties dialog shared styles (Block/Event properties) */
Expand Down Expand Up @@ -445,12 +546,8 @@ input[type="checkbox"]:focus-visible {
margin-bottom: 0;
}

/* Typography from .section-label (see component library); spacing only */
.properties-dialog .section-title {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
margin-bottom: var(--space-sm);
}

Expand Down
4 changes: 2 additions & 2 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PathView Visual Block Diagram Simulation Editor</title>
<title>PathView · Visual Block Diagram Simulation Editor</title>
<meta name="description" content="Build and simulate dynamic systems visually in your browser. Drag-and-drop block diagram editor powered by PathSim. No installation required." />
<link rel="canonical" href="https://view.pathsim.org/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="PathView Visual Block Diagram Simulation Editor" />
<meta property="og:title" content="PathView · Visual Block Diagram Simulation Editor" />
<meta property="og:description" content="Build and simulate dynamic systems visually in your browser. Drag-and-drop block diagram editor powered by PathSim." />
<script type="application/ld+json">
{
Expand Down
65 changes: 40 additions & 25 deletions src/lib/components/FlowCanvas.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
findFirstAvailableInputPort
} from './canvas';

interface Props {
/** Render as a non-interactive preview: no editing, selection,
* keyboard shortcuts, context menus or pan/zoom; auto-fits the view. */
readonly?: boolean;
}
let { readonly = false }: Props = $props();

// Theme for SvelteFlow
let colorMode = $state<Theme>('dark');
const unsubscribeTheme = themeStore.subscribe((theme) => {
Expand Down Expand Up @@ -1052,18 +1059,18 @@
}
</script>

<svelte:window onkeydown={handleKeydown} />
<svelte:window onkeydown={readonly ? undefined : handleKeydown} />

<div
bind:this={canvasEl}
class="flow-canvas"
role="application"
aria-label="Flow canvas"
ondragover={handleDragOver}
ondragenter={handleDragEnter}
ondragleave={handleDragLeave}
ondblclick={handleCanvasDoubleClick}
onmousemove={handleMouseMove}
ondragover={readonly ? undefined : handleDragOver}
ondragenter={readonly ? undefined : handleDragEnter}
ondragleave={readonly ? undefined : handleDragLeave}
ondblclick={readonly ? undefined : handleCanvasDoubleClick}
onmousemove={readonly ? undefined : handleMouseMove}
>
{#if isFileDragOver}
<div class="drop-zone-overlay">
Expand All @@ -1079,32 +1086,40 @@
bind:edges
{nodeTypes}
{edgeTypes}
onconnect={handleConnect}
onnodedragstart={handleNodeDragStart}
onnodedrag={handleNodeDrag}
onnodedragstop={handleNodeDragStop}
ondelete={handleDelete}
onselectionchange={handleSelectionChange}
ondrop={(e: any) => handleDrop(e.event || e)}
ondragover={(e: any) => handleDragOver(e.event || e)}
onnodecontextmenu={handleNodeContextMenu}
onedgecontextmenu={handleEdgeContextMenu}
onpanecontextmenu={handlePaneContextMenu}
onconnect={readonly ? undefined : handleConnect}
onnodedragstart={readonly ? undefined : handleNodeDragStart}
onnodedrag={readonly ? undefined : handleNodeDrag}
onnodedragstop={readonly ? undefined : handleNodeDragStop}
ondelete={readonly ? undefined : handleDelete}
onselectionchange={readonly ? undefined : handleSelectionChange}
ondrop={readonly ? undefined : (e: any) => handleDrop(e.event || e)}
ondragover={readonly ? undefined : (e: any) => handleDragOver(e.event || e)}
onnodecontextmenu={readonly ? undefined : handleNodeContextMenu}
onedgecontextmenu={readonly ? undefined : handleEdgeContextMenu}
onpanecontextmenu={readonly ? undefined : handlePaneContextMenu}
nodeOrigin={[0.5, 0.5]}
{...{ snapToGrid: true, snapGrid: SNAP_GRID } as any}
deleteKeyCode={['Delete', 'Backspace']}
selectionKeyCode={['Shift']}
multiSelectionKeyCode={['Shift', 'Meta', 'Control']}
deleteKeyCode={readonly ? null : ['Delete', 'Backspace']}
selectionKeyCode={readonly ? null : ['Shift']}
multiSelectionKeyCode={readonly ? null : ['Shift', 'Meta', 'Control']}
{colorMode}
connectOnClick
edgesReconnectable
edgesFocusable
edgesSelectable
connectOnClick={!readonly}
nodesDraggable={!readonly}
nodesConnectable={!readonly}
elementsSelectable={!readonly}
edgesReconnectable={!readonly}
edgesFocusable={!readonly}
edgesSelectable={!readonly}
panOnDrag={!readonly}
zoomOnScroll={!readonly}
zoomOnPinch={!readonly}
preventScrolling={!readonly}
fitView={readonly}
zoomOnDoubleClick={false}
elevateEdgesOnSelect={false}
proOptions={{ hideAttribution: true }}
>
<FlowUpdater pendingUpdates={pendingNodeUpdates} onUpdatesProcessed={clearPendingUpdates} />
<FlowUpdater pendingUpdates={pendingNodeUpdates} onUpdatesProcessed={clearPendingUpdates} embedded={readonly} />
<Background variant={BackgroundVariant.Dots} gap={BACKGROUND_GAP} size={1} />
</SvelteFlow>
</div>
Expand Down
10 changes: 9 additions & 1 deletion src/lib/components/FlowUpdater.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
pendingUpdates: string[];
onUpdatesProcessed: () => void;
edges?: { id: string }[];
/** The canvas doesn't fill the window (e.g. landing preview tile) —
* the panel-padding fitView math is window-based, so use SvelteFlow's
* native fitView against the actual canvas dimensions instead. */
embedded?: boolean;
}

let { pendingUpdates, onUpdatesProcessed, edges = [] }: Props = $props();
let { pendingUpdates, onUpdatesProcessed, edges = [], embedded = false }: Props = $props();

const { getNodes, getEdges, fitView, zoomIn, zoomOut, getViewport, setViewport, screenToFlowPosition } = useSvelteFlow();
const updateNodeInternals = useUpdateNodeInternals();
Expand All @@ -36,6 +40,10 @@
if (nodes.length === 0) {
return;
}
if (embedded) {
fitView({ padding: 0.1, duration });
return;
}

const previewsPinned = get(pinnedPreviewsStore);
const plotState = get(plotDataStore);
Expand Down
17 changes: 1 addition & 16 deletions src/lib/components/ResizablePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -343,22 +343,7 @@
}


/* Panel header */
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--header-height);
padding: 0 var(--space-md);
background: var(--surface-raised);
border-bottom: 1px solid var(--border);
font-weight: 500;
font-size: var(--font-base);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
}
/* .panel-header comes from the app.css component library */

.panel-toolbar {
flex-shrink: 0;
Expand Down
Loading