From 8660e29ff95bba020c89eedc93bd7baeafe506d5 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 28 Apr 2025 23:27:41 +0200 Subject: [PATCH] fixup family tree layout --- apps/app/src/lib/graph/PersonNode.svelte | 30 ++--------- apps/app/src/lib/graph/fetch_family_tree.ts | 12 ++--- apps/app/src/lib/graph/layout.ts | 23 +++++---- apps/app/src/lib/graph/model.ts | 2 +- apps/app/src/routes/+page.server.ts | 4 +- apps/app/src/routes/+page.svelte | 56 +++++++++++++++++++-- 6 files changed, 77 insertions(+), 50 deletions(-) diff --git a/apps/app/src/lib/graph/PersonNode.svelte b/apps/app/src/lib/graph/PersonNode.svelte index 65e0d74..2396c0e 100644 --- a/apps/app/src/lib/graph/PersonNode.svelte +++ b/apps/app/src/lib/graph/PersonNode.svelte @@ -9,43 +9,19 @@ export let id: NodeProps['id']; export let data: NodeProps['data'] & components['schemas']['PersonProperties']; const connection = useConnection(); - let isConnecting = false; - let isTarget = false; - - $: isConnecting = connection.current.fromHandle !== null; - $: isTarget = connection.current.toHandle?.id !== id;
- {#if !isConnecting} - - {/if} - + + +
- {#if isConnecting && isTarget} - - {/if}
- {#if isConnecting && isTarget} - - {/if} Picture of {data.last_name} {data.first_name} { - let newEdge = { data: { ...relationship.properties } } as Edge; - if (relationship.start !== null && relationship.start !== undefined) { - newEdge.source = relationship.start.toString(); + let newEdge = { data: { ...relationship.Props } } as Edge; + newEdge.data!.type = relationship.Type?.toLowerCase(); + if (relationship.StartElementId !== null && relationship.StartElementId !== undefined) { + newEdge.source = relationship.StartElementId; } - if (relationship.end !== null && relationship.end !== undefined) { - newEdge.target = relationship.end.toString(); + if (relationship.EndElementId !== null && relationship.EndElementId !== undefined) { + newEdge.target = relationship.EndElementId; } return newEdge; diff --git a/apps/app/src/lib/graph/layout.ts b/apps/app/src/lib/graph/layout.ts index 4f3c963..46c0ab6 100644 --- a/apps/app/src/lib/graph/layout.ts +++ b/apps/app/src/lib/graph/layout.ts @@ -2,7 +2,6 @@ import dagre from '@dagrejs/dagre'; import type { Layout } from './model'; import type { Edge, Node } from '@xyflow/svelte'; import { Position } from '@xyflow/svelte'; -import PersonNode from './PersonNode.svelte'; export class FamilyTree extends dagre.graphlib.Graph { constructor() { @@ -18,27 +17,25 @@ export class FamilyTree extends dagre.graphlib.Graph { ): Layout { const isHorizontal = direction === 'LR'; this.setGraph({ rankdir: direction }); - + this.setDefaultEdgeLabel(() => ({})) nodes.forEach((node) => { - this.setNode(node.id, { width: nodeWidth, height: nodeHeight}); + this.setNode(node.id, { width: nodeWidth, height: nodeHeight }); }); edges.forEach((edge) => { - if (edge.data?.type === 'child') { + if (edge.data!.type === 'child') { this.setEdge(edge.source, edge.target); } }); dagre.layout(this); + let newEdges: Edge[] = []; edges.forEach((edge) => { - if (edge.data?.type === 'parent' || edge.data?.type === 'sibling') { - this.setEdge(edge.source, edge.target); - } - }); - - edges.forEach((edge) => { + let newEdge = edge; if (edge.data?.type === 'spouse') { + newEdge.style = "dashed; stroke: #000; stroke-width: 2px; color: red;"; + const sourceNode = this.node(edge.source); const targetNode = this.node(edge.target); @@ -80,6 +77,10 @@ export class FamilyTree extends dagre.graphlib.Graph { targetNode.x = desiredX; targetNode.y = sourceNode.y; } + newEdge.type = 'smoothstep' + + + newEdges.push(newEdge); }); const layoutedNodes = nodes.map((node) => { @@ -91,7 +92,7 @@ export class FamilyTree extends dagre.graphlib.Graph { // so it matches the React Flow node anchor point (top left). return { ...node, - type: 'personNode', + type: 'personNode', position: { x: nodeWithPosition.x - nodeWidth / 2, y: nodeWithPosition.y - nodeHeight / 2 diff --git a/apps/app/src/lib/graph/model.ts b/apps/app/src/lib/graph/model.ts index 394e68f..1f5f608 100644 --- a/apps/app/src/lib/graph/model.ts +++ b/apps/app/src/lib/graph/model.ts @@ -1,5 +1,5 @@ import type { Node, Edge, NodeTypes } from '@xyflow/svelte'; -import PersonNode from './PersonNode.svelte'; +import PersonNode from './PersonNode.svelte'; export const nodeTypes: NodeTypes = { personNode: PersonNode }; diff --git a/apps/app/src/routes/+page.server.ts b/apps/app/src/routes/+page.server.ts index c017f1e..7024d20 100644 --- a/apps/app/src/routes/+page.server.ts +++ b/apps/app/src/routes/+page.server.ts @@ -3,6 +3,7 @@ import { parseFamilyTree } from '$lib/graph/fetch_family_tree'; import type { components } from '$lib/api/api.gen'; import type { RequestEvent } from './$types'; import { browser } from '$app/environment'; +import type { Layout } from '$lib/graph/model'; export async function load(event: RequestEvent) { if (event.locals.session === null /*|| event.locals.familytree === nul*/) { @@ -24,7 +25,8 @@ export async function load(event: RequestEvent) { const data = (await response.json()) as components['schemas']['FamilyTree']; - let layout = parseFamilyTree(data) + let layout = parseFamilyTree(data) as Layout & {id: string}; + layout.id = event.locals.session.userId; return layout; } diff --git a/apps/app/src/routes/+page.svelte b/apps/app/src/routes/+page.svelte index 333761c..6efb3e6 100644 --- a/apps/app/src/routes/+page.svelte +++ b/apps/app/src/routes/+page.svelte @@ -26,7 +26,7 @@ import { FamilyTree } from '$lib/graph/layout'; import { tailwindClassToPixels } from '$lib/tailwindSizeToPx'; import type { Layout } from '$lib/graph/model'; - let { data }: { data: Layout } = $props(); + let { data }: { data: Layout & { id: string } } = $props(); let selectedPerson: components['schemas']['PersonProperties'] & { id: number | null } = $state({ id: null @@ -66,7 +66,29 @@ openPersonMenu = undefined; }, deleteNode: () => { - relationshipStart = Number(node.id); + if (Number(data.id) === Number(node.id)) { + relationshipStart = null; + openPersonMenu = undefined; + + return; + } + fetch('/api/person/' + node.id, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.ok) { + nodes = nodes.filter((n) => n.id !== node.id); + edges = edges.filter((e) => e.source !== node.id && e.target !== node.id); + } else { + alert('Error deleting person'); + } + }) + .catch((error) => { + console.error('Error:', error); + }); openPersonMenu = undefined; }, createRelationshipAndNode: () => { @@ -102,13 +124,16 @@ edges = [...edges, ...newEdges]; } - familyTreeDAG.getLayoutedElements( + let newLayout = familyTreeDAG.getLayoutedElements( nodes, edges, tailwindClassToPixels('w-40') || 160, tailwindClassToPixels('h-40') || 160, 'TB' ); + + edges = newLayout.Edges; + nodes = newLayout.Nodes; }; let handleNodeClickFunc = handleNodeClick( @@ -118,8 +143,28 @@ } ) => { openPersonPanel = true; - console.log('person', person); selectedPerson = person; + fetch('/api/person/' + person.id, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.ok) { + return response.json() as Promise; + } else { + alert('Error fetching person data'); + return null; + } + }) + .then((data) => { + if (data) { + selectedPerson = data.Props as components['schemas']['PersonProperties'] & { + id: number | null; + }; + } + }); } ); @@ -161,6 +206,9 @@ {/if} {#if createPerson} { + createPerson = false; + }} {onCreation} closeModal={() => { createPerson = false;