Skip to content

Commit 6ad2a76

Browse files
Long Tran (U811370)BOCOVO
authored andcommitted
add: ability to toggle table visualization
1 parent 3819d37 commit 6ad2a76

File tree

14 files changed

+310
-41
lines changed

14 files changed

+310
-41
lines changed

packages/extension-shared/src/hooks/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
22
import { type JSONTableSchema } from "shared/types/tableSchema";
33
import { tableCoordsStore } from "json-table-schema-visualizer/src/stores/tableCoords";
44
import { stageStateStore } from "json-table-schema-visualizer/src/stores/stagesState";
5+
import { detailLevelStore } from "json-table-schema-visualizer/src/stores/detailLevelStore";
56

67
import { type SetSchemaCommandPayload } from "../../extension/types/webviewCommand";
78

@@ -24,6 +25,7 @@ export const useSchema = (): {
2425
// update stores
2526
tableCoordsStore.switchTo(message.key, message.payload.tables);
2627
stageStateStore.switchTo(message.key);
28+
detailLevelStore.switchTo(message.key);
2729

2830
setSchemaKey(message.key);
2931
}

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DiagramWrapper from "./DiagramWrapper";
1212

1313
import TablesPositionsProvider from "@/providers/TablesPositionsProvider";
1414
import MainProviders from "@/providers/MainProviders";
15+
import TabelLevelDetailProvider from "@/providers/TableDetailLevelProvider";
1516

1617
interface DiagramViewerProps {
1718
tables: JSONTableTable[];
@@ -25,15 +26,17 @@ const DiagramViewer = ({ refs, tables, enums }: DiagramViewerProps) => {
2526
}
2627

2728
return (
28-
<TablesPositionsProvider tables={tables}>
29-
<MainProviders tables={tables} enums={enums}>
30-
<DiagramWrapper>
31-
<RelationsConnections refs={refs} />
32-
33-
<Tables tables={tables} />
34-
</DiagramWrapper>
35-
</MainProviders>
36-
</TablesPositionsProvider>
29+
<TabelLevelDetailProvider>
30+
<TablesPositionsProvider tables={tables}>
31+
<MainProviders tables={tables} enums={enums}>
32+
<DiagramWrapper>
33+
<RelationsConnections refs={refs} />
34+
35+
<Tables tables={tables} />
36+
</DiagramWrapper>
37+
</MainProviders>
38+
</TablesPositionsProvider>
39+
</TabelLevelDetailProvider>
3740
);
3841
};
3942

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

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Group, Rect } from "react-konva";
2-
import { useEffect, useRef } from "react";
2+
import { useEffect, useMemo, useRef } from "react";
33

44
import TableHeader from "./TableHeader";
55
import Column from "./Column/Column";
@@ -23,16 +23,22 @@ import {
2323
useTableWidth,
2424
} from "@/hooks/table";
2525
import { tableCoordsStore } from "@/stores/tableCoords";
26+
import { useTableDetailLevel } from "@/hooks/tableDetailLevel";
27+
import { TableDetailLevel } from "@/types/tabelDetailLevel";
28+
import { filterByDetailLevel } from "@/utils/filterByDetailLevel";
2629

2730
interface TableProps extends JSONTableTable {}
2831

