mirror of
https://github.com/vcscsvcscs/GenerationsHeritage.git
synced 2025-08-12 13:59:08 +02:00
create person and relation ship on edge drop
This commit is contained in:
74
apps/app/src/lib/graph/FamilyEdge.svelte
Normal file
74
apps/app/src/lib/graph/FamilyEdge.svelte
Normal file
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import type { components } from '$lib/api/api.gen.ts';
|
||||
import { child, spouse, parent, sibling } from '$lib/paraglide/messages';
|
||||
import { getSmoothStepPath, BaseEdge, EdgeLabelRenderer, type EdgeProps } from '@xyflow/svelte';
|
||||
|
||||
let {
|
||||
sourceX,
|
||||
sourceY,
|
||||
source,
|
||||
sourcePosition,
|
||||
target,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
markerEnd,
|
||||
style,
|
||||
data
|
||||
}: EdgeProps = $props();
|
||||
|
||||
let [edgePath, labelX, labelY] = $derived(
|
||||
getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition
|
||||
})
|
||||
);
|
||||
|
||||
const onEdgeClick = () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('edge-click', {
|
||||
detail: {
|
||||
start: source,
|
||||
end: target,
|
||||
data: data as components['schemas']['FamilyRelationship'] & { type: string }
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
let edgeType = (
|
||||
data as components['schemas']['FamilyRelationship'] & { type: string }
|
||||
).type.toLowerCase();
|
||||
let edgeLabel: string = $state(edgeType);
|
||||
let edgeColor: string;
|
||||
if (edgeType === 'spouse') {
|
||||
edgeColor = 'stroke: red;';
|
||||
edgeLabel = spouse();
|
||||
} else if (edgeType === 'child') {
|
||||
edgeColor = 'stroke: blue;';
|
||||
edgeLabel = child();
|
||||
} else if (edgeType === 'parent') {
|
||||
edgeColor = 'stroke: green;';
|
||||
edgeLabel = parent();
|
||||
} else if (edgeType === 'sibling') {
|
||||
edgeColor = 'stroke: brown;';
|
||||
edgeLabel = sibling();
|
||||
} else {
|
||||
edgeColor = 'stroke: gray;';
|
||||
edgeLabel = edgeType;
|
||||
}
|
||||
</script>
|
||||
|
||||
<BaseEdge path={edgePath} {markerEnd} {style} />
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
class="button-edge__label nodrag nopan"
|
||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
||||
>
|
||||
<button class="button-edge__button" onclick={onEdgeClick}>{edgeLabel}</button>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
@@ -7,7 +7,6 @@
|
||||
type $$Props = NodeProps;
|
||||
|
||||
export let data: NodeProps['data'] & components['schemas']['PersonProperties'];
|
||||
const connection = useConnection();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -16,6 +15,7 @@
|
||||
<Handle
|
||||
class="customHandle"
|
||||
{isValidConnection}
|
||||
isConnectable={true}
|
||||
position={Position.Bottom}
|
||||
type="source"
|
||||
style="z-index: 1;"
|
||||
@@ -25,11 +25,12 @@
|
||||
class="customHandle"
|
||||
{isValidConnection}
|
||||
position={Position.Top}
|
||||
isConnectable={true}
|
||||
type="target"
|
||||
isConnectableStart={false}
|
||||
/>
|
||||
|
||||
<div class="avatar mb-2">
|
||||
<div class="avatar mb-2" style="z-index: 2; cursor: pointer;">
|
||||
<div
|
||||
class="ring-accent ring-offset-accent bg-accent w-24 rounded-full border-0 ring ring-offset-1"
|
||||
>
|
||||
|
@@ -34,8 +34,6 @@ export class FamilyTree extends dagre.graphlib.Graph {
|
||||
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);
|
||||
if (!sourceNode || !targetNode) {
|
||||
@@ -76,7 +74,7 @@ export class FamilyTree extends dagre.graphlib.Graph {
|
||||
targetNode.x = desiredX;
|
||||
targetNode.y = sourceNode.y;
|
||||
}
|
||||
newEdge.type = 'smoothstep';
|
||||
newEdge.type = 'familyEdge';
|
||||
|
||||
newEdges.push(newEdge);
|
||||
});
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import type { Node, Edge, NodeTypes } from '@xyflow/svelte';
|
||||
import type { Node, Edge, NodeTypes, EdgeTypes } from '@xyflow/svelte';
|
||||
import FamilyEdge from './FamilyEdge.svelte';
|
||||
import PersonNode from './PersonNode.svelte';
|
||||
|
||||
export const nodeTypes: NodeTypes = { personNode: PersonNode };
|
||||
export const edgeTypes: EdgeTypes = {
|
||||
familyEdge: FamilyEdge
|
||||
};
|
||||
|
||||
export type NodeMenu = {
|
||||
onClick: () => void;
|
||||
|
@@ -35,7 +35,7 @@
|
||||
closeModal = () => {},
|
||||
onCreation = (nodes: Array<Node> | null, edges: Array<Edge> | null) => {},
|
||||
onOnlyPersonCreation = (person: components['schemas']['Person']) => {},
|
||||
relationshipStartID = null
|
||||
relationshipStartID
|
||||
}: {
|
||||
closeModal: () => void;
|
||||
onCreation: (newNodes: Array<Node> | null, newEdges: Array<Edge> | null) => void;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { nodeTypes } from '$lib/graph/model';
|
||||
import { nodeTypes, edgeTypes } from '$lib/graph/model';
|
||||
import { title, family_tree } from '$lib/paraglide/messages.js';
|
||||
|
||||
import {
|
||||
@@ -7,10 +7,10 @@
|
||||
SvelteFlow,
|
||||
Controls,
|
||||
MiniMap,
|
||||
ConnectionLineType
|
||||
ConnectionLineType,
|
||||
} from '@xyflow/svelte';
|
||||
import '@xyflow/svelte/dist/style.css';
|
||||
import type { Node, Edge, NodeEventWithPointer } from '@xyflow/svelte';
|
||||
import type {OnConnectEnd, Node, Edge, NodeEventWithPointer } from '@xyflow/svelte';
|
||||
|
||||
import PersonModal from '$lib/profile/Modal.svelte';
|
||||
import PersonMenu from '$lib/graph/PersonMenu.svelte';
|
||||
@@ -173,8 +173,17 @@
|
||||
let handlePaneClick = ({ event }: { event: MouseEvent }) => {
|
||||
openPersonPanel = false;
|
||||
openPersonMenu = undefined;
|
||||
relationshipStart = null;
|
||||
};
|
||||
|
||||
const handleConnectEnd: OnConnectEnd = (event, connectionState) => {
|
||||
if (connectionState.isValid) return;
|
||||
const sourceNodeId = connectionState.fromNode?.id
|
||||
if (sourceNodeId === undefined) return;
|
||||
relationshipStart = Number(sourceNodeId);
|
||||
createPerson = true;
|
||||
console.log('createPerson', createPerson);
|
||||
console.log('relationshipStart', relationshipStart);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -186,11 +195,13 @@
|
||||
<SvelteFlow
|
||||
bind:nodes
|
||||
bind:edges
|
||||
onconnectend={handleConnectEnd}
|
||||
onnodeclick={handleNodeClickFunc}
|
||||
onnodecontextmenu={handleContextMenu}
|
||||
onpaneclick={handlePaneClick}
|
||||
class="!bg-base-200"
|
||||
{nodeTypes}
|
||||
{edgeTypes}
|
||||
fitView
|
||||
onlyRenderVisibleElements
|
||||
connectionLineType={ConnectionLineType.SmoothStep}
|
||||
|
@@ -29,6 +29,12 @@ export const load: PageServerLoad = async (event: RequestEvent) => {
|
||||
return {};
|
||||
}
|
||||
|
||||
let already_loaded = event.cookies.get('already_loaded') ?? null;
|
||||
if (already_loaded !== null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
const storedState = event.cookies.get('google_oauth_state') ?? null;
|
||||
const codeVerifier = event.cookies.get('google_code_verifier') ?? null;
|
||||
const code = event.url.searchParams.get('code');
|
||||
@@ -95,6 +101,14 @@ export const load: PageServerLoad = async (event: RequestEvent) => {
|
||||
email: email
|
||||
};
|
||||
|
||||
event.cookies.set('already_loaded', 'true',{
|
||||
path: '/login/google/callback',
|
||||
sameSite: 'lax',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10,
|
||||
secure: import.meta.env.PROD,
|
||||
})
|
||||
|
||||
return {
|
||||
props: personP
|
||||
};
|
||||
@@ -110,13 +124,26 @@ async function register(event: RequestEvent) {
|
||||
}
|
||||
|
||||
const data = await event.request.formData();
|
||||
let parsedData: components['schemas']['PersonRegistration'] = {
|
||||
first_name: data.get('first_name'),
|
||||
last_name: data.get('last_name'),
|
||||
email: data.get('email'),
|
||||
biological_sex: data.get('biological_sex'),
|
||||
born: data.get('birth_date'),
|
||||
mothers_first_name: data.get('mothers_first_name'),
|
||||
mothers_last_name: data.get('mothers_last_name'),
|
||||
google_id: data.get('google_id'),
|
||||
limit: StorageLimit,
|
||||
} as components['schemas']['PersonRegistration'];
|
||||
|
||||
if (!event.platform || !event.platform.env || !event.platform.env.GH_SESSIONS) {
|
||||
return fail(500, { message: 'Server configuration error. GH_SESSIONS KeyValue store missing' });
|
||||
return fail(500, { data: parsedData, message: 'Server configuration error. GH_SESSIONS KeyValue store missing' });
|
||||
}
|
||||
|
||||
const first_name_f = data.get('first_name');
|
||||
if (first_name_f === null || first_name_f === '') {
|
||||
return fail(400, {
|
||||
data:parsedData,
|
||||
message: missing_field({
|
||||
field: first_name()
|
||||
})
|
||||
@@ -126,6 +153,7 @@ async function register(event: RequestEvent) {
|
||||
const google_id = data.get('google_id');
|
||||
if (google_id === null || google_id === '') {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: missing_field({
|
||||
field: 'google_id'
|
||||
})
|
||||
@@ -135,6 +163,7 @@ async function register(event: RequestEvent) {
|
||||
const last_name_f = data.get('last_name');
|
||||
if (last_name_f === null || last_name_f === '') {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: missing_field({
|
||||
field: last_name()
|
||||
})
|
||||
@@ -161,6 +190,7 @@ async function register(event: RequestEvent) {
|
||||
const bbiological_sex = data.get('biological_sex');
|
||||
if (bbiological_sex === null || bbiological_sex === '') {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: missing_field({
|
||||
field: biological_sex()
|
||||
})
|
||||
@@ -169,6 +199,7 @@ async function register(event: RequestEvent) {
|
||||
!['male', 'female', 'intersex', 'unknown', 'other'].includes(bbiological_sex.toString())
|
||||
) {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: `Invalid value for biological_sex. Must be one of "male", "female", "intersex", "unknown", or "other".`
|
||||
});
|
||||
}
|
||||
@@ -176,6 +207,7 @@ async function register(event: RequestEvent) {
|
||||
const mothers_first_name_f = data.get('mothers_first_name');
|
||||
if (mothers_first_name_f === null || mothers_first_name_f === '') {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: missing_field({
|
||||
field: mothers_first_name()
|
||||
})
|
||||
@@ -184,6 +216,7 @@ async function register(event: RequestEvent) {
|
||||
const mothers_last_name_f = data.get('mothers_last_name');
|
||||
if (mothers_last_name_f === null) {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: missing_field({
|
||||
field: mothers_last_name()
|
||||
})
|
||||
@@ -205,6 +238,7 @@ async function register(event: RequestEvent) {
|
||||
|
||||
let response = await client.POST('/person/google/{google_id}', {
|
||||
params: {
|
||||
data: parsedData,
|
||||
path: { google_id: google_id.toString() }
|
||||
},
|
||||
body: personP
|
||||
@@ -212,24 +246,28 @@ async function register(event: RequestEvent) {
|
||||
|
||||
if (response.response.status !== 200) {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: failed_to_create_user() + response.error?.msg
|
||||
});
|
||||
}
|
||||
|
||||
if (response.data === undefined) {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: failed_to_create_user() + 'No user data returned'
|
||||
});
|
||||
}
|
||||
|
||||
if (response.data.Id === undefined) {
|
||||
return fail(400, {
|
||||
data: parsedData,
|
||||
message: failed_to_create_user() + 'No user ID returned'
|
||||
});
|
||||
}
|
||||
|
||||
if (!event.platform) {
|
||||
return fail(500, {
|
||||
data: parsedData,
|
||||
message: 'Server configuration error. GH_SESSIONS KeyValue store missing'
|
||||
});
|
||||
}
|
||||
@@ -242,11 +280,21 @@ async function register(event: RequestEvent) {
|
||||
);
|
||||
if (session === null) {
|
||||
return fail(500, {
|
||||
data: parsedData,
|
||||
message: failed_to_create_user() + 'Failed to create session'
|
||||
});
|
||||
}
|
||||
|
||||
setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
||||
|
||||
event.cookies.delete('already_loaded',
|
||||
{
|
||||
path: '/login/google/callback',
|
||||
sameSite: 'lax',
|
||||
httpOnly: true,
|
||||
maxAge: 0,
|
||||
secure: import.meta.env.PROD
|
||||
}
|
||||
);
|
||||
return redirect(302, '/');
|
||||
}
|
||||
|
Reference in New Issue
Block a user