flowbook 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/client/App.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { EmptyState } from "./components/EmptyState";
|
|
|
6
6
|
|
|
7
7
|
export function App() {
|
|
8
8
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
9
|
-
const [searchQuery] = useState("");
|
|
9
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
10
10
|
|
|
11
11
|
const filteredFlows = useMemo(() => {
|
|
12
12
|
if (!searchQuery) return data.flows;
|
|
@@ -38,14 +38,18 @@ export function App() {
|
|
|
38
38
|
<span className="ml-4 text-sm font-mono" style={{ color: 'var(--fb-text-muted)' }}>
|
|
39
39
|
flowbook — localhost:6200
|
|
40
40
|
</span>
|
|
41
|
+
<span className="ml-auto text-sm font-mono" style={{ color: 'var(--fb-text-muted)' }}>
|
|
42
|
+
{data.flows.length} flows
|
|
43
|
+
</span>
|
|
41
44
|
</div>
|
|
42
|
-
|
|
43
45
|
{/* Content area */}
|
|
44
46
|
<div className="flex" style={{ height: '520px' }}>
|
|
45
47
|
<Sidebar
|
|
46
48
|
flows={filteredFlows}
|
|
47
49
|
selectedId={selectedId}
|
|
48
50
|
onSelect={setSelectedId}
|
|
51
|
+
searchQuery={searchQuery}
|
|
52
|
+
onSearchChange={setSearchQuery}
|
|
49
53
|
/>
|
|
50
54
|
<main className="flex-1 overflow-auto" style={{ background: 'var(--fb-bg-content)' }}>
|
|
51
55
|
{selectedFlow ? (
|
|
@@ -8,10 +8,22 @@ interface FlowViewProps {
|
|
|
8
8
|
export function FlowView({ flow }: FlowViewProps) {
|
|
9
9
|
return (
|
|
10
10
|
<div className="p-8 max-w-5xl mx-auto">
|
|
11
|
-
{/* Title */}
|
|
12
|
-
<
|
|
13
|
-
{
|
|
14
|
-
|
|
11
|
+
{/* Title + Category badge */}
|
|
12
|
+
<div className="flex items-center gap-3 mb-1">
|
|
13
|
+
<h1 className="text-2xl font-bold" style={{ color: 'var(--fb-text-primary)' }}>
|
|
14
|
+
{flow.title}
|
|
15
|
+
</h1>
|
|
16
|
+
<span
|
|
17
|
+
className="px-2.5 py-0.5 rounded-md text-xs font-medium"
|
|
18
|
+
style={{
|
|
19
|
+
background: 'rgba(99, 102, 241, 0.12)',
|
|
20
|
+
color: '#818cf8',
|
|
21
|
+
border: '1px solid rgba(99, 102, 241, 0.2)',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
{flow.category}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
15
27
|
|
|
16
28
|
{/* Description */}
|
|
17
29
|
{flow.description && (
|
|
@@ -121,6 +121,12 @@ export function MermaidRenderer({ code, className }: Props) {
|
|
|
121
121
|
};
|
|
122
122
|
}, [code, safeId]);
|
|
123
123
|
|
|
124
|
+
// Reset zoom & pan when switching to a different diagram
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
setScale(1);
|
|
127
|
+
setPos({ x: 0, y: 0 });
|
|
128
|
+
}, [code]);
|
|
129
|
+
|
|
124
130
|
// Wheel zoom (non-passive to allow preventDefault)
|
|
125
131
|
useEffect(() => {
|
|
126
132
|
const el = containerRef.current;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
2
|
import type { FlowEntry } from "../../types";
|
|
3
3
|
|
|
4
4
|
interface SidebarProps {
|
|
5
5
|
flows: FlowEntry[];
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
onSelect: (id: string) => void;
|
|
8
|
+
searchQuery: string;
|
|
9
|
+
onSearchChange: (query: string) => void;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
|
|
12
|
+
export function Sidebar({ flows, selectedId, onSelect, searchQuery, onSearchChange }: SidebarProps) {
|
|
11
13
|
const categories = useMemo(() => {
|
|
12
14
|
const map = new Map<string, FlowEntry[]>();
|
|
13
15
|
for (const flow of flows) {
|
|
@@ -21,45 +23,107 @@ export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
|
|
|
21
23
|
return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
22
24
|
}, [flows]);
|
|
23
25
|
|
|
26
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
27
|
+
|
|
28
|
+
function toggleCategory(category: string) {
|
|
29
|
+
setCollapsed((prev) => {
|
|
30
|
+
const next = new Set(prev);
|
|
31
|
+
if (next.has(category)) next.delete(category);
|
|
32
|
+
else next.add(category);
|
|
33
|
+
return next;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
return (
|
|
25
38
|
<aside
|
|
26
|
-
className="w-64 overflow-y-auto shrink-0 border-r"
|
|
39
|
+
className="w-64 overflow-y-auto shrink-0 border-r flex flex-col"
|
|
27
40
|
style={{
|
|
28
41
|
background: 'var(--fb-bg-sidebar)',
|
|
29
42
|
borderColor: 'var(--fb-border)',
|
|
30
43
|
}}
|
|
31
44
|
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
{/* Search */}
|
|
46
|
+
<div className="p-3 border-b" style={{ borderColor: 'var(--fb-border)' }}>
|
|
47
|
+
<div className="relative">
|
|
48
|
+
<svg
|
|
49
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4"
|
|
50
|
+
style={{ color: 'var(--fb-text-muted)' }}
|
|
51
|
+
fill="none"
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth={2}
|
|
55
|
+
>
|
|
56
|
+
<circle cx="11" cy="11" r="8" />
|
|
57
|
+
<path d="m21 21-4.35-4.35" />
|
|
58
|
+
</svg>
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
placeholder="Search flows..."
|
|
62
|
+
value={searchQuery}
|
|
63
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
64
|
+
className="w-full pl-9 pr-3 py-2 rounded-lg text-sm outline-none"
|
|
65
|
+
style={{
|
|
66
|
+
background: 'rgba(255,255,255,0.05)',
|
|
67
|
+
color: 'var(--fb-text-primary)',
|
|
68
|
+
border: '1px solid var(--fb-border)',
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Categories */}
|
|
75
|
+
<nav className="p-3 space-y-1 flex-1 overflow-y-auto">
|
|
76
|
+
{categories.map(([category, items]) => {
|
|
77
|
+
const isCollapsed = collapsed.has(category);
|
|
78
|
+
return (
|
|
79
|
+
<div key={category}>
|
|
80
|
+
<button
|
|
81
|
+
onClick={() => toggleCategory(category)}
|
|
82
|
+
className="w-full flex items-center gap-1.5 px-2 py-2 rounded-lg text-xs font-bold uppercase tracking-widest hover:bg-white/5 transition-colors"
|
|
83
|
+
style={{ color: 'var(--fb-text-muted)' }}
|
|
84
|
+
>
|
|
85
|
+
<svg
|
|
86
|
+
className="w-3 h-3 transition-transform"
|
|
87
|
+
style={{ transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)' }}
|
|
88
|
+
fill="currentColor"
|
|
89
|
+
viewBox="0 0 20 20"
|
|
90
|
+
>
|
|
91
|
+
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
|
92
|
+
</svg>
|
|
93
|
+
<span className="flex-1 text-left">{category}</span>
|
|
94
|
+
<span
|
|
95
|
+
className="text-xs font-medium tabular-nums"
|
|
96
|
+
style={{ color: 'var(--fb-text-muted)', opacity: 0.7 }}
|
|
97
|
+
>
|
|
98
|
+
{items.length}
|
|
99
|
+
</span>
|
|
100
|
+
</button>
|
|
101
|
+
{!isCollapsed && (
|
|
102
|
+
<div className="space-y-0.5 mt-0.5">
|
|
103
|
+
{items.map((flow) => {
|
|
104
|
+
const isActive = selectedId === flow.id;
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
key={flow.id}
|
|
108
|
+
onClick={() => onSelect(flow.id)}
|
|
109
|
+
className={`w-full text-left px-4 py-2 rounded-lg text-sm transition-colors ${
|
|
110
|
+
isActive ? 'font-medium' : 'hover:bg-white/5'
|
|
111
|
+
}`}
|
|
112
|
+
style={{
|
|
113
|
+
color: isActive ? 'var(--fb-accent-green)' : 'var(--fb-text-secondary)',
|
|
114
|
+
background: isActive ? 'rgba(74, 222, 128, 0.08)' : undefined,
|
|
115
|
+
paddingLeft: '1.75rem',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{flow.title}
|
|
119
|
+
</button>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
60
124
|
</div>
|
|
61
|
-
|
|
62
|
-
)
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
63
127
|
{categories.length === 0 && (
|
|
64
128
|
<p className="text-sm px-2 py-4 text-center" style={{ color: 'var(--fb-text-muted)' }}>
|
|
65
129
|
No flows found
|