Skip to content

Commit 225af7e

Browse files
authored
Merge pull request #280 from pathsim/feature/guided-tour
Guided tours: three interactive walkthroughs + composable infra
2 parents 2d77987 + 60bac18 commit 225af7e

36 files changed

Lines changed: 2364 additions & 93 deletions

package-lock.json

Lines changed: 8 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@codemirror/theme-one-dark": "^6.0.0",
4545
"@xyflow/svelte": "^1.5.0",
4646
"codemirror": "^6.0.0",
47+
"driver.js": "^1.4.0",
4748
"jspdf": "^4.1.0",
4849
"katex": "^0.16.0",
4950
"opentype.js": "^1.3.4",

src/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,3 +717,4 @@ body.assembly-pending .svelte-flow__edge {
717717
transform: scale(1);
718718
}
719719
}
720+

src/lib/components/FlowCanvas.svelte

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import '@xyflow/svelte/dist/style.css';
1515
1616
import { isInputFocused } from '$lib/utils/focus';
17+
import { isTourActive } from '$lib/tours/inputMode';
1718
import BaseNode from './nodes/BaseNode.svelte';
1819
import EventNode from './nodes/EventNode.svelte';
1920
import AnnotationNode from './nodes/AnnotationNode.svelte';
@@ -31,6 +32,7 @@
3132
import { dropTargetBridge } from '$lib/stores/dropTargetBridge';
3233
import { contextMenuStore } from '$lib/stores/contextMenu';
3334
import { nodeUpdatesStore } from '$lib/stores/nodeUpdates';
35+
import { assemblyAnimationTrigger } from '$lib/animation/assemblyAnimation';
3436
import { nodeRegistry } from '$lib/nodes';
3537
import { NODE_TYPES } from '$lib/constants/nodeTypes';
3638
import { GRID_SIZE, SNAP_GRID, BACKGROUND_GAP } from '$lib/constants/grid';
@@ -74,6 +76,8 @@
7476
7577
// Keyboard shortcuts for node manipulation
7678
function handleKeydown(event: KeyboardEvent) {
79+
// While a guided tour runs, driver.js owns the keyboard.
80+
if (isTourActive()) return;
7781
if (isInputFocused(event)) return;
7882
7983
// Handle Delete key (SvelteFlow's deleteKeyCode doesn't work reliably for 'Delete')
@@ -444,6 +448,19 @@
444448
}
445449
});
446450
451+
// On model load, the initial routing pass runs with fallback 80×40
452+
// dimensions because nodes haven't been measured yet, and the per-node
453+
// $effect above silently records first measurements without rerouting.
454+
// Trigger a full reroute once measurements have settled.
455+
let lastAssemblyTrigger = 0;
456+
const unsubAssembly = assemblyAnimationTrigger.subscribe((v) => {
457+
if (v > lastAssemblyTrigger) {
458+
lastAssemblyTrigger = v;
459+
setTimeout(() => updateRoutingContext(), 400);
460+
}
461+
});
462+
onDestroy(() => unsubAssembly());
463+
447464
// Track if we're currently syncing to prevent loops
448465
let isSyncing = false;
449466

src/lib/components/ResizablePanel.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@
179179
<aside
180180
class="resizable-panel glass-panel {position}"
181181
class:resizing={isResizing}
182+
data-panel={title ?? ''}
183+
data-tour={title ? `panel-${title.toLowerCase()}` : ''}
182184
style="
183185
{position === 'left' || position === 'right' ? `width: ${getWidth()}px;` : ''}
184186
{(position === 'bottom-left' || position === 'bottom-right') && controlledWidth !== undefined ? `width: ${getWidth()}px;` : ''}

src/lib/components/SubsystemBreadcrumb.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</script>
2828

