|
|
|
|
@@ -1,15 +1,17 @@
|
|
|
|
|
import React from "react";
|
|
|
|
|
import f3, { f3Data } from "family-chart";
|
|
|
|
|
import "./family-chart.css";
|
|
|
|
|
import { FamilyTreeNode } from "../../utils/family_tree";
|
|
|
|
|
import { Member, fmtDate } from "../../api/MemberApi";
|
|
|
|
|
import { Couple } from "../../api/CoupleApi";
|
|
|
|
|
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
|
|
|
|
|
import { IconButton } from "@mui/material";
|
|
|
|
|
import { mdiXml } from "@mdi/js";
|
|
|
|
|
import Icon from "@mdi/react";
|
|
|
|
|
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
|
|
|
|
import { IconButton } from "@mui/material";
|
|
|
|
|
import f3, { f3Data } from "family-chart";
|
|
|
|
|
import { jsPDF } from "jspdf";
|
|
|
|
|
import React from "react";
|
|
|
|
|
import "svg2pdf.js";
|
|
|
|
|
import { getAllIndexes } from "../../utils/string_utils";
|
|
|
|
|
import { Couple } from "../../api/CoupleApi";
|
|
|
|
|
import { Member, fmtDate } from "../../api/MemberApi";
|
|
|
|
|
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
|
|
|
|
|
import { FamilyTreeNode } from "../../utils/family_tree";
|
|
|
|
|
import { downloadBlob } from "../../utils/files_utils";
|
|
|
|
|
import "./family-chart.css";
|
|
|
|
|
|
|
|
|
|
export function ComplexFamilyTree(p: {
|
|
|
|
|
tree: FamilyTreeNode;
|
|
|
|
|
@@ -24,21 +26,21 @@ export function ComplexFamilyTree(p: {
|
|
|
|
|
data: treeToF3Data(p.tree, p.isUp),
|
|
|
|
|
node_separation: 250,
|
|
|
|
|
level_separation: 150,
|
|
|
|
|
}),
|
|
|
|
|
view = f3.d3AnimationView({
|
|
|
|
|
});
|
|
|
|
|
const view = f3.d3AnimationView({
|
|
|
|
|
store,
|
|
|
|
|
cont: container,
|
|
|
|
|
}),
|
|
|
|
|
Card = f3.elements.Card({
|
|
|
|
|
});
|
|
|
|
|
const Card = f3.elements.Card({
|
|
|
|
|
store,
|
|
|
|
|
svg: view.svg,
|
|
|
|
|
card_dim: {
|
|
|
|
|
w: 210,
|
|
|
|
|
h: 118,
|
|
|
|
|
h: 120,
|
|
|
|
|
text_x: 5,
|
|
|
|
|
text_y: 70,
|
|
|
|
|
text_y: 75,
|
|
|
|
|
img_w: 60,
|
|
|
|
|
img_h: 60,
|
|
|
|
|
img_h: 70,
|
|
|
|
|
img_x: 5,
|
|
|
|
|
img_y: 5,
|
|
|
|
|
},
|
|
|
|
|
@@ -71,21 +73,33 @@ export function ComplexFamilyTree(p: {
|
|
|
|
|
link_break: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
view.setCard(Card);
|
|
|
|
|
// Patch generated card
|
|
|
|
|
const PatchedCard: f3.F3CardBuilder = (p) => {
|
|
|
|
|
const res = Card(p);
|
|
|
|
|
|
|
|
|
|
// Patch card colors for PDF export
|
|
|
|
|
res
|
|
|
|
|
.querySelector(".card-male")
|
|
|
|
|
?.querySelector(".card-body-rect")
|
|
|
|
|
?.setAttribute("fill", "#add8e6");
|
|
|
|
|
res
|
|
|
|
|
.querySelector(".card-female")
|
|
|
|
|
?.querySelector(".card-body-rect")
|
|
|
|
|
?.setAttribute("fill", "#ffb6c1");
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
view.setCard(PatchedCard);
|
|
|
|
|
store.setOnUpdate((props) => view.update(props || {}));
|
|
|
|
|
store.update.tree({ initial: false, transition_time: 0 });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const exportPDF = async () => {
|
|
|
|
|
const doExport = async (onlySVG: boolean) => {
|
|
|
|
|
const docWidth = treeWidth(p.tree) * 65;
|
|
|
|
|
const docHeight = treeHeight(p.tree) * 60;
|
|
|
|
|
console.info(`Tree w=${treeWidth(p.tree)} h=${treeHeight(p.tree)}`);
|
|
|
|
|
|
|
|
|
|
const doc = new jsPDF({
|
|
|
|
|
orientation: "l",
|
|
|
|
|
format: [docHeight, docWidth],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Clone the SVG to manipulate it
|
|
|
|
|
const container = document.createElement("div");
|
|
|
|
|
container.classList.add("f3", "f3-export");
|
|
|
|
|
@@ -104,11 +118,6 @@ export function ComplexFamilyTree(p: {
|
|
|
|
|
`<path class="link" fill="none" stroke="#000"`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
dstSVG = dstSVG.replaceAll(
|
|
|
|
|
`class="card-body-rect"`,
|
|
|
|
|
`class="card-body-rect" fill="white"`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
dstSVG = dstSVG.replaceAll(
|
|
|
|
|
`class="text-overflow-mask"`,
|
|
|
|
|
`class="text-overflow-mask" fill="transparent"`
|
|
|
|
|
@@ -123,29 +132,32 @@ export function ComplexFamilyTree(p: {
|
|
|
|
|
|
|
|
|
|
dstSVG = dstSVG.replaceAll("✝", " ");
|
|
|
|
|
|
|
|
|
|
let womanTiles = getAllIndexes(dstSVG, "card-female");
|
|
|
|
|
let count = 0;
|
|
|
|
|
for (let i of womanTiles) {
|
|
|
|
|
// Correct padding due to previous corrections
|
|
|
|
|
i += count++ * 2;
|
|
|
|
|
dstSVG =
|
|
|
|
|
dstSVG.substring(0, i) +
|
|
|
|
|
dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#ffb6c1"`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count = 0;
|
|
|
|
|
let manTiles = getAllIndexes(dstSVG, "card-male");
|
|
|
|
|
for (let i of manTiles) {
|
|
|
|
|
// Correct padding due to previous corrections
|
|
|
|
|
i += count++ * 2;
|
|
|
|
|
dstSVG =
|
|
|
|
|
dstSVG.substring(0, i) +
|
|
|
|
|
dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#add8e6"`);
|
|
|
|
|
// Download in SVG format
|
|
|
|
|
if (onlySVG) {
|
|
|
|
|
// Fix background color (first rect background)
|
|
|
|
|
dstSVG = dstSVG.replace(
|
|
|
|
|
`fill="transparent"></rect>`,
|
|
|
|
|
`fill="white"></rect>`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const blob = new Blob([`<svg>${dstSVG}</svg>`], {
|
|
|
|
|
type: "image/svg+xml",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
downloadBlob(blob, "ArbreGenealogique.svg");
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Download in PDF format
|
|
|
|
|
//navigator.clipboard.writeText(dstSVG);
|
|
|
|
|
target.innerHTML = dstSVG;
|
|
|
|
|
|
|
|
|
|
const doc = new jsPDF({
|
|
|
|
|
orientation: "l",
|
|
|
|
|
format: [docHeight, docWidth],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await doc.svg(target, {
|
|
|
|
|
height: docHeight,
|
|
|
|
|
width: docWidth,
|
|
|
|
|
@@ -157,12 +169,19 @@ export function ComplexFamilyTree(p: {
|
|
|
|
|
doc.save("ArbreGenealogique.pdf");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const exportPDF = () => doExport(false);
|
|
|
|
|
|
|
|
|
|
const exportSVG = () => doExport(true);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<div style={{ textAlign: "right" }}>
|
|
|
|
|
<IconButton onClick={exportPDF}>
|
|
|
|
|
<PictureAsPdfIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
<IconButton onClick={exportSVG}>
|
|
|
|
|
<Icon path={mdiXml} size={1} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
style={{ width: "100%" }}
|
|
|
|
|
|