Skip to content

Commit d2e3729

Browse files
committed
feat: demo - copy and explanation panel tweaks
1 parent 3c12065 commit d2e3729

File tree

13 files changed

+1084
-468
lines changed

13 files changed

+1084
-468
lines changed

apps/demo/src/lib/components/CodePanel.svelte

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script lang="ts">
2-
import { onMount, createEventDispatcher } from 'svelte';
2+
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
3+
import { fade } from 'svelte/transition';
34
import { codeToHtml } from 'shiki';
45
import { FLOW_CODE, getStepFromLine, FLOW_SECTIONS } from '$lib/data/flow-code';
56
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
67
import StatusBadge from '$lib/components/StatusBadge.svelte';
78
import PulseDot from '$lib/components/PulseDot.svelte';
8-
import MiniDAG from '$lib/components/MiniDAG.svelte';
99
1010
interface Props {
1111
flowState: ReturnType<typeof createFlowState>;
@@ -16,7 +16,7 @@
1616
let { flowState, selectedStep, hoveredStep }: Props = $props();
1717
1818
const dispatch = createEventDispatcher<{
19-
'step-selected': { stepSlug: string };
19+
'step-selected': { stepSlug: string | null };
2020
'step-hovered': { stepSlug: string | null };
2121
}>();
2222
@@ -25,6 +25,7 @@
2525
let highlightedSectionsExpanded = $state<Record<string, string>>({});
2626
let codeContainer: HTMLElement | undefined = $state(undefined);
2727
let isMobile = $state(false);
28+
let cleanupHandlers: (() => void) | undefined;
2829
2930
// Section order for mobile rendering
3031
const SECTION_ORDER = ['flow_config', 'fetchArticle', 'summarize', 'extractKeywords', 'publish'];
@@ -126,11 +127,17 @@
126127
function setupClickHandlersDelayed() {
127128
setTimeout(() => {
128129
if (codeContainer) {
129-
setupClickHandlers();
130+
cleanupHandlers = setupClickHandlers();
130131
}
131132
}, 50);
132133
}
133134
135+
onDestroy(() => {
136+
if (cleanupHandlers) {
137+
cleanupHandlers();
138+
}
139+
});
140+
134141
// Re-setup handlers when view changes
135142
$effect(() => {
136143
const mobile = isMobile;
@@ -145,6 +152,9 @@
145152
function setupClickHandlers() {
146153
if (!codeContainer) return;
147154
155+
// Store handlers for cleanup
156+
const handlers: Array<{ element: Element; type: string; handler: EventListener }> = [];
157+
148158
// Find all line elements
149159
const lines = codeContainer.querySelectorAll('.line');
150160
lines.forEach((line, index) => {
@@ -167,19 +177,32 @@
167177
dispatch('step-selected', { stepSlug });
168178
};
169179
line.addEventListener('click', clickHandler);
180+
handlers.push({ element: line, type: 'click', handler: clickHandler });
170181
171182
// Hover handlers - dispatch hover events (desktop only)
172183
if (!isMobile) {
173-
line.addEventListener('mouseenter', () => {
184+
const enterHandler = () => {
174185
dispatch('step-hovered', { stepSlug });
175-
});
176-
177-
line.addEventListener('mouseleave', () => {
186+
};
187+
const leaveHandler = () => {
178188
dispatch('step-hovered', { stepSlug: null });
179-
});
189+
};
190+
191+
line.addEventListener('mouseenter', enterHandler);
192+
line.addEventListener('mouseleave', leaveHandler);
193+
194+
handlers.push({ element: line, type: 'mouseenter', handler: enterHandler });
195+
handlers.push({ element: line, type: 'mouseleave', handler: leaveHandler });
180196
}
181197
}
182198
});
199+
200+
// Return cleanup function
201+
return () => {
202+
handlers.forEach(({ element, type, handler }) => {
203+
element.removeEventListener(type, handler);
204+
});
205+
};
183206
}
184207
185208
// Update line highlighting and borders based on step status, selected, and hovered steps
@@ -221,22 +244,32 @@
221244
<div class="code-panel-wrapper">
222245
{#if isMobile && selectedStep}
223246
<!-- Mobile: Show only selected section in explanation panel (expanded version) with optional mini DAG -->
224-
<div class="mobile-code-container">
225-
<div class="code-panel mobile-selected">
247+
{#key selectedStep}
248+
<div
249+
class="code-panel mobile-selected"
250+
in:fade={{ duration: 250 }}
251+
out:fade={{ duration: 150 }}
252+
onclick={(e) => {
253+
// Handle clicks anywhere in code panel
254+
e.stopPropagation();
255+
dispatch('step-selected', { stepSlug: null });
256+
}}
257+
role="button"
258+
tabindex="0"
259+
>
226260
{#if highlightedSectionsExpanded[selectedStep]}
227261
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
228262
{@html highlightedSectionsExpanded[selectedStep]}
229263
{/if}
230264
</div>
231-
{#if selectedStep !== 'flow_config'}
232-
<div class="mini-dag-container">
233-
<MiniDAG {selectedStep} />
234-
</div>
235-
{/if}
236-
</div>
265+
{/key}
237266
{:else if isMobile}
238267
<!-- Mobile: Show all sections as separate blocks -->
239-
<div class="code-panel mobile-sections">
268+
<div
269+
class="code-panel mobile-sections"
270+
in:fade={{ duration: 250 }}
271+
out:fade={{ duration: 150 }}
272+
>
240273
{#each SECTION_ORDER as sectionSlug, index (sectionSlug)}
241274
{@const stepStatus = getStepStatus(sectionSlug)}
242275
{@const isDimmed = selectedStep && sectionSlug !== selectedStep}
@@ -305,31 +338,19 @@
305338
position: relative;
306339
}
307340
308-
.mobile-code-container {
309-
display: flex;
310-
gap: 12px;
311-
align-items: center;
312-
}
313-
314-
.mini-dag-container {
315-
flex-shrink: 0;
316-
width: 95px;
317-
padding-right: 12px;
318-
opacity: 0.7;
319-
}
320-
321341
.code-panel {
322342
overflow-x: auto;
323343
border-radius: 5px;
324344
}
325345
326346
.code-panel.mobile-selected {
327347
/* Compact height when showing only selected step on mobile */
328-
min-height: auto;
348+
min-height: 120px;
329349
font-size: 12px;
330350
background: #0d1117;
331351
position: relative;
332352
flex: 1;
353+
cursor: pointer;
333354
}
334355
335356
.code-panel.mobile-selected :global(pre) {
@@ -344,6 +365,8 @@
344365
/* Mobile: Container for separate section blocks */
345366
font-size: 12px;
346367
border-radius: 0;
368+
will-change: opacity;
369+
background: #0d1117;
347370
}
348371
349372
/* Mobile: Smaller font, no border radius (touches edges) */
@@ -408,6 +431,8 @@
408431
border-radius: 5px;
409432
line-height: 1.5;
410433
font-size: 13px; /* Desktop default */
434+
display: table;
435+
min-width: 100%;
411436
}
412437
413438
/* Mobile: Smaller padding */
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script lang="ts">
2+
import { Handle, Position } from '@xyflow/svelte';
3+
import PulseDot from './PulseDot.svelte';
4+
5+
interface Props {
6+
data: { label: string };
7+
}
8+
9+
let { data }: Props = $props();
10+
</script>
11+
12+
<div class="dag-node-content">
13+
<PulseDot />
14+
{data.label}
15+
<Handle type="target" position={Position.Top} />
16+
<Handle type="source" position={Position.Bottom} />
17+
</div>
18+
19+
<style>
20+
.dag-node-content {
21+
position: relative;
22+
padding: 6px;
23+
font-size: 14px;
24+
text-align: center;
25+
min-width: 120px;
26+
width: 120px;
27+
box-sizing: border-box;
28+
display: flex;
29+
align-items: center;
30+
justify-content: center;
31+
}
32+
</style>

apps/demo/src/lib/components/DAGVisualization.svelte

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { SvelteFlow } from '@xyflow/svelte';
44
import '@xyflow/svelte/dist/style.css';
55
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
6+
import DAGNode from './DAGNode.svelte';
67
78
interface Props {
89
flowState: ReturnType<typeof createFlowState>;
@@ -15,6 +16,11 @@
1516
let containerElement: HTMLDivElement | undefined = $state(undefined);
1617
let shouldFitView = $state(true);
1718
19+
// Custom node types with PulseDot
20+
const nodeTypes = {
21+
dagNode: DAGNode
22+
};
23+
1824
// Re-center when container or window resizes
1925
onMount(() => {
2026
const handleResize = () => {
@@ -88,37 +94,37 @@
8894
}
8995
9096
// Define the 4-step DAG structure - reactive to step states and selection
91-
// Vertical spacing between nodes (81px between levels)
97+
// Vertical spacing between nodes (110px between levels)
9298
// Shifted up by 30px to center better in viewport
9399
let nodes = $derived([
94100
{
95101
id: 'fetchArticle',
96-
type: 'default',
102+
type: 'dagNode',
97103
position: { x: 150, y: -30 },
98104
data: { label: 'fetchArticle' },
99105
class: getNodeClass('fetchArticle'),
100106
draggable: false
101107
},
102108
{
103109
id: 'summarize',
104-
type: 'default',
105-
position: { x: 50, y: 51 },
110+
type: 'dagNode',
111+
position: { x: 50, y: 80 },
106112
data: { label: 'summarize' },
107113
class: getNodeClass('summarize'),
108114
draggable: false
109115
},
110116
{
111117
id: 'extractKeywords',
112-
type: 'default',
113-
position: { x: 250, y: 51 },
118+
type: 'dagNode',
119+
position: { x: 250, y: 80 },
114120
data: { label: 'extractKeywords' },
115121
class: getNodeClass('extractKeywords'),
116122
draggable: false
117123
},
118124
{
119125
id: 'publish',
120-
type: 'default',
121-
position: { x: 150, y: 132 },
126+
type: 'dagNode',
127+
position: { x: 150, y: 190 },
122128
data: { label: 'publish' },
123129
class: getNodeClass('publish'),
124130
draggable: false
@@ -223,8 +229,9 @@
223229
<SvelteFlow
224230
{nodes}
225231
{edges}
232+
{nodeTypes}
226233
fitView={shouldFitView}
227-
fitViewOptions={{ padding: 0.1 }}
234+
fitViewOptions={{ padding: 0.15 }}
228235
panOnDrag={false}
229236
zoomOnScroll={false}
230237
zoomOnPinch={false}

0 commit comments

Comments
 (0)