Skip to content
Closed
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
89 changes: 57 additions & 32 deletions apps/demo/src/lib/components/CodePanel.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script lang="ts">
import { onMount, createEventDispatcher } from 'svelte';
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { codeToHtml } from 'shiki';
import { FLOW_CODE, getStepFromLine, FLOW_SECTIONS } from '$lib/data/flow-code';
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
import StatusBadge from '$lib/components/StatusBadge.svelte';
import PulseDot from '$lib/components/PulseDot.svelte';
import MiniDAG from '$lib/components/MiniDAG.svelte';

interface Props {
flowState: ReturnType<typeof createFlowState>;
Expand All @@ -16,7 +16,7 @@
let { flowState, selectedStep, hoveredStep }: Props = $props();

const dispatch = createEventDispatcher<{
'step-selected': { stepSlug: string };
'step-selected': { stepSlug: string | null };
'step-hovered': { stepSlug: string | null };
}>();

Expand All @@ -25,6 +25,7 @@
let highlightedSectionsExpanded = $state<Record<string, string>>({});
let codeContainer: HTMLElement | undefined = $state(undefined);
let isMobile = $state(false);
let cleanupHandlers: (() => void) | undefined;

// Section order for mobile rendering
const SECTION_ORDER = ['flow_config', 'fetchArticle', 'summarize', 'extractKeywords', 'publish'];
Expand Down Expand Up @@ -126,11 +127,17 @@
function setupClickHandlersDelayed() {
setTimeout(() => {
if (codeContainer) {
setupClickHandlers();
cleanupHandlers = setupClickHandlers();
}
}, 50);
}

onDestroy(() => {
if (cleanupHandlers) {
cleanupHandlers();
}
});

// Re-setup handlers when view changes
$effect(() => {
const mobile = isMobile;
Expand All @@ -145,6 +152,9 @@
function setupClickHandlers() {
if (!codeContainer) return;

// Store handlers for cleanup
const handlers: Array<{ element: Element; type: string; handler: EventListener }> = [];

// Find all line elements
const lines = codeContainer.querySelectorAll('.line');
lines.forEach((line, index) => {
Expand All @@ -167,19 +177,32 @@
dispatch('step-selected', { stepSlug });
};
line.addEventListener('click', clickHandler);
handlers.push({ element: line, type: 'click', handler: clickHandler });

// Hover handlers - dispatch hover events (desktop only)
if (!isMobile) {
line.addEventListener('mouseenter', () => {
const enterHandler = () => {
dispatch('step-hovered', { stepSlug });
});

line.addEventListener('mouseleave', () => {
};
const leaveHandler = () => {
dispatch('step-hovered', { stepSlug: null });
});
};

line.addEventListener('mouseenter', enterHandler);
line.addEventListener('mouseleave', leaveHandler);

handlers.push({ element: line, type: 'mouseenter', handler: enterHandler });
handlers.push({ element: line, type: 'mouseleave', handler: leaveHandler });
}
}
});

// Return cleanup function
return () => {
handlers.forEach(({ element, type, handler }) => {
element.removeEventListener(type, handler);
});
};
}

// Update line highlighting and borders based on step status, selected, and hovered steps
Expand Down Expand Up @@ -221,22 +244,32 @@
<div class="code-panel-wrapper">
{#if isMobile && selectedStep}
<!-- Mobile: Show only selected section in explanation panel (expanded version) with optional mini DAG -->
<div class="mobile-code-container">
<div class="code-panel mobile-selected">
{#key selectedStep}
<div
class="code-panel mobile-selected"
in:fade={{ duration: 250 }}
out:fade={{ duration: 150 }}
onclick={(e) => {
// Handle clicks anywhere in code panel
e.stopPropagation();
dispatch('step-selected', { stepSlug: null });
}}
role="button"
tabindex="0"
>
{#if highlightedSectionsExpanded[selectedStep]}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html highlightedSectionsExpanded[selectedStep]}
{/if}
</div>
{#if selectedStep !== 'flow_config'}
<div class="mini-dag-container">
<MiniDAG {selectedStep} />
</div>
{/if}
</div>
{/key}
{:else if isMobile}
<!-- Mobile: Show all sections as separate blocks -->
<div class="code-panel mobile-sections">
<div
class="code-panel mobile-sections"
in:fade={{ duration: 250 }}
out:fade={{ duration: 150 }}
>
{#each SECTION_ORDER as sectionSlug, index (sectionSlug)}
{@const stepStatus = getStepStatus(sectionSlug)}
{@const isDimmed = selectedStep && sectionSlug !== selectedStep}
Expand Down Expand Up @@ -305,31 +338,19 @@
position: relative;
}

.mobile-code-container {
display: flex;
gap: 12px;
align-items: center;
}

.mini-dag-container {
flex-shrink: 0;
width: 95px;
padding-right: 12px;
opacity: 0.7;
}

.code-panel {
overflow-x: auto;
border-radius: 5px;
}

.code-panel.mobile-selected {
/* Compact height when showing only selected step on mobile */
min-height: auto;
min-height: 120px;
font-size: 12px;
background: #0d1117;
position: relative;
flex: 1;
cursor: pointer;
}

.code-panel.mobile-selected :global(pre) {
Expand All @@ -344,6 +365,8 @@
/* Mobile: Container for separate section blocks */
font-size: 12px;
border-radius: 0;
will-change: opacity;
background: #0d1117;
}

/* Mobile: Smaller font, no border radius (touches edges) */
Expand Down Expand Up @@ -408,6 +431,8 @@
border-radius: 5px;
line-height: 1.5;
font-size: 13px; /* Desktop default */
display: table;
min-width: 100%;
}

/* Mobile: Smaller padding */
Expand Down
32 changes: 32 additions & 0 deletions apps/demo/src/lib/components/DAGNode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts">
import { Handle, Position } from '@xyflow/svelte';
import PulseDot from './PulseDot.svelte';

interface Props {
data: { label: string };
}

let { data }: Props = $props();
</script>

<div class="dag-node-content">
<PulseDot />
{data.label}
<Handle type="target" position={Position.Top} />
<Handle type="source" position={Position.Bottom} />
</div>

<style>
.dag-node-content {
position: relative;
padding: 6px;
font-size: 14px;
text-align: center;
min-width: 120px;
width: 120px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
</style>
25 changes: 16 additions & 9 deletions apps/demo/src/lib/components/DAGVisualization.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { SvelteFlow } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
import DAGNode from './DAGNode.svelte';

interface Props {
flowState: ReturnType<typeof createFlowState>;
Expand All @@ -15,6 +16,11 @@
let containerElement: HTMLDivElement | undefined = $state(undefined);
let shouldFitView = $state(true);

// Custom node types with PulseDot
const nodeTypes = {
dagNode: DAGNode
};

// Re-center when container or window resizes
onMount(() => {
const handleResize = () => {
Expand Down Expand Up @@ -88,37 +94,37 @@
}

// Define the 4-step DAG structure - reactive to step states and selection
// Vertical spacing between nodes (81px between levels)
// Vertical spacing between nodes (110px between levels)
// Shifted up by 30px to center better in viewport
let nodes = $derived([
{
id: 'fetchArticle',
type: 'default',
type: 'dagNode',
position: { x: 150, y: -30 },
data: { label: 'fetchArticle' },
class: getNodeClass('fetchArticle'),
draggable: false
},
{
id: 'summarize',
type: 'default',
position: { x: 50, y: 51 },
type: 'dagNode',
position: { x: 50, y: 80 },
data: { label: 'summarize' },
class: getNodeClass('summarize'),
draggable: false
},
{
id: 'extractKeywords',
type: 'default',
position: { x: 250, y: 51 },
type: 'dagNode',
position: { x: 250, y: 80 },
data: { label: 'extractKeywords' },
class: getNodeClass('extractKeywords'),
draggable: false
},
{
id: 'publish',
type: 'default',
position: { x: 150, y: 132 },
type: 'dagNode',
position: { x: 150, y: 190 },
data: { label: 'publish' },
class: getNodeClass('publish'),
draggable: false
Expand Down Expand Up @@ -223,8 +229,9 @@
<SvelteFlow
{nodes}
{edges}
{nodeTypes}
fitView={shouldFitView}
fitViewOptions={{ padding: 0.1 }}
fitViewOptions={{ padding: 0.15 }}
panOnDrag={false}
zoomOnScroll={false}
zoomOnPinch={false}
Expand Down
Loading
Loading