mirror of
https://github.com/vcscsvcscs/GenerationsHeritage.git
synced 2025-08-12 13:59:08 +02:00
fixup family tree layout
This commit is contained in:
@@ -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;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="card card-compact bg-primary-content text-primary flex h-40 w-40 flex-col items-center justify-center rounded-full shadow-lg"
|
||||
>
|
||||
{#if !isConnecting}
|
||||
<Handle class="customHandle" position={Position.Right} type="source" style="z-index: 1;" />
|
||||
{/if}
|
||||
<Handle class="customHandle" position={Position.Left} type="target" isConnectableStart={false} />
|
||||
<Handle class="customHandle" isValidConnection={isValidConnection} position={Position.Bottom} type="source" style="z-index: 1;" />
|
||||
|
||||
<Handle class="customHandle" isValidConnection={isValidConnection} position={Position.Top} type="target" isConnectableStart={false} />
|
||||
|
||||
<div class="avatar mb-2">
|
||||
{#if isConnecting && isTarget}
|
||||
<Handle
|
||||
{isValidConnection}
|
||||
position={Position.Left}
|
||||
type="target"
|
||||
isConnectableStart={false}
|
||||
style="z-index: 1;"
|
||||
/>
|
||||
{/if}
|
||||
<div
|
||||
class="ring-accent ring-offset-accent bg-accent w-24 rounded-full border-0 ring ring-offset-1"
|
||||
>
|
||||
{#if isConnecting && isTarget}
|
||||
<Handle
|
||||
{isValidConnection}
|
||||
position={Position.Left}
|
||||
type="target"
|
||||
isConnectableStart={false}
|
||||
style="z-index: 1;"
|
||||
/>
|
||||
{/if}
|
||||
<img
|
||||
src={data.profile_picture || 'https://cdn-icons-png.flaticon.com/512/10628/10628885.png'}
|
||||
alt="Picture of {data.last_name} {data.first_name}"
|
||||
|
@@ -3,7 +3,6 @@ import type { Layout } from '$lib/graph/model';
|
||||
import type { Edge, Node } from '@xyflow/svelte';
|
||||
|
||||
export function parseFamilyTree(data: components['schemas']['FamilyTree']): Layout {
|
||||
|
||||
if (
|
||||
data === null ||
|
||||
data?.people === null ||
|
||||
@@ -25,12 +24,13 @@ export function parseFamilyTree(data: components['schemas']['FamilyTree']): Layo
|
||||
let relationships: Edge[] = [];
|
||||
if (data.relationships) {
|
||||
relationships = data.relationships.map((relationship) => {
|
||||
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;
|
||||
|
@@ -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
|
||||
|
@@ -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 };
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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<components['schemas']['Person']>;
|
||||
} 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
|
||||
onOnlyPersonCreation={() => {
|
||||
createPerson = false;
|
||||
}}
|
||||
{onCreation}
|
||||
closeModal={() => {
|
||||
createPerson = false;
|
||||
|
Reference in New Issue
Block a user