From ac7374206db06734ee3b9e57be55f7a67a8a4ac4 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 18:22:28 +0900 Subject: [PATCH 1/3] refactor: implement hidden node state in userEditingStore --- .../ERDRenderer/ERDContent/ERDContent.tsx | 2 ++ .../RelatedTables/RelatedTables.tsx | 22 +++++++++------- .../ERDContent/useSyncHiddenNodesChange.ts | 25 +++++++++++++++++++ .../TableNameMenuButton/VisibilityButton.tsx | 8 +++--- .../src/stores/userEditing/actions.ts | 19 ++++++++++++++ .../erd-core/src/stores/userEditing/store.ts | 3 +++ 6 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useSyncHiddenNodesChange.ts diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/ERDContent.tsx b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/ERDContent.tsx index 199c961f8..b60db1917 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/ERDContent.tsx +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/ERDContent.tsx @@ -20,6 +20,7 @@ import { TableNode } from './TableNode' import { highlightNodesAndEdges } from './highlightNodesAndEdges' import { useFitViewWhenActiveTableChange } from './useFitViewWhenActiveTableChange' import { useInitialAutoLayout } from './useInitialAutoLayout' +import { useSyncHiddenNodesChange } from './useSyncHiddenNodesChange' import { useSyncHighlightsActiveTableChange } from './useSyncHighlightsActiveTableChange' const nodeTypes = { @@ -74,6 +75,7 @@ export const ERDContentInner: FC = ({ enabledFeatures?.fitViewWhenActiveTableChange ?? true, ) useSyncHighlightsActiveTableChange() + useSyncHiddenNodesChange() const handleNodeClick = useCallback((nodeId: string) => { updateActiveTableName(nodeId) diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/TableNode/TableDetail/RelatedTables/RelatedTables.tsx b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/TableNode/TableDetail/RelatedTables/RelatedTables.tsx index 1ba1a0f3d..0dc6e856a 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/TableNode/TableDetail/RelatedTables/RelatedTables.tsx +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/TableNode/TableDetail/RelatedTables/RelatedTables.tsx @@ -1,5 +1,9 @@ import { convertDBStructureToNodes } from '@/components/ERDRenderer/convertDBStructureToNodes' -import { updateActiveTableName, useDBStructureStore } from '@/stores' +import { + replaceHiddenNodeIds, + updateActiveTableName, + useDBStructureStore, +} from '@/stores' import type { Table } from '@liam-hq/db-structure' import { GotoIcon, IconButton } from '@liam-hq/ui' import { ReactFlowProvider, useReactFlow } from '@xyflow/react' @@ -19,17 +23,17 @@ export const RelatedTables: FC = ({ table }) => { dbStructure: extractedDBStructure, showMode: 'TABLE_NAME', }) - const { setNodes } = useReactFlow() + const { getNodes } = useReactFlow() const handleClick = useCallback(() => { const visibleNodeIds: string[] = nodes.map((node) => node.id) - setNodes((mainPaneNodes) => { - return mainPaneNodes.map((node) => ({ - ...node, - hidden: !visibleNodeIds.includes(node.id), - })) - }) + const mainPaneNodes = getNodes() + const hiddenNodeIds = mainPaneNodes + .filter((node) => !visibleNodeIds.includes(node.id)) + .map((node) => node.id) + + replaceHiddenNodeIds(hiddenNodeIds) updateActiveTableName(undefined) - }, [nodes, setNodes]) + }, [nodes, getNodes]) return (
diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useSyncHiddenNodesChange.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useSyncHiddenNodesChange.ts new file mode 100644 index 000000000..1ed7e3dd0 --- /dev/null +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useSyncHiddenNodesChange.ts @@ -0,0 +1,25 @@ +import { useUserEditingStore } from '@/stores' +import { useReactFlow } from '@xyflow/react' +import { useEffect } from 'react' +import { useERDContentContext } from './ERDContentContext' + +export const useSyncHiddenNodesChange = () => { + const { + state: { initializeComplete }, + } = useERDContentContext() + const { getNodes, setNodes } = useReactFlow() + const { hiddenNodeIds } = useUserEditingStore() + + useEffect(() => { + if (!initializeComplete) { + return + } + const nodes = getNodes() + const updatedNodes = nodes.map((node) => { + const hidden = hiddenNodeIds.has(node.id) + return { ...node, hidden } + }) + + setNodes(updatedNodes) + }, [initializeComplete, getNodes, setNodes, hiddenNodeIds]) +} diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/LeftPane/TableNameMenuButton/VisibilityButton.tsx b/frontend/packages/erd-core/src/components/ERDRenderer/LeftPane/TableNameMenuButton/VisibilityButton.tsx index 8748ebd33..32655b839 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/LeftPane/TableNameMenuButton/VisibilityButton.tsx +++ b/frontend/packages/erd-core/src/components/ERDRenderer/LeftPane/TableNameMenuButton/VisibilityButton.tsx @@ -1,5 +1,5 @@ +import { toggleHiddenNodeId } from '@/stores' import { Eye, EyeClosed, SidebarMenuAction } from '@liam-hq/ui' -import { useReactFlow } from '@xyflow/react' import { type FC, type MouseEvent, useCallback } from 'react' import styles from './VisibilityButton.module.css' @@ -9,14 +9,12 @@ type Props = { } export const VisibilityButton: FC = ({ tableName, hidden }) => { - const { updateNode } = useReactFlow() - const handleClick = useCallback( (event: MouseEvent) => { event.stopPropagation() - updateNode(tableName, (node) => ({ ...node, hidden: !node.hidden })) + toggleHiddenNodeId(tableName) }, - [updateNode, tableName], + [tableName], ) return ( diff --git a/frontend/packages/erd-core/src/stores/userEditing/actions.ts b/frontend/packages/erd-core/src/stores/userEditing/actions.ts index 628671ff8..4656a6086 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/actions.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/actions.ts @@ -8,3 +8,22 @@ export const updateActiveTableName = (tableName: string | undefined) => { export const updateShowMode = (showMode: ShowMode) => { userEditingStore.showMode = showMode } + +export const toggleHiddenNodeId = (nodeId: string) => { + if (userEditingStore.hiddenNodeIds.has(nodeId)) { + userEditingStore.hiddenNodeIds.delete(nodeId) + } else { + userEditingStore.hiddenNodeIds.add(nodeId) + } +} + +const addHiddenNodeIds = (nodeIds: string[]) => { + for (const id of nodeIds) { + userEditingStore.hiddenNodeIds.add(id) + } +} + +export const replaceHiddenNodeIds = (nodeIds: string[]) => { + userEditingStore.hiddenNodeIds.clear() + addHiddenNodeIds(nodeIds) +} diff --git a/frontend/packages/erd-core/src/stores/userEditing/store.ts b/frontend/packages/erd-core/src/stores/userEditing/store.ts index d16e5a11b..63003715c 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/store.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/store.ts @@ -1,12 +1,14 @@ import type { QueryParam } from '@/schemas/queryParam' import type { ShowMode } from '@/schemas/showMode' import { proxy, subscribe } from 'valtio' +import { proxySet } from 'valtio/utils' type UserEditingStore = { active: { tableName: string | undefined } showMode: ShowMode + hiddenNodeIds: Set } export const userEditingStore = proxy({ @@ -14,6 +16,7 @@ export const userEditingStore = proxy({ tableName: undefined, }, showMode: 'TABLE_NAME', + hiddenNodeIds: proxySet(), }) subscribe(userEditingStore.active, () => { From 7497c2d494ee7ea4568d17654194939c6e2f58ef Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 18:46:23 +0900 Subject: [PATCH 2/3] feat: add support for hidden nodes in auto layout and URL query parameters --- .../ERDContent/useAutoLayout/useAutoLayout.ts | 17 ++++++++++++++--- .../ERDContent/useInitialAutoLayout.ts | 15 +++++++++++++-- .../erd-core/src/schemas/queryParam/schemas.ts | 2 +- .../erd-core/src/stores/userEditing/actions.ts | 2 +- .../erd-core/src/stores/userEditing/store.ts | 13 +++++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useAutoLayout/useAutoLayout.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useAutoLayout/useAutoLayout.ts index b7a796873..73c5fa947 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useAutoLayout/useAutoLayout.ts +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useAutoLayout/useAutoLayout.ts @@ -11,12 +11,23 @@ export const useAutoLayout = () => { } = useERDContentContext() const handleLayout = useCallback( - async (fitViewOptions?: FitViewOptions) => { + async ( + fitViewOptions: FitViewOptions = {}, + initialHiddenNodeIds: string[] = [], + ) => { setLoading(true) const nodes = getNodes() const edges = getEdges() - const visibleNodes: Node[] = nodes.filter((node) => !node.hidden) - const hiddenNodes: Node[] = nodes.filter((node) => node.hidden) + + const hiddenNodes: Node[] = [] + const visibleNodes: Node[] = [] + for (const node of nodes) { + if (initialHiddenNodeIds.includes(node.id) || node.hidden) { + hiddenNodes.push({ ...node, hidden: true }) + } else { + visibleNodes.push(node) + } + } // NOTE: Only include edges where both the source and target are in the nodes const nodeMap = new Map(visibleNodes.map((node) => [node.id, node])) diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts index 752025a65..86e503756 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts @@ -1,5 +1,5 @@ import type { QueryParam } from '@/schemas/queryParam' -import { updateActiveTableName } from '@/stores' +import { addHiddenNodeIds, updateActiveTableName } from '@/stores' import { useNodesInitialized } from '@xyflow/react' import { useEffect } from 'react' import { useERDContentContext } from './ERDContentContext' @@ -13,6 +13,14 @@ const getActiveTableNameFromUrl = (): string | undefined => { return tableName || undefined } +const getHiddenNodeIdsFromUrl = (): string[] => { + const urlParams = new URLSearchParams(window.location.search) + const hiddenQueryParam: QueryParam = 'hidden' + const hiddenNodeIds = urlParams.get(hiddenQueryParam) + + return hiddenNodeIds ? hiddenNodeIds.split(',') : [] +} + export const useInitialAutoLayout = () => { const nodesInitialized = useNodesInitialized() const { @@ -27,12 +35,15 @@ export const useInitialAutoLayout = () => { const tableNameFromUrl = getActiveTableNameFromUrl() updateActiveTableName(tableNameFromUrl) + const hiddenNodeIds = getHiddenNodeIdsFromUrl() + addHiddenNodeIds(hiddenNodeIds) + const fitViewOptions = tableNameFromUrl ? { maxZoom: 1, duration: 300, nodes: [{ id: tableNameFromUrl }] } : undefined if (nodesInitialized) { - handleLayout(fitViewOptions) + handleLayout(fitViewOptions, hiddenNodeIds) } }, [nodesInitialized, initializeComplete, handleLayout]) } diff --git a/frontend/packages/erd-core/src/schemas/queryParam/schemas.ts b/frontend/packages/erd-core/src/schemas/queryParam/schemas.ts index ff709c5dd..2770ca056 100644 --- a/frontend/packages/erd-core/src/schemas/queryParam/schemas.ts +++ b/frontend/packages/erd-core/src/schemas/queryParam/schemas.ts @@ -1,3 +1,3 @@ import { picklist } from 'valibot' -export const queryParamSchema = picklist(['active']) +export const queryParamSchema = picklist(['active', 'hidden']) diff --git a/frontend/packages/erd-core/src/stores/userEditing/actions.ts b/frontend/packages/erd-core/src/stores/userEditing/actions.ts index 4656a6086..8e1d5d56f 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/actions.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/actions.ts @@ -17,7 +17,7 @@ export const toggleHiddenNodeId = (nodeId: string) => { } } -const addHiddenNodeIds = (nodeIds: string[]) => { +export const addHiddenNodeIds = (nodeIds: string[]) => { for (const id of nodeIds) { userEditingStore.hiddenNodeIds.add(id) } diff --git a/frontend/packages/erd-core/src/stores/userEditing/store.ts b/frontend/packages/erd-core/src/stores/userEditing/store.ts index 63003715c..8f8d547ce 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/store.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/store.ts @@ -32,3 +32,16 @@ subscribe(userEditingStore.active, () => { window.history.pushState({}, '', url) }) + +subscribe(userEditingStore.hiddenNodeIds, () => { + const url = new URL(window.location.href) + const activeQueryParam: QueryParam = 'hidden' + const hiddenNodeIds = Array.from(userEditingStore.hiddenNodeIds).join(',') + + url.searchParams.delete(activeQueryParam) + if (hiddenNodeIds) { + url.searchParams.set(activeQueryParam, hiddenNodeIds) + } + + window.history.pushState({}, '', url) +}) From 555a1575a11f6f325213b3ce2ac52e24065ec3e3 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 18:47:14 +0900 Subject: [PATCH 3/3] maintenance: add changeset --- frontend/.changeset/empty-parents-knock.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 frontend/.changeset/empty-parents-knock.md diff --git a/frontend/.changeset/empty-parents-knock.md b/frontend/.changeset/empty-parents-knock.md new file mode 100644 index 000000000..48bf6c7dd --- /dev/null +++ b/frontend/.changeset/empty-parents-knock.md @@ -0,0 +1,6 @@ +--- +"@liam-hq/erd-core": patch +"@liam-hq/cli": patch +--- + +feat: hidden nodes can now be reflected from query parameters