This commit is contained in:
		
							
								
								
									
										95
									
								
								geneit_app/src/@types/family_chart.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										95
									
								
								geneit_app/src/@types/family_chart.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,95 +0,0 @@
 | 
			
		||||
declare module "family-chart" {
 | 
			
		||||
  type f3data = any;
 | 
			
		||||
  type f3tree = any;
 | 
			
		||||
 | 
			
		||||
  interface f3Rels {
 | 
			
		||||
    spouses?: string[];
 | 
			
		||||
    father?: string;
 | 
			
		||||
    mother?: string;
 | 
			
		||||
    children?: string[];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface f3DataData {
 | 
			
		||||
    gender: "M" | "F";
 | 
			
		||||
    avatar?: string;
 | 
			
		||||
    dead: boolean;
 | 
			
		||||
    birthday?: string;
 | 
			
		||||
    deathday?: string;
 | 
			
		||||
    first_name: string;
 | 
			
		||||
    last_name: string;
 | 
			
		||||
    dateOfWedding?: string;
 | 
			
		||||
    wedding_state?: string;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface f3Data {
 | 
			
		||||
    id: string;
 | 
			
		||||
    rels: f3Rels;
 | 
			
		||||
    data: f3DataData;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type f3State = {
 | 
			
		||||
    data: f3Data[];
 | 
			
		||||
    main_id?: any;
 | 
			
		||||
    tree?: f3tree;
 | 
			
		||||
    node_separation?: number;
 | 
			
		||||
    level_separation?: number;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  interface f3Update {
 | 
			
		||||
    tree: (props) => void;
 | 
			
		||||
    mainId: (mainId) => void;
 | 
			
		||||
    data: (data: f3data) => void;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface f3Store {
 | 
			
		||||
    state: f3State;
 | 
			
		||||
    update: f3update;
 | 
			
		||||
    getData: () => f3data;
 | 
			
		||||
    getTree: () => f3tree;
 | 
			
		||||
    setOnUpdate: (cb: (props) => void) => void;
 | 
			
		||||
    methods: any;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function createStore(initial_state: f3State): f3Store;
 | 
			
		||||
 | 
			
		||||
  function CalculateTree({
 | 
			
		||||
    data_stash,
 | 
			
		||||
    main_id = null,
 | 
			
		||||
    is_vertical = true,
 | 
			
		||||
    node_separation = 250,
 | 
			
		||||
    level_separation = 150,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function d3AnimationView(p: {
 | 
			
		||||
    store: f3Store;
 | 
			
		||||
    cont: HTMLElement | null;
 | 
			
		||||
    Card?: any;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const handlers: any;
 | 
			
		||||
 | 
			
		||||
  type F3elements = {
 | 
			
		||||
    Card: (props: {
 | 
			
		||||
      store: f3Store;
 | 
			
		||||
      svg: HTMLElement;
 | 
			
		||||
      mini_tree: boolean;
 | 
			
		||||
      link_break: boolean;
 | 
			
		||||
      cardEditForm?: boolean;
 | 
			
		||||
      card_dim: {
 | 
			
		||||
        w: number;
 | 
			
		||||
        h: number;
 | 
			
		||||
        text_x: number;
 | 
			
		||||
        text_y: number;
 | 
			
		||||
        img_w: number;
 | 
			
		||||
        img_h: number;
 | 
			
		||||
        img_x: number;
 | 
			
		||||
        img_y: number;
 | 
			
		||||
      };
 | 
			
		||||
      card_display: ((data: f3Data) => string)[];
 | 
			
		||||
    }) => F3CardBuilder;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type F3CardBuilder = (p: { node; d }) => HTMLElement;
 | 
			
		||||
 | 
			
		||||
  const elements: F3elements;
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,6 @@ import { SimpleFamilyTree } from "../../widgets/simple_family_tree/SimpleFamilyT
 | 
			
		||||
enum CurrTab {
 | 
			
		||||
  BasicTree,
 | 
			
		||||
  SimpleTree,
 | 
			
		||||
  AdvancedTree,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum TreeMode {
 | 
			
		||||
@@ -148,7 +147,6 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
 | 
			
		||||
        >
 | 
			
		||||
          <Tab tabIndex={CurrTab.BasicTree} label="Basique" />
 | 
			
		||||
          <Tab tabIndex={CurrTab.SimpleTree} label="Simple" />
 | 
			
		||||
          {/*<Tab tabIndex={CurrTab.AdvancedTree} label="Avancé" />*/}
 | 
			
		||||
        </Tabs>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -156,14 +154,8 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
 | 
			
		||||
      <Paper style={{ flex: "1", display: "flex", flexDirection: "column" }}>
 | 
			
		||||
        {currTab === CurrTab.BasicTree ? (
 | 
			
		||||
          <BasicFamilyTree tree={tree!} depth={currDepth} />
 | 
			
		||||
        ) : currTab === CurrTab.SimpleTree ? (
 | 
			
		||||
          <SimpleFamilyTree tree={tree!} depth={currDepth} />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <>unimplemented</> /*<ComplexFamilyTree
 | 
			
		||||
            tree={tree!}
 | 
			
		||||
            isUp={currMode === TreeMode.Ascending}
 | 
			
		||||
            depth={currDepth}
 | 
			
		||||
        />*/
 | 
			
		||||
          <SimpleFamilyTree tree={tree!} depth={currDepth} />
 | 
			
		||||
        )}
 | 
			
		||||
      </Paper>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,339 +0,0 @@
 | 
			
		||||
import { mdiXml } from "@mdi/js";
 | 
			
		||||
import Icon from "@mdi/react";
 | 
			
		||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
 | 
			
		||||
import { IconButton, Tooltip } from "@mui/material";
 | 
			
		||||
import "family-chart";
 | 
			
		||||
import { jsPDF } from "jspdf";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import "svg2pdf.js";
 | 
			
		||||
import { Couple } from "../../api/CoupleApi";
 | 
			
		||||
import { Member, fmtDate } from "../../api/MemberApi";
 | 
			
		||||
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
 | 
			
		||||
import {
 | 
			
		||||
  FamilyTreeNode,
 | 
			
		||||
  getAvailableMembers,
 | 
			
		||||
  treeHeight,
 | 
			
		||||
  treeWidth,
 | 
			
		||||
} from "../../utils/family_tree";
 | 
			
		||||
import { downloadBlob } from "../../utils/files_utils";
 | 
			
		||||
import "./family-chart.css";
 | 
			
		||||
 | 
			
		||||
export function ComplexFamilyTree(p: {
 | 
			
		||||
  tree: FamilyTreeNode;
 | 
			
		||||
  isUp: boolean;
 | 
			
		||||
  depth: number;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const darkTheme = useDarkTheme();
 | 
			
		||||
  console.log(f3);
 | 
			
		||||
  const applyTree = (container: HTMLDivElement) => {
 | 
			
		||||
    if (!container) return;
 | 
			
		||||
 | 
			
		||||
    const store = f3.createStore({
 | 
			
		||||
      data: treeToF3Data(p.tree, p.isUp, p.depth),
 | 
			
		||||
      node_separation: 250,
 | 
			
		||||
      level_separation: 150,
 | 
			
		||||
    });
 | 
			
		||||
    const view = f3.d3AnimationView({
 | 
			
		||||
      store,
 | 
			
		||||
      cont: container,
 | 
			
		||||
    });
 | 
			
		||||
    const Card = f3.elements.Card({
 | 
			
		||||
      store,
 | 
			
		||||
      svg: view.svg,
 | 
			
		||||
      card_dim: {
 | 
			
		||||
        w: 210,
 | 
			
		||||
        h: 120,
 | 
			
		||||
        text_x: 5,
 | 
			
		||||
        text_y: 75,
 | 
			
		||||
        img_w: 60,
 | 
			
		||||
        img_h: 70,
 | 
			
		||||
        img_x: 5,
 | 
			
		||||
        img_y: 5,
 | 
			
		||||
      },
 | 
			
		||||
      card_display: [
 | 
			
		||||
        (d) =>
 | 
			
		||||
          `${d.data.first_name || ""} ${d.data.last_name || ""} ${
 | 
			
		||||
            d.data.dead ? "✝" : ""
 | 
			
		||||
          }`,
 | 
			
		||||
        (d) => {
 | 
			
		||||
          let birthDeath = [];
 | 
			
		||||
          if (d.data.birthday) birthDeath.push(d.data.birthday);
 | 
			
		||||
          if (d.data.deathday) birthDeath.push(d.data.deathday);
 | 
			
		||||
 | 
			
		||||
          let s = birthDeath.join(" -> ");
 | 
			
		||||
 | 
			
		||||
          if (d.data.wedding_state || d.data.dateOfWedding) {
 | 
			
		||||
            let weddingInfo = [];
 | 
			
		||||
            if (d.data.wedding_state) weddingInfo.push(d.data.wedding_state);
 | 
			
		||||
            if (d.data.dateOfWedding)
 | 
			
		||||
              weddingInfo.push("Mariage : " + d.data.dateOfWedding);
 | 
			
		||||
            s += `</tspan> <tspan x="0" dy="14" font-size="10">${weddingInfo.join(
 | 
			
		||||
              " - "
 | 
			
		||||
            )}`;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return s;
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      mini_tree: true,
 | 
			
		||||
      link_break: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 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 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)}`);
 | 
			
		||||
 | 
			
		||||
    // Clone the SVG to manipulate it
 | 
			
		||||
    const container = document.createElement("div");
 | 
			
		||||
    container.classList.add("f3", "f3-export");
 | 
			
		||||
    container.style.width = docWidth + "px";
 | 
			
		||||
    container.style.height = docHeight + "px";
 | 
			
		||||
    document.body.appendChild(container);
 | 
			
		||||
    applyTree(container);
 | 
			
		||||
 | 
			
		||||
    const target = container.children[0];
 | 
			
		||||
 | 
			
		||||
    await new Promise((res) => setTimeout(() => res(null), 100));
 | 
			
		||||
 | 
			
		||||
    // SVG manipulations (adaptations to export)
 | 
			
		||||
    let dstSVG = target.innerHTML.replaceAll(
 | 
			
		||||
      `<path class="link" fill="none" stroke="#fff"`,
 | 
			
		||||
      `<path class="link" fill="none" stroke="#000"`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    dstSVG = dstSVG.replaceAll(
 | 
			
		||||
      `class="text-overflow-mask"`,
 | 
			
		||||
      `class="text-overflow-mask" fill="transparent"`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    dstSVG = dstSVG.replaceAll(`>UNKNOWN<`, `fill="#000">INCONNU<`);
 | 
			
		||||
 | 
			
		||||
    dstSVG = dstSVG.replaceAll(
 | 
			
		||||
      `class="card-outline`,
 | 
			
		||||
      `fill="transparent" class="card-outline`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    dstSVG = dstSVG.replaceAll("✝", " ");
 | 
			
		||||
 | 
			
		||||
    // 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,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    container.remove();
 | 
			
		||||
 | 
			
		||||
    // Save the created pdf
 | 
			
		||||
    doc.save("ArbreGenealogique.pdf");
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const exportPDF = () => doExport(false);
 | 
			
		||||
 | 
			
		||||
  const exportSVG = () => doExport(true);
 | 
			
		||||
 | 
			
		||||
  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>
 | 
			
		||||
      <div
 | 
			
		||||
        style={{ width: "100%" }}
 | 
			
		||||
        className={`f3 ${darkTheme.enabled ? "f3-dark" : "f3-light"}`}
 | 
			
		||||
        id="FamilyChart"
 | 
			
		||||
        ref={applyTree}
 | 
			
		||||
      ></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function treeToF3Data(
 | 
			
		||||
  node: FamilyTreeNode,
 | 
			
		||||
  isUp: boolean,
 | 
			
		||||
  depth: number
 | 
			
		||||
): f3.f3Data[] {
 | 
			
		||||
  const availableMembers = getAvailableMembers(node, depth);
 | 
			
		||||
 | 
			
		||||
  const list: f3.f3Data[] = [];
 | 
			
		||||
  if (isUp) treeToF3DataUpRecurse(node, list, availableMembers);
 | 
			
		||||
  else treeToF3DataDownRecurse(node, list, availableMembers);
 | 
			
		||||
  return list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function memberData(m: Member, c?: Couple): f3.f3DataData {
 | 
			
		||||
  return {
 | 
			
		||||
    first_name: m.first_name ?? "_",
 | 
			
		||||
    last_name: m.last_name ?? "_",
 | 
			
		||||
    gender: m.sex ?? "M",
 | 
			
		||||
    avatar: m.thumbnailURL ?? undefined,
 | 
			
		||||
    dead: m.dead,
 | 
			
		||||
    birthday: m.dateOfBirth ? fmtDate(m.dateOfBirth) : undefined,
 | 
			
		||||
    deathday: m.dateOfDeath ? fmtDate(m.dateOfDeath) : undefined,
 | 
			
		||||
    wedding_state: c?.stateFr,
 | 
			
		||||
    dateOfWedding: c?.dateOfWedding ? fmtDate(c?.dateOfWedding) : undefined,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function treeToF3DataUpRecurse(
 | 
			
		||||
  node: FamilyTreeNode,
 | 
			
		||||
  array: f3.f3Data[],
 | 
			
		||||
  availableMembers: Set<number>,
 | 
			
		||||
  child?: number,
 | 
			
		||||
  spouses?: number[]
 | 
			
		||||
) {
 | 
			
		||||
  if (!availableMembers.has(node.member.id)) return;
 | 
			
		||||
 | 
			
		||||
  array.push({
 | 
			
		||||
    data: memberData(node.member),
 | 
			
		||||
    id: node.member.id.toString(),
 | 
			
		||||
    rels: {
 | 
			
		||||
      father:
 | 
			
		||||
        node.member.father && availableMembers.has(node.member.father)
 | 
			
		||||
          ? node.member.father.toString()
 | 
			
		||||
          : undefined,
 | 
			
		||||
      mother:
 | 
			
		||||
        node.member.mother && availableMembers.has(node.member.mother)
 | 
			
		||||
          ? node.member.mother.toString()
 | 
			
		||||
          : undefined,
 | 
			
		||||
 | 
			
		||||
      spouses: spouses
 | 
			
		||||
        ?.filter((c) => c !== node.member.id)
 | 
			
		||||
        .map((c) => c.toString()),
 | 
			
		||||
      children: child ? [child.toString()] : undefined,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const parentSpouses = node.down?.map((c) => c.member.id);
 | 
			
		||||
 | 
			
		||||
  node.down?.forEach((d) =>
 | 
			
		||||
    treeToF3DataUpRecurse(
 | 
			
		||||
      d,
 | 
			
		||||
      array,
 | 
			
		||||
      availableMembers,
 | 
			
		||||
      node.member.id,
 | 
			
		||||
      parentSpouses
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function treeToF3DataDownRecurse(
 | 
			
		||||
  node: FamilyTreeNode,
 | 
			
		||||
  array: f3.f3Data[],
 | 
			
		||||
  availableMembers: Set<number>
 | 
			
		||||
) {
 | 
			
		||||
  if (!availableMembers.has(node.member.id)) return;
 | 
			
		||||
 | 
			
		||||
  // Get all members ids
 | 
			
		||||
  let children = node?.down?.map((c) => c.member.id) ?? [];
 | 
			
		||||
  node.couples?.map((c) => c.down.forEach((m) => children.push(m.member.id)));
 | 
			
		||||
 | 
			
		||||
  children = children.filter((c) => availableMembers.has(c));
 | 
			
		||||
 | 
			
		||||
  array.push({
 | 
			
		||||
    data: memberData(node.member),
 | 
			
		||||
    id: node.member.id.toString(),
 | 
			
		||||
    rels: {
 | 
			
		||||
      father:
 | 
			
		||||
        node.member.father && availableMembers.has(node.member.father)
 | 
			
		||||
          ? node.member.father.toString()
 | 
			
		||||
          : undefined,
 | 
			
		||||
      mother:
 | 
			
		||||
        node.member.mother && availableMembers.has(node.member.mother)
 | 
			
		||||
          ? node.member.mother.toString()
 | 
			
		||||
          : undefined,
 | 
			
		||||
 | 
			
		||||
      spouses: node.couples
 | 
			
		||||
        ?.filter((s) => availableMembers.has(s.member.id))
 | 
			
		||||
        .map((c) => c.member.id.toString()),
 | 
			
		||||
      children: children.map((c) => c.toString()),
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  node?.down?.forEach((e) =>
 | 
			
		||||
    treeToF3DataDownRecurse(e, array, availableMembers)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (node.couples) {
 | 
			
		||||
    for (const c of node.couples) {
 | 
			
		||||
      if (!availableMembers.has(c.member.id)) continue;
 | 
			
		||||
      array.push({
 | 
			
		||||
        data: memberData(c.member, c.couple),
 | 
			
		||||
        id: c.member.id.toString(),
 | 
			
		||||
        rels: {
 | 
			
		||||
          father:
 | 
			
		||||
            c.member.father && availableMembers.has(c.member.father)
 | 
			
		||||
              ? c.member.father.toString()
 | 
			
		||||
              : undefined,
 | 
			
		||||
          mother:
 | 
			
		||||
            c.member.mother && availableMembers.has(c.member.mother)
 | 
			
		||||
              ? c.member.mother.toString()
 | 
			
		||||
              : undefined,
 | 
			
		||||
          spouses: [node.member.id.toString()],
 | 
			
		||||
          children: c.down
 | 
			
		||||
            .filter((c) => availableMembers.has(c.member.id))
 | 
			
		||||
            .map((c) => c.member.id.toString()),
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      c.down.forEach((e) =>
 | 
			
		||||
        treeToF3DataDownRecurse(e, array, availableMembers)
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,111 +0,0 @@
 | 
			
		||||
.f3 {
 | 
			
		||||
  height: 700px;
 | 
			
		||||
  max-height: calc(100vh - 80px);
 | 
			
		||||
  width: 900px;
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  margin: auto;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.f3 .cursor-pointer {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.f3 svg.main_svg {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  /*background-color: #3b5560;*/
 | 
			
		||||
  color: #3b5560;
 | 
			
		||||
}
 | 
			
		||||
.f3 svg.main_svg text {
 | 
			
		||||
  fill: currentColor;
 | 
			
		||||
}
 | 
			
		||||
.f3 rect.card-female,
 | 
			
		||||
.f3 .card-female .card-body-rect,
 | 
			
		||||
.f3 .card-female .text-overflow-mask {
 | 
			
		||||
  fill: lightpink;
 | 
			
		||||
}
 | 
			
		||||
.f3 rect.card-male,
 | 
			
		||||
.f3 .card-male .card-body-rect,
 | 
			
		||||
.f3 .card-male .text-overflow-mask {
 | 
			
		||||
  fill: lightblue;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card-genderless .card-body-rect,
 | 
			
		||||
.f3 .card-genderless .text-overflow-mask {
 | 
			
		||||
  fill: lightgray;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_add .card-body-rect {
 | 
			
		||||
  fill: #3b5560;
 | 
			
		||||
  stroke-width: 4px;
 | 
			
		||||
  stroke: #fff;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.f3 g.card_add text {
 | 
			
		||||
  fill: #fff;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card-main {
 | 
			
		||||
  stroke: #000;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_family_tree rect {
 | 
			
		||||
  transition: 0.3s;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_family_tree:hover rect {
 | 
			
		||||
  transform: scale(1.1);
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_add_relative {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  transition: 0.3s;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_add_relative circle {
 | 
			
		||||
  fill: rgba(0, 0, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_add_relative:hover {
 | 
			
		||||
  color: black;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_edit.pencil_icon {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  transition: 0.3s;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_edit.pencil_icon:hover {
 | 
			
		||||
  color: black;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_break_link,
 | 
			
		||||
.f3 .link_upper,
 | 
			
		||||
.f3 .link_lower,
 | 
			
		||||
.f3 .link_particles {
 | 
			
		||||
  transform-origin: 50% 50%;
 | 
			
		||||
  transition: 1s;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_break_link {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_break_link.closed .link_upper {
 | 
			
		||||
  transform: translate(-140.5px, 655.6px);
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_break_link.closed .link_upper g {
 | 
			
		||||
  transform: rotate(-58deg);
 | 
			
		||||
}
 | 
			
		||||
.f3 .card_break_link.closed .link_particles {
 | 
			
		||||
  transform: scale(0);
 | 
			
		||||
}
 | 
			
		||||
.f3 .input-field input {
 | 
			
		||||
  height: 2.5rem !important;
 | 
			
		||||
}
 | 
			
		||||
.f3 .input-field > label:not(.label-icon).active {
 | 
			
		||||
  -webkit-transform: translateY(-8px) scale(0.8);
 | 
			
		||||
  transform: translateY(-8px) scale(0.8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.f3-light .link {
 | 
			
		||||
  stroke: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.f3-export {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0px;
 | 
			
		||||
  left: 0px;
 | 
			
		||||
  /*width: 3508px;
 | 
			
		||||
  height: 2480px;*/
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user