2932
const Table = ({ fields, name }: TableProps) => {
3033
const themeColors = useThemeColors();
34+
const { detailLevel } = useTableDetailLevel();
3135
const tableRef = useRef<null | Konva.Group>(null);
3236
const { setHoveredTableName } = useTablesInfo();
3337
const { x: tableX, y: tableY } = useTableDefaultPosition(name);
3438
const tablePreferredWidth = useTableWidth();
35-
39+
const visibleFields = useMemo(() => {
40+
return filterByDetailLevel(fields, detailLevel);
41+
}, [detailLevel, fields]);
3642
useEffect(() => {
3743
if (tableRef.current != null) {
3844
tableRef.current.x(tableX);
@@ -44,7 +50,7 @@ const Table = ({ fields, name }: TableProps) => {
4450
const tableHeight =
4551
TABLE_COLOR_HEIGHT +
4652
COLUMN_HEIGHT +
47-
fields.length * COLUMN_HEIGHT +
53+
visibleFields.length * COLUMN_HEIGHT +
4854
PADDINGS.sm;
4955

5056
const tableDragEventName = computeTableDragEventName(name);
@@ -96,22 +102,25 @@ const Table = ({ fields, name }: TableProps) => {
96102
/>
97103

98104
<TableHeader title={name} />
99-
100-
<Group y={TABLE_HEADER_HEIGHT}>
101-
{fields.map((field, index) => (
102-
<Column
103-
key={field.name}
104-
colName={field.name}
105-
tableName={name}
106-
isEnum={field.type.is_enum}
107-
type={field.type.type_name}
108-
isPrimaryKey={field.pk}
109-
offsetY={index * COLUMN_HEIGHT}
110-
relationalTables={field.relational_tables}
111-
note={field.note}
112-
/>
113-
))}
114-
</Group>
105+
{detailLevel !== TableDetailLevel.HeaderOnly ? (
106+
<Group y={TABLE_HEADER_HEIGHT}>
107+
{visibleFields.map((field, index) => (
108+
<Column
109+
key={field.name}
110+
colName={field.name}
111+
tableName={name}
112+
isEnum={field.type.is_enum}
113+
type={field.type.type_name}
114+
isPrimaryKey={field.pk}
115+
offsetY={index * COLUMN_HEIGHT}
116+
relationalTables={field.relational_tables}
117+
note={field.note}
118+
/>
119+
))}
120+
</Group>
121+
) : (
122+
<></>
123+
)}
115124
</Group>
116125
);
117126
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import DetailLevelToggle from "./DetailLevelToggle";
2+
3+
import type { Meta, StoryObj } from "@storybook/react";
4+
5+
import TableDetailLevelProvider from "@/providers/TableDetailLevelProvider";
6+
7+
const meta: Meta = {
8+
component: DetailLevelToggle,
9+
title: "components/Toolbar/DetailLevelToggle",
10+
};
11+
12+
export default meta;
13+
14+
type Story = StoryObj<typeof DetailLevelToggle>;
15+
16+
export const DetailLevelToggleStory: Story = {
17+
render: () => <DetailLevelToggle />,
18+
decorators: [
19+
(Story) => (
20+
<TableDetailLevelProvider>
21+
<Story />
22+
</TableDetailLevelProvider>
23+
),
24+
],
25+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { PanelsTopLeftIcon, PanelTopIcon, KeyRoundIcon } from "lucide-react";
2+
import { useMemo } from "react";
3+
4+
import ToolbarButton from "../Button";
5+
6+
import { useTableDetailLevel } from "@/hooks/tableDetailLevel";
7+
import { TableDetailLevel } from "@/types/tabelDetailLevel";
8+
9+
interface DetailLevelToggleProps {
10+
onClick: () => void;
11+
}
12+
13+
const FullDetailLevel = ({ onClick }: DetailLevelToggleProps) => {
14+
return (
15+
<ToolbarButton title="Full Details" onClick={onClick}>
16+
<PanelsTopLeftIcon />
17+
18+
<span className="ml-2">Full Details</span>
19+
</ToolbarButton>
20+
);
21+
};
22+
const HeaderOnlyLevel = ({ onClick }: DetailLevelToggleProps) => {
23+
return (
24+
<ToolbarButton title="Header Only" onClick={onClick}>
25+
<PanelTopIcon />
26+
27+
<span className="ml-2">Header Only</span>
28+
</ToolbarButton>
29+
);
30+
};
31+
const KeyOnlyLevel = ({ onClick }: DetailLevelToggleProps) => {
32+
return (
33+
<ToolbarButton title="Key Only" onClick={onClick}>
34+
<KeyRoundIcon />
35+
36+
<span className="ml-2">Key Only</span>
37+
</ToolbarButton>
38+
);
39+
};
40+
41+
const COMPONENT_MAP = {
42+
[TableDetailLevel.FullDetails]: FullDetailLevel,
43+
[TableDetailLevel.HeaderOnly]: HeaderOnlyLevel,
44+
[TableDetailLevel.KeyOnly]: KeyOnlyLevel,
45+
};
46+
47+
const DetailLevelToggle = () => {
48+
const { detailLevel, next } = useTableDetailLevel();
49+
const Component = useMemo(() => COMPONENT_MAP[detailLevel], [detailLevel]);
50+
51+
return <Component onClick={next} />;
52+
};
53+
54+
export default DetailLevelToggle;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import AutoArrangeTableButton from "./AutoArrage/AutoArrangeTables";
22
import ThemeToggler from "./ThemeToggler/ThemeToggler";
3+
import DetailLevelToggle from "./DetailLevelToggle/DetailLevelToggle";
34

45
const Toolbar = () => {
56
return (
67
<div className="flex absolute [&_svg]:w-5 [&_svg]:h-5 px-6 py-1 bottom-14 text-sm bg-gray-100 dark:bg-gray-700 shadow-lg rounded-2xl">
78
<AutoArrangeTableButton />
8-
9+
<DetailLevelToggle />
910
<hr className="w-px h-6 mx-4 my-1 bg-gray-300" />
1011

1112
<ThemeToggler />

packages/json-table-schema-visualizer/src/hooks/relationConnection.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
22

33
import { useTablesInfo } from "./table";
44
import { useTableWidthStoredValue } from "./tableWidthStore";
5+
import { useTableDetailLevel } from "./tableDetailLevel";
56

67
import type { RelationItem } from "@/types/relation";
78
import type { Position, XYPosition } from "@/types/positions";
@@ -11,6 +12,8 @@ import { computeTableDragEventName } from "@/utils/eventName";
1112
import eventEmitter from "@/events-emitter";
1213
import { computeConnectionHandlePos } from "@/utils/computeConnectionHandlePositions";
1314
import { tableCoordsStore } from "@/stores/tableCoords";
15+
import { TableDetailLevel } from "@/types/tabelDetailLevel";
16+
import { TABLE_HEADER_HEIGHT } from "@/constants/sizing";
1417

1518
interface UseRelationTablesCoordsReturn {
1619
sourceXY: XYPosition;
@@ -55,7 +58,7 @@ export const useRelationsCoords = (
5558

5659
const sourceTableDragEventName = computeTableDragEventName(source?.tableName);
5760
const targetTableDragEventName = computeTableDragEventName(target?.tableName);
58-
61+
const { detailLevel } = useTableDetailLevel();
5962
// update source table coordinates on the source table drag event
6063
useEffect(() => {
6164
const coordsUpdater = (coords: XYPosition): void => {
@@ -92,6 +95,20 @@ export const useRelationsCoords = (
9295

9396
// addition of the coordX to the cordXq to obtain the including
9497
// the real position of the table in the scene
98+
if (detailLevel === TableDetailLevel.HeaderOnly) {
99+
return {
100+
sourcePosition,
101+
targetPosition,
102+
sourceXY: {
103+
x: finalSourceX,
104+
y: sourceTableCoords.y + TABLE_HEADER_HEIGHT / 2,
105+
},
106+
targetXY: {
107+
x: finalTargetX,
108+
y: targetTableCoords.y + TABLE_HEADER_HEIGHT / 2,
109+
},
110+
};
111+
}
95112
const finalSourceY = sourceColY + sourceTableCoords.y;
96113
const finalTargetY = targetColY + targetTableCoords.y;
97114

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useContext } from "react";
2+
3+
import { TableDetailLevelContext } from "@/providers/TableDetailLevelProvider";
4+
import { type TableDetailLevel } from "@/types/tabelDetailLevel";
5+
6+
export const useTableDetailLevel = (): {
7+
detailLevel: TableDetailLevel;
8+
next: () => void;
9+
} => {
10+
const contextValue = useContext(TableDetailLevelContext);
11+
if (contextValue === undefined) {
12+
throw new Error(
13+
"it seem you forgot to wrap your app with TableDetailLevelProvider",
14+
);
15+
}
16+
return contextValue;
17+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
createContext,
3+
useCallback,
4+
useEffect,
5+
useState,
6+
type ReactNode,
7+
} from "react";
8+
9+
import {
10+
type TableDetailLevelValue,
11+
TableDetailLevel,
12+
} from "@/types/tabelDetailLevel";
13+
import { detailLevelStore } from "@/stores/detailLevelStore";
14+
15+
export const TableDetailLevelContext = createContext<TableDetailLevelValue>({
16+
detailLevel: TableDetailLevel.FullDetails,
17+
next() {},
18+
});
19+
20+
interface TabelLevelDetailProviderProps {
21+
children: ReactNode;
22+
level?: TableDetailLevel;
23+
}
24+
25+
const TabelLevelDetailProvider = ({
26+
children,
27+
}: TabelLevelDetailProviderProps) => {
28+
const [state, setState] = useState(detailLevelStore.getCurrentDetailLevel());
29+
useEffect(() => {
30+
if (state !== detailLevelStore.getCurrentDetailLevel()) {
31+
detailLevelStore.set(state);
32+
detailLevelStore.saveCurrentState();
33+
}
34+
}, [state]);
35+
const next = useCallback((): void => {
36+
if (state === TableDetailLevel.FullDetails) {
37+
setState(TableDetailLevel.HeaderOnly);
38+
} else if (state === TableDetailLevel.HeaderOnly) {
39+
setState(TableDetailLevel.KeyOnly);
40+
} else {
41+
setState(TableDetailLevel.FullDetails);
42+
}
43+
}, [state, setState]);
44+
return (
45+
<TableDetailLevelContext.Provider value={{ detailLevel: state, next }}>
46+
{children}
47+
</TableDetailLevelContext.Provider>
48+
);
49+
};
50+
51+
export default TabelLevelDetailProvider;

packages/json-table-schema-visualizer/src/providers/TablesInfoProvider.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { TablesInfoProviderValue } from "@/types/tablesInfoProviderValue";
44
import type { JSONTableTable } from "shared/types/tableSchema";
55

66
import { computeColIndexes } from "@/utils/computeColIndexes";
7+
import { useTableDetailLevel } from "@/hooks/tableDetailLevel";
78

89
export const TablesInfoContext = createContext<
910
TablesInfoProviderValue | undefined
@@ -16,7 +17,8 @@ interface TablesInfoProviderProps {
1617

1718
const TablesInfoProvider = ({ children, tables }: TablesInfoProviderProps) => {
1819
const [hoveredTableName, setHoveredTableName] = useState<string | null>(null);
19-
const colsIndexes = computeColIndexes(tables);
20+
const { detailLevel } = useTableDetailLevel();
21+
const colsIndexes = computeColIndexes(tables, detailLevel);
2022

2123
return (
2224
<TablesInfoContext.Provider

0 commit comments

Comments
 (0)