2 Commits

Author SHA1 Message Date
881b8c0d60 Add PDF export
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-26 15:25:53 +02:00
784f7ecb6b Add dark theme support 2023-08-26 15:12:02 +02:00
4 changed files with 99 additions and 43 deletions

View File

@@ -158,11 +158,7 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
{currTab === CurrTab.BasicTree ? ( {currTab === CurrTab.BasicTree ? (
<BasicFamilyTree tree={tree!} depth={currDepth} /> <BasicFamilyTree tree={tree!} depth={currDepth} />
) : currTab === CurrTab.SimpleTree ? ( ) : currTab === CurrTab.SimpleTree ? (
<SimpleFamilyTree <SimpleFamilyTree tree={tree!} depth={currDepth} />
tree={tree!}
isUp={currMode === TreeMode.Ascending}
depth={currDepth}
/>
) : ( ) : (
<ComplexFamilyTree <ComplexFamilyTree
tree={tree!} tree={tree!}

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,18 @@
import { mdiXml } from "@mdi/js";
import Icon from "@mdi/react";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import { IconButton, Tooltip } from "@mui/material";
import jsPDF from "jspdf";
import React from "react"; import React from "react";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { Couple } from "../../api/CoupleApi"; import { Couple } from "../../api/CoupleApi";
import { Member } from "../../api/MemberApi"; import { Member } from "../../api/MemberApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { FamilyTreeNode } from "../../utils/family_tree"; import { FamilyTreeNode } from "../../utils/family_tree";
import { downloadBlob } from "../../utils/files_utils";
import { getTextWidth } from "../../utils/render_utils"; import { getTextWidth } from "../../utils/render_utils";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import "./simpletree.css";
import "./Roboto-normal";
const FACE_WIDTH = 60; const FACE_WIDTH = 60;
const FACE_HEIGHT = 70; const FACE_HEIGHT = 70;
@@ -76,7 +85,7 @@ function center(container_width: number, el_width: number): number {
return Math.floor((container_width - el_width) / 2); return Math.floor((container_width - el_width) / 2);
} }
function buildSimpleDownTreeNode( function buildSimpleTreeNode(
tree: FamilyTreeNode, tree: FamilyTreeNode,
depth: number depth: number
): SimpleTreeNode { ): SimpleTreeNode {
@@ -94,8 +103,7 @@ function buildSimpleDownTreeNode(
const node: SimpleTreeNode = { const node: SimpleTreeNode = {
down: down:
childrenToProcess?.map((c) => buildSimpleDownTreeNode(c, depth - 1)) ?? childrenToProcess?.map((c) => buildSimpleTreeNode(c, depth - 1)) ?? [],
[],
member: tree.member, member: tree.member,
spouse: lastCouple spouse: lastCouple
? { ? {
@@ -131,23 +139,6 @@ function buildSimpleDownTreeNode(
return node; return node;
} }
// TODO : if useless, remove and merge up and down techniques
function buildSimpleUpTreeNode(
tree: FamilyTreeNode,
depth: number
): SimpleTreeNode {
/*if (depth === 0) throw new Error("Too much recursion reached!");
return {
member: tree.member,
children:
depth === 1
? []
: tree.down?.map((c) => buildSimpleUpTreeNode(c, depth - 1)) ?? [],
};*/
return buildSimpleDownTreeNode(tree, depth);
}
/** /**
* Simple family tree * Simple family tree
* *
@@ -156,25 +147,79 @@ function buildSimpleUpTreeNode(
export function SimpleFamilyTree(p: { export function SimpleFamilyTree(p: {
tree: FamilyTreeNode; tree: FamilyTreeNode;
isUp: boolean;
depth: number; depth: number;
}): React.ReactElement { }): React.ReactElement {
const darkTheme = useDarkTheme();
const tree = React.useMemo( const tree = React.useMemo(
() => () => buildSimpleTreeNode(p.tree, p.depth),
p.isUp [p.tree, p.depth]
? buildSimpleUpTreeNode(p.tree, p.depth)
: buildSimpleDownTreeNode(p.tree, p.depth),
[p.tree, p.isUp, p.depth]
); );
const height = p.depth * (CARD_HEIGHT + LEVEL_SPACING) - LEVEL_SPACING; const height = p.depth * (CARD_HEIGHT + LEVEL_SPACING) - LEVEL_SPACING;
console.info(`tree width=${tree.width} height=${height}`); console.info(`tree width=${tree.width} height=${height}`);
const doExport = async (onlySVG: boolean) => {
const el = document.querySelector(".simpletree")!;
const svg = el.outerHTML;
// Download in SVG format
if (onlySVG) {
const blob = new Blob([svg], {
type: "image/svg+xml",
});
downloadBlob(blob, "ArbreGenealogique.svg");
return;
}
const PDF_MARGIN = 10;
// Download in PDF format
const doc = new jsPDF({
orientation: "l",
format: [height + PDF_MARGIN * 2, tree.width + PDF_MARGIN * 2],
});
doc.setFont("Roboto", "normal");
await doc.svg(el, {
x: PDF_MARGIN,
y: PDF_MARGIN,
height: height,
width: tree.width,
});
// Save the created pdf
doc.save("ArbreGenealogique.pdf");
};
const exportPDF = () => doExport(false);
const exportSVG = () => doExport(true);
return ( return (
<div>
<div style={{ textAlign: "right" }}>
<Tooltip title="Exporter le graphique au format PDF">
<IconButton onClick={exportPDF}>
<PictureAsPdfIcon />
</IconButton>
</Tooltip>
<Tooltip title="Exporter le graphique au format SVG">
<IconButton onClick={exportSVG}>
<Icon path={mdiXml} size={1} />
</IconButton>
</Tooltip>
</div>
<TransformWrapper maxScale={15} minScale={0.2}> <TransformWrapper maxScale={15} minScale={0.2}>
<TransformComponent wrapperStyle={{ width: "100%", flex: "1" }}> <TransformComponent wrapperStyle={{ width: "100%", flex: "1" }}>
<svg <svg
className={`simpletree ${
darkTheme.enabled ? "simpletree-dark" : ""
}`}
width={tree.width} width={tree.width}
height={height} height={height}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -183,6 +228,7 @@ export function SimpleFamilyTree(p: {
</svg> </svg>
</TransformComponent> </TransformComponent>
</TransformWrapper> </TransformWrapper>
</div>
); );
} }

View File

@@ -0,0 +1,7 @@
.simpletree-dark path {
stroke: white;
}
.simpletree-dark tspan {
fill: white;
}