Skip to content

Commit 82d9853

Browse files
committed
feat(visualizer): improve searching experience
- dark mode support - navigate between result options using up and down - handle enter press for selection - add shortcut command for search
1 parent e5420be commit 82d9853

File tree

8 files changed

+16779
-12097
lines changed

8 files changed

+16779
-12097
lines changed

.vscode/launch.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"outFiles": [
1616
"${workspaceFolder}/packages/dbml-vs-code-extension/dist/extension/*.js"
1717
],
18-
"preLaunchTask": "npm: dev"
18+
"preLaunchTask": "yarn: dev"
1919
},
2020
{
2121
"name": "Preview DBML Extension",
@@ -27,7 +27,7 @@
2727
"outFiles": [
2828
"${workspaceFolder}/packages/dbml-vs-code-extension/dist/extension/*.js"
2929
],
30-
"preLaunchTask": "npm: build"
30+
"preLaunchTask": "yarn: build"
3131
},
3232
{
3333
"name": "Preview Prisma Extension",
@@ -39,7 +39,7 @@
3939
"outFiles": [
4040
"${workspaceFolder}/packages/prisma-vs-code-extension/dist/extension/*.js"
4141
],
42-
"preLaunchTask": "npm: build:prisma"
42+
"preLaunchTask": "yarn: build:prisma"
4343
}
4444
]
4545
}

packages/dbml-vs-code-extension/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@
8787
"pretest": "yarn run compile-tests && yarn run build && yarn run lint",
8888
"lint": "eslint src --ext ts",
8989
"test": "vscode-test",
90-
"build": "vite build && yarn run generate:css",
90+
"build": "npx vite build && yarn run generate:css",
9191
"package": "yarn run build",
92-
"preview": "vite preview",
92+
"preview": "npx vite preview",
9393
"create:package": "vsce package",
9494
"publish": "vsce publish",
9595
"generate:css": "cd ../json-table-schema-visualizer && npx tailwindcss -i ./src/styles/index.css > ../dbml-vs-code-extension/dist/webview/assets/index.css --minify"

packages/json-table-schema-visualizer/src/components/DiagramViewer/DiagramViewer.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import Tables from "./Tables";
1414
import TablesPositionsProvider from "@/providers/TablesPositionsProvider";
1515
import MainProviders from "@/providers/MainProviders";
1616
import TableLevelDetailProvider from "@/providers/TableDetailLevelProvider";
17-
17+
import { useThemeContext } from "@/hooks/theme";
18+
import { Theme } from "@/types/theme";
1819
interface DiagramViewerProps {
1920
tables: JSONTableTable[];
2021
refs: JSONTableRef[];
2122
enums: JSONTableEnum[];
2223
}
2324

