File size: 2,516 Bytes
f909d7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { firaMono } from "src/constants/fonts";
import useConfig from "src/store/useConfig";
import useGraph from "src/store/useGraph";

type Text = string | [string, string][];
type Size = { width: number; height: number };

export const isContentImage = (value: Text) => {
  if (typeof value !== "string") return false;

  const isImageURL = /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(value);
  const isBase64 = value.startsWith("data:image/") && value.includes("base64");

  return isImageURL || isBase64;
};

const calculateLines = (text: Text): string => {
  if (typeof text === "string") {
    return text;
  } else {
    return text.map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 80)}`).join("\n");
  }
};

const calculateWidthAndHeight = (str: string, single = false) => {
  if (!str) return { width: 45, height: 45 };

  const dummyElement = document.createElement("div");

  dummyElement.style.whiteSpace = single ? "nowrap" : "pre-wrap";
  dummyElement.innerHTML = str;
  dummyElement.style.fontSize = "12px";
  dummyElement.style.width = "fit-content";
  dummyElement.style.height = "fit-content";
  dummyElement.style.padding = "10px";
  dummyElement.style.fontWeight = "500";
  dummyElement.style.overflowWrap = "break-word";
  dummyElement.style.fontFamily = firaMono.style.fontFamily;
  document.body.appendChild(dummyElement);

  const clientRect = dummyElement.getBoundingClientRect();
  const width = clientRect.width + 4;
  const height = clientRect.height;

  document.body.removeChild(dummyElement);

  return { width, height };
};

const sizeCache = new Map<Text, Size>();

// clear cache every 2 mins
setInterval(() => sizeCache.clear(), 120_000);

export const calculateNodeSize = (text: Text, isParent = false) => {
  const { foldNodes } = useGraph.getState();
  const { imagePreviewEnabled } = useConfig.getState();
  const isImage = isContentImage(text) && imagePreviewEnabled;

  const cacheKey = [text, isParent, foldNodes].toString();

  // check cache if data already exists
  if (sizeCache.has(cacheKey)) {
    const size = sizeCache.get(cacheKey);

    if (size) return size;
  }

  const lines = calculateLines(text);
  const sizes = calculateWidthAndHeight(lines, typeof text === "string");

  if (isImage) {
    sizes.width = 80;
    sizes.height = 80;
  }

  if (foldNodes) sizes.width = 300;
  if (isParent && foldNodes) sizes.width = 170;
  if (isParent) sizes.width += 100;
  if (sizes.width > 700) sizes.width = 700;

  sizeCache.set(cacheKey, sizes);
  return sizes;
};