2929
{#if !isAtRoot}
30-
<nav class="breadcrumb glass-panel" transition:fly={{ y: -10, duration: 150 }}>
30+
<nav class="breadcrumb glass-panel" data-tour="breadcrumb" transition:fly={{ y: -10, duration: 150 }}>
3131
{#each breadcrumbs as crumb, i}
3232
<button
3333
class="crumb-pill"

src/lib/components/WelcomeModal.svelte

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { cubicOut } from 'svelte/easing';
66
import Icon from '$lib/components/icons/Icon.svelte';
77
import { PATHVIEW_VERSION, EXTRACTED_VERSIONS } from '$lib/constants/dependencies';
8+
import { startGuidedTour, type TourId } from '$lib/tours';
89
910
interface Example {
1011
name: string;
@@ -51,6 +52,12 @@
5152
onClose();
5253
}
5354
55+
function handleStartTour(id: TourId) {
56+
onClose();
57+
// Wait for the banner's slide-out so highlights aren't covered
58+
setTimeout(() => startGuidedTour(id), 350);
59+
}
60+
5461
function handleKeydown(e: KeyboardEvent) {
5562
if (e.key === 'Escape') {
5663
onClose();
@@ -132,6 +139,29 @@
132139

133140
<div class="separator"></div>
134141

142+
<div class="tour-section">
143+
<div class="tour-text">
144+
<strong>Guided Tours</strong>
145+
<span>Step-by-step walkthroughs that highlight the editor's main areas in turn so you know what each does.</span>
146+
</div>
147+
<div class="tour-buttons">
148+
<button class="action-card" onclick={() => handleStartTour('start')}>
149+
<Icon name="compass" size={20} />
150+
<span class="action-label">Start</span>
151+
</button>
152+
<button class="action-card" onclick={() => handleStartTour('modeling')}>
153+
<Icon name="shapes" size={20} />
154+
<span class="action-label">Modeling</span>
155+
</button>
156+
<button class="action-card" onclick={() => handleStartTour('simulation')}>
157+
<Icon name="play-circle" size={20} />
158+
<span class="action-label">Simulation</span>
159+
</button>
160+
</div>
161+
</div>
162+
163+
<div class="separator"></div>
164+
135165
<div class="examples-section">
136166
<div class="examples-grid">
137167
{#each examples as example}
@@ -228,6 +258,43 @@
228258
letter-spacing: 0.2px;
229259
}
230260
261+
.tour-section {
262+
display: grid;
263+
grid-template-columns: repeat(6, 1fr);
264+
gap: 8px;
265+
align-items: center;
266+
}
267+
268+
.tour-text {
269+
grid-column: 1 / 4;
270+
}
271+
272+
.tour-buttons {
273+
grid-column: 4 / -1;
274+
display: grid;
275+
grid-template-columns: repeat(3, 1fr);
276+
gap: 8px;
277+
}
278+
279+
.tour-text {
280+
display: flex;
281+
flex-direction: column;
282+
gap: 2px;
283+
line-height: 1.35;
284+
color: var(--text-muted);
285+
}
286+
287+
.tour-text strong {
288+
font-size: 10px;
289+
font-weight: 600;
290+
text-transform: uppercase;
291+
letter-spacing: 0.5px;
292+
}
293+
294+
.tour-text span {
295+
font-size: 11px;
296+
}
297+
231298
.actions {
232299
display: grid;
233300
grid-template-columns: repeat(6, 1fr);
@@ -278,7 +345,7 @@
278345
279346
.examples-grid {
280347
display: grid;
281-
grid-template-columns: repeat(3, 1fr);
348+
grid-template-columns: repeat(2, 1fr);
282349
grid-auto-rows: min-content;
283350
align-items: start;
284351
gap: 10px;
@@ -383,10 +450,6 @@
383450
.banner-content {
384451
padding-right: 90px;
385452
}
386-
387-
.examples-grid {
388-
grid-template-columns: repeat(2, 1fr);
389-
}
390453
}
391454
392455
@media (max-width: 500px) {

src/lib/components/dialogs/BlockPropertiesDialog.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@
249249

250250
{#if nodeId && node && typeDef}
251251
<div class="dialog-backdrop" onclick={handleBackdropClick} transition:fade={{ duration: 150 }} role="presentation">
252-
<div class="properties-dialog glass-panel" style="--node-color: {currentColor}" transition:scale={{ start: 0.95, duration: 150, easing: cubicOut }} role="dialog" tabindex="-1" aria-labelledby="dialog-title">
252+
<div class="properties-dialog glass-panel" data-tour="dialog-properties" style="--node-color: {currentColor}" transition:scale={{ start: 0.95, duration: 150, easing: cubicOut }} role="dialog" tabindex="-1" aria-labelledby="dialog-title">
253253
<div class="dialog-header">
254254
{#if showCode}
255255
<span id="dialog-title">Python Code</span>
@@ -258,6 +258,7 @@
258258
<input
259259
id="dialog-title"
260260
class="node-name-input"
261+
data-tour="block-name-input"
261262
type="text"
262263
value={node.name}
263264
oninput={(e) => handleNameChange(e.currentTarget.value)}
@@ -377,6 +378,7 @@
377378
<button
378379
class="pin-btn"
379380
class:pinned
381+
data-tour="block-pin-btn"
380382
onclick={() => togglePinParam(param.name)}
381383
use:tooltip={pinned ? "Unpin from node" : "Pin to node"}
382384
aria-label={pinned ? "Unpin from node" : "Pin to node"}

src/lib/components/dialogs/ExportDialog.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135

136136
{#if open}
137137
<div class="dialog-backdrop" transition:fade={{ duration: 150 }} onclick={handleBackdropClick} role="presentation">
138-
<div class="dialog glass-panel" transition:scale={{ start: 0.95, duration: 150, easing: cubicOut }} role="dialog" tabindex="-1" aria-labelledby="dialog-title">
138+
<div class="dialog glass-panel" data-tour="dialog-python-export" transition:scale={{ start: 0.95, duration: 150, easing: cubicOut }} role="dialog" tabindex="-1" aria-labelledby="dialog-title">
139139
<div class="dialog-header">
140140
<span id="dialog-title">Export Python Code</span>
141141
<div class="header-actions">

src/lib/components/dialogs/KeyboardShortcutsDialog.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
9696
<div
9797
class="dialog glass-panel"
98+
data-tour="dialog-shortcuts"
9899
transition:scale={{ start: 0.95, duration: 150, easing: cubicOut }}
99100
onclick={(e) => e.stopPropagation()}
100101
role="dialog"

0 commit comments

Comments
 (0)