2425
const DiagramViewer = ({ refs, tables, enums }: DiagramViewerProps) => {
26+
const { theme } = useThemeContext();
27+
2528
if (tables.length === 0) {
2629
return <EmptyTableMessage />;
2730
}
@@ -30,13 +33,16 @@ const DiagramViewer = ({ refs, tables, enums }: DiagramViewerProps) => {
3033
<TableLevelDetailProvider>
3134
<TablesPositionsProvider tables={tables} refs={refs}>
3235
<MainProviders tables={tables} enums={enums}>
33-
<>
36+
<main
37+
className={`relative flex flex-col items-center ${theme === Theme.dark ? "dark" : ""}`}
38+
>
3439
<Search tables={tables} />
40+
3541
<DiagramWrapper>
3642
<RelationsConnections refs={refs} />
3743
<Tables tables={tables} />
3844
</DiagramWrapper>
39-
</>
45+
</main>
4046
</MainProviders>
4147
</TablesPositionsProvider>
4248
</TableLevelDetailProvider>

packages/json-table-schema-visualizer/src/components/DiagramViewer/DiagramWrapper.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import type { Stage as CoreStage } from "konva/lib/Stage";
99
import { useWindowSize } from "@/hooks/window";
1010
import { useCursorChanger } from "@/hooks/cursor";
1111
import { DIAGRAM_PADDING } from "@/constants/sizing";
12-
import { useThemeColors, useThemeContext } from "@/hooks/theme";
13-
import { Theme } from "@/types/theme";
12+
import { useThemeColors } from "@/hooks/theme";
1413
import { useStageStartingState } from "@/hooks/stage";
1514
import { stageStateStore } from "@/stores/stagesState";
1615
import { useScrollDirectionContext } from "@/hooks/scrollDirection";
@@ -26,7 +25,6 @@ interface DiagramWrapperProps {
2625
const DiagramWrapper = ({ children }: DiagramWrapperProps) => {
2726
const scaleBy = 1.02;
2827
const { height: windowHeight, width: windowWidth } = useWindowSize();
29-
const { theme } = useThemeContext();
3028
const { scrollDirection } = useScrollDirectionContext();
3129
const { onChange: onGrabbing, onRestore: onGrabRelease } =
3230
useCursorChanger("grabbing");
@@ -211,9 +209,7 @@ const DiagramWrapper = ({ children }: DiagramWrapperProps) => {
211209
}, []);
212210

213211
return (
214-
<main
215-
className={`relative flex flex-col items-center ${theme === Theme.dark ? "dark" : ""}`}
216-
>
212+
<>
217213
<Stage
218214
draggable
219215
ref={stageRef}
@@ -234,7 +230,7 @@ const DiagramWrapper = ({ children }: DiagramWrapperProps) => {
234230
</Stage>
235231

236232
<Toolbar onFitToView={fitToView} />
237-
</main>
233+
</>
238234
);
239235
};
240236

packages/json-table-schema-visualizer/src/components/Search/Search.tsx

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { useState, useMemo, useRef, useEffect } from "react";
1+
import {
2+
useState,
3+
useMemo,
4+
useRef,
5+
useEffect,
6+
type KeyboardEvent,
7+
} from "react";
28
import { type JSONTableTable } from "shared/types/tableSchema";
39

410
import eventEmitter from "@/events-emitter";
@@ -23,6 +29,7 @@ const Search = ({ tables }: SearchProps) => {
2329
const [isOpen, setIsOpen] = useState(false);
2430
const { setHoveredTableName, setHighlightedColumns } = useTablesInfo();
2531
const dropdownRef = useRef<HTMLDivElement>(null);
32+
const inputRef = useRef<HTMLInputElement>(null);
2633

2734
const searchResults = useMemo(() => {
2835
if (search.length < 2) return [];
@@ -87,35 +94,85 @@ const Search = ({ tables }: SearchProps) => {
8794
};
8895
}, []);
8996

97+
useEffect(() => {
98+
const handleShortcut = (e: globalThis.KeyboardEvent) => {
99+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "f") {
100+
e.preventDefault();
101+
inputRef.current?.focus();
102+
}
103+
};
104+
105+
window.addEventListener("keydown", handleShortcut);
106+
return () => {
107+
window.removeEventListener("keydown", handleShortcut);
108+
};
109+
}, []);
110+
111+
const handleOnEnterClick = (e: KeyboardEvent<HTMLInputElement>) => {
112+
if (e.key === "Enter") {
113+
if (searchResults.length > 0) {
114+
handleSelect(searchResults[0]);
115+
setIsOpen(false);
116+
}
117+
}
118+
if (e.key === "ArrowDown") {
119+
const firstButton = dropdownRef.current?.querySelector("button");
120+
firstButton?.focus();
121+
}
122+
};
123+
124+
const handleOptionClick = (e: KeyboardEvent<HTMLButtonElement>) => {
125+
if (e.key === "ArrowDown") {
126+
e.preventDefault();
127+
(e.currentTarget.nextElementSibling as HTMLElement | null)?.focus();
128+
} else if (e.key === "ArrowUp") {
129+
e.preventDefault();
130+
(e.currentTarget.previousElementSibling as HTMLElement | null)?.focus();
131+
} else if (e.key === "Enter") {
132+
e.preventDefault();
133+
e.currentTarget.click();
134+
}
135+
};
136+
90137
return (
91138
<div className="fixed top-4 right-4 z-50" ref={dropdownRef}>
92139
<div className="relative">
93-
<input
94-
type="text"
95-
value={search}
96-
onChange={(e) => {
97-
setSearch(e.target.value);
98-
setIsOpen(true);
99-
}}
100-
placeholder="Search tables and columns..."
101-
className="w-64 px-4 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
102-
/>
140+
<div
141+
title="Use ⌘+F or control+F command to search"
142+
className="relative flex items-center"
143+
>
144+
<input
145+
type="text"
146+
value={search}
147+
onKeyDown={handleOnEnterClick}
148+
ref={inputRef}
149+
onChange={(e) => {
150+
setSearch(e.target.value);
151+
setIsOpen(true);
152+
}}
153+
placeholder="Search tables and columns..."
154+
className="w-72 px-4 py-3 focus:outline-none text-sm rounded-xl border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
155+
/>
156+
157+
<span className="absolute right-2 px-2 py-1 rounded-lg bg-gray-200 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
158+
⌘F
159+
</span>
160+
</div>
103161

104162
{isOpen && searchResults.length > 0 && (
105-
<div className="absolute top-full mt-1 w-full bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 max-h-60 overflow-y-auto">
163+
<div className="absolute top-full mt-1 w-full bg-white dark:bg-gray-700 rounded-xl shadow-lg border border-gray-200 dark:border-gray-600 max-h-60 overflow-y-auto">
106164
{searchResults.map((result, index) => (
107165
<button
166+
tabIndex={-index}
108167
key={`${result.type}-${result.tableName}-${result.name}-${index}`}
109168
onClick={() => {
110169
handleSelect(result);
111170
}}
112-
className="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-600 flex flex-col items-start"
171+
onKeyDown={handleOptionClick}
172+
className="w-full px-4 py-2 text-left text-sm focus:outline-none hover:bg-gray-100 dark:hover:bg-gray-600 focus:bg-gray-100 dark:focus:bg-gray-600 flex flex-col items-start"
113173
>
114174
<div className="flex space-x-2 w-full items-start">
115-
<span className="text-xs mt-[2px]">
116-
{result.type === "table" ? "📋" : "🔤"}
117-
</span>
118-
<span className="font-medium break-all text-gray-700">
175+
<span className="font-medium break-all text-gray-700 dark:text-gray-200">
119176
{result.name}
120177
</span>
121178
</div>

packages/json-table-schema-visualizer/src/components/Table.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,31 @@ const Table = ({ fields, name }: TableProps) => {
6262
// Subscribe to highlight events for this table and animate the border
6363
useEffect(() => {
6464
const eventName = `highlight:table:${name}`;
65+
6566
const handler = () => {
66-
// Animate using Konva to make transition smooth
6767
const rect = highlightRef.current;
68-
if (rect != null) {
69-
// Set stroke color according to theme
70-
const color = theme === Theme.dark ? "#FBBF24" : "#3B82F6";
71-
rect.stroke(color);
72-
rect.to({ strokeWidth: 3, opacity: 1, duration: 0.18 });
73-
74-
// After 2s, animate out
75-
setTimeout(() => {
76-
rect.to({ strokeWidth: 0, opacity: 0, duration: 0.28 });
77-
}, 2000);
78-
}
68+
if (rect === null || rect === undefined) return;
69+
70+
const color = theme === Theme.dark ? "#FBBF24" : "#3B82F6";
71+
72+
// Nombre de clignotements et intervalle
73+
const flashes = 3;
74+
const interval = 200; // ms
75+
76+
let count = 0;
77+
rect.stroke(color);
78+
rect.opacity(1);
79+
rect.strokeWidth(5);
80+
81+
const blinkInterval = setInterval(() => {
82+
rect.opacity(rect.opacity() === 1 ? 0 : 1);
83+
count++;
84+
if (count >= flashes * 2) {
85+
clearInterval(blinkInterval);
86+
// Reset strokeWidth and opacity
87+
rect.to({ strokeWidth: 0, opacity: 0, duration: 0.5 });
88+
}
89+
}, interval);
7990
};
8091

8192
eventEmitter.on(eventName, handler);

packages/prisma-vs-code-extension/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@
7070
"pretest": "yarn run compile-tests && yarn run build && yarn run lint",
7171
"lint": "eslint src --ext ts",
7272
"test": "vscode-test",
73-
"build:prisma": "vite build && yarn run generate:css",
73+
"build:prisma": "npx vite build && yarn run generate:css",
7474
"package": "yarn run build:prisma",
75-
"preview": "vite preview",
75+
"preview": "npx vite preview",
7676
"create:package": "vsce package",
7777
"publish": "vsce publish",
7878
"generate:css": "cd ../json-table-schema-visualizer && npx tailwindcss -i ./src/styles/index.css > ../prisma-vs-code-extension/dist/webview/assets/index.css --minify"

0 commit comments

Comments
 (0)