@lv-x-software-house/x_view 1.2.4 → 1.2.5-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1984 -865
  2. package/dist/index.mjs +1991 -866
  3. package/package.json +52 -43
package/dist/index.mjs CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/XViewScene.jsx
2
- import React25, { useCallback as useCallback4, useEffect as useEffect22, useRef as useRef19, useState as useState25, useMemo as useMemo12 } from "react";
2
+ import React25, {
3
+ useCallback as useCallback4,
4
+ useEffect as useEffect22,
5
+ useRef as useRef19,
6
+ useState as useState25,
7
+ useMemo as useMemo12
8
+ } from "react";
3
9
  import { useRouter, useSearchParams } from "next/navigation";
4
10
  import { useSession } from "next-auth/react";
5
11
  import CryptoJS from "crypto-js";
@@ -105,6 +111,7 @@ function ContextMenu({
105
111
  const [menuView, setMenuView] = useState("main");
106
112
  const [selectedAncestry, setSelectedAncestry] = useState(null);
107
113
  const [versionSubMenu, setVersionSubMenu] = useState(null);
114
+ const [labelSubMenu, setLabelSubMenu] = useState(null);
108
115
  const [isLinkCopied, setIsLinkCopied] = useState(false);
109
116
  const [selectedQuestStatus, setSelectedQuestStatus] = useState(null);
110
117
  const ability = useMemo(() => defineAbilityFor(userRole), [userRole]);
@@ -113,6 +120,7 @@ function ContextMenu({
113
120
  setMenuView("main");
114
121
  setSelectedAncestry(null);
115
122
  setVersionSubMenu(null);
123
+ setLabelSubMenu(null);
116
124
  setSelectedQuestStatus(null);
117
125
  }
118
126
  }, [data.visible, (_a = data.nodeData) == null ? void 0 : _a.id]);
@@ -128,7 +136,7 @@ function ContextMenu({
128
136
  if (left + w + 8 > vw) left = Math.max(8, vw - w - 8);
129
137
  if (top + h + 8 > vh) top = Math.max(8, vh - h - 8);
130
138
  setMenuPos({ left, top });
131
- }, [data, menuView, versionSubMenu, selectedQuestStatus]);
139
+ }, [data, menuView, versionSubMenu, labelSubMenu, selectedQuestStatus]);
132
140
  useEffect(() => {
133
141
  if (!data.visible) return;
134
142
  const handleClickOutside = (e) => {
@@ -180,7 +188,21 @@ function ContextMenu({
180
188
  var _a2;
181
189
  return (_a2 = c.targetNode) == null ? void 0 : _a2.is_quest;
182
190
  });
183
- const groupedConnections = commonConnections.reduce((acc, conn) => {
191
+ const getLabelForConnection = (conn) => {
192
+ if (conn.direction === "outgoing") return conn.link.source_label || null;
193
+ if (conn.direction === "incoming") return conn.link.target_label || null;
194
+ return null;
195
+ };
196
+ const commonWithLabel = commonConnections.filter((c) => getLabelForConnection(c));
197
+ const commonWithoutLabel = commonConnections.filter((c) => !getLabelForConnection(c));
198
+ const labelGroups = commonWithLabel.reduce((acc, conn) => {
199
+ const label = getLabelForConnection(conn);
200
+ if (!acc[label]) acc[label] = [];
201
+ acc[label].push(conn);
202
+ return acc;
203
+ }, {});
204
+ const labelGroupEntries = Object.entries(labelGroups).sort(([a], [b]) => a.localeCompare(b));
205
+ const groupedConnections = commonWithoutLabel.reduce((acc, conn) => {
184
206
  var _a2;
185
207
  const { targetNode } = conn;
186
208
  const groupingKey = ((_a2 = targetNode.version_node) == null ? void 0 : _a2.is_version) ? targetNode.version_node.parent_node : targetNode.id;
@@ -308,11 +330,32 @@ function ContextMenu({
308
330
  /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
309
331
  ))));
310
332
  };
333
+ const renderLabelSubMenuView = () => {
334
+ const group = labelSubMenu;
335
+ const isScrollable = group.connections.length > 10;
336
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React.createElement("button", { onClick: () => {
337
+ setLabelSubMenu(null);
338
+ setMenuView("connections");
339
+ }, className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1.5 min-w-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ React.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-violet-300/80 truncate", title: group.label }, group.label))), group.connections.length > 0 && /* @__PURE__ */ React.createElement("button", { onClick: () => handleExpandAndClose(group.connections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, group.connections.map((conn) => /* @__PURE__ */ React.createElement(
340
+ "button",
341
+ {
342
+ key: conn.targetNode.id,
343
+ onClick: () => handleExpandAndClose([conn.link]),
344
+ className: baseButtonClass,
345
+ title: `Expandir conex\xE3o com ${conn.targetNode.name}`
346
+ },
347
+ /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }), /* @__PURE__ */ React.createElement("polyline", { points: "12 5 19 12 12 19" })),
348
+ /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
349
+ ))));
350
+ };
311
351
  const renderConnectionsView = () => {
312
352
  if (versionSubMenu) {
313
353
  return renderVersionSubMenuView();
314
354
  }
315
- const totalItems = availableAncestries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
355
+ if (labelSubMenu) {
356
+ return renderLabelSubMenuView();
357
+ }
358
+ const totalItems = availableAncestries.length + labelGroupEntries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
316
359
  const isScrollable = totalItems > 10;
317
360
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { onClick: () => setMenuView("main"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Conex\xF5es")), commonConnections.length > 0 && /* @__PURE__ */ React.createElement("button", { onClick: () => handleExpandAndClose(commonConnections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ React.createElement("div", { className: `flex flex-col ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, /* @__PURE__ */ React.createElement("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wider text-indigo-400/90" }, "Ancestralidades Salvas"), availableAncestries.map((anc) => /* @__PURE__ */ React.createElement(
318
361
  "button",
@@ -324,7 +367,20 @@ function ContextMenu({
324
367
  },
325
368
  /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 3v4a2 2 0 0 0 2 2h4" }), /* @__PURE__ */ React.createElement("path", { d: "M18 3v4a2 2 0 0 1-2 2h-4" }), /* @__PURE__ */ React.createElement("circle", { cx: "6", cy: "3", r: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "18", cy: "3", r: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "11", r: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M12 13v6" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "21", r: "2" })),
326
369
  /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, anc.name || `Ancestralidade #${anc.ancestry_id.substring(0, 8)}`)
327
- ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
370
+ ))), labelGroupEntries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${availableAncestries.length > 0 ? "mt-2" : ""} ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), labelGroupEntries.map(([label, conns]) => /* @__PURE__ */ React.createElement(
371
+ "button",
372
+ {
373
+ key: label,
374
+ onClick: () => {
375
+ setLabelSubMenu({ label, connections: conns });
376
+ },
377
+ className: baseButtonClass,
378
+ title: `Ver grupo: ${label}`
379
+ },
380
+ /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400" }, /* @__PURE__ */ React.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })),
381
+ /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, label),
382
+ /* @__PURE__ */ React.createElement("span", { className: "text-xs px-2 py-0.5 bg-violet-500/20 text-violet-300 rounded-full" }, conns.length)
383
+ ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, (availableAncestries.length > 0 || labelGroupEntries.length > 0) && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
328
384
  if (group.isVersionGroup) {
329
385
  return /* @__PURE__ */ React.createElement(
330
386
  "button",
@@ -402,11 +458,18 @@ function ContextMenu({
402
458
  ))));
403
459
  };
404
460
  const renderAncestryActionsView = () => {
461
+ var _a2, _b2;
405
462
  const ancestryTitle = (selectedAncestry == null ? void 0 : selectedAncestry.name) || `Ancestralidade #${selectedAncestry == null ? void 0 : selectedAncestry.ancestry_id.substring(0, 8)}`;
406
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React.createElement("button", { onClick: () => setMenuView("connections"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400 truncate", title: ancestryTitle }, ancestryTitle))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, ability.can("read", "Ancestry") && /* @__PURE__ */ React.createElement("button", { onClick: () => {
407
- onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry);
463
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React.createElement("button", { onClick: () => setMenuView("connections"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400 truncate", title: ancestryTitle }, ancestryTitle))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, ability.can("read", "Ancestry") && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("button", { onClick: () => {
464
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "full");
465
+ onClose();
466
+ }, className: baseButtonClass, title: "Renderizar Ancestralidade Completa" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "3" })), /* @__PURE__ */ React.createElement("span", null, "Renderizar Ancestralidade")), /* @__PURE__ */ React.createElement("button", { onClick: () => {
467
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "ancestry_only");
468
+ onClose();
469
+ }, className: baseButtonClass, title: "Renderizar apenas a \xC1rvore de Ancestralidade (Radial)" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "3" }), /* @__PURE__ */ React.createElement("path", { d: "M12 2v3m0 14v3m10-10h-3m-14 0H2m15.66-6.34-2.12 2.12m-9.08 9.08-2.12 2.12m13.32 0-2.12-2.12m-9.08-9.08-2.12-2.12" })), /* @__PURE__ */ React.createElement("span", null, "\xC1rvore de Ancestralidade")), ((_b2 = (_a2 = selectedAncestry == null ? void 0 : selectedAncestry.abstraction_tree) == null ? void 0 : _a2.children) == null ? void 0 : _b2.length) > 0 && /* @__PURE__ */ React.createElement("button", { onClick: () => {
470
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "abstraction_only");
408
471
  onClose();
409
- }, className: baseButtonClass, title: "Renderizar Ancestralidade" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "3" })), /* @__PURE__ */ React.createElement("span", null, "Renderizar Ancestralidade")), ability.can("update", "Ancestry") && /* @__PURE__ */ React.createElement("button", { onClick: () => {
472
+ }, className: baseButtonClass, title: "Renderizar apenas a \xC1rvore de Abstra\xE7\xE3o (Vertical)" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ React.createElement("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ React.createElement("rect", { x: "14", y: "14", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ React.createElement("rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ React.createElement("path", { d: "M10 6.5h4" }), /* @__PURE__ */ React.createElement("path", { d: "M10 17.5h4" }), /* @__PURE__ */ React.createElement("path", { d: "M6.5 10v4" }), /* @__PURE__ */ React.createElement("path", { d: "M17.5 10v4" })), /* @__PURE__ */ React.createElement("span", null, "\xC1rvore de Abstra\xE7\xE3o"))), ability.can("update", "Ancestry") && /* @__PURE__ */ React.createElement("button", { onClick: () => {
410
473
  onEditAncestry == null ? void 0 : onEditAncestry(selectedAncestry);
411
474
  onClose();
412
475
  }, className: baseButtonClass, title: "Editar Ancestralidade" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M12 20h9" }), /* @__PURE__ */ React.createElement("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })), /* @__PURE__ */ React.createElement("span", null, "Editar Ancestralidade")), (ability.can("update", "Ancestry") || ability.can("delete", "Ancestry")) && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), ability.can("delete", "Ancestry") && /* @__PURE__ */ React.createElement("button", { onClick: () => {
@@ -653,7 +716,7 @@ function XViewSidebar({
653
716
  "div",
654
717
  {
655
718
  ref: containerRef,
656
- className: "ui-overlay fixed left-0 top-0 h-full w-[min(92vw,320px)] z-40",
719
+ className: "ui-overlay fixed left-0 top-0 h-[100dvh] w-[min(92vw,320px)] z-40 overflow-hidden",
657
720
  onPointerDown: swallow,
658
721
  onPointerMove: swallow,
659
722
  onPointerUp: swallow,
@@ -662,7 +725,7 @@ function XViewSidebar({
662
725
  onContextMenu: swallow,
663
726
  onDoubleClick: swallow
664
727
  },
665
- /* @__PURE__ */ React2.createElement("div", { className: "h-full flex flex-col bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ React2.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ React2.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React2.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ React2.createElement(
728
+ /* @__PURE__ */ React2.createElement("div", { className: "h-full flex flex-col overflow-hidden bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ React2.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ React2.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React2.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ React2.createElement(
666
729
  "button",
667
730
  {
668
731
  className: "ml-auto p-2 rounded-md text-slate-400 hover:text-white hover:bg-white/10 transition-colors",
@@ -719,7 +782,7 @@ function XViewSidebar({
719
782
  autoComplete: "off"
720
783
  }
721
784
  )
722
- ), showList && /* @__PURE__ */ React2.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[28rem] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
785
+ ), showList && /* @__PURE__ */ React2.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[min(448px,50dvh)] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
723
786
  const inView = isNodeInView(n.id);
724
787
  const active = selectedNodeId === n.id;
725
788
  const typeLabel = Array.isArray(n.type) ? n.type.join(", ") : n.type;
@@ -3095,6 +3158,7 @@ function calculateNodePositions(nodes) {
3095
3158
  return positions;
3096
3159
  }
3097
3160
  function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, isSceneBusy, parentData, ancestryData }) {
3161
+ var _a, _b;
3098
3162
  if (!tooltipEl || !camera || !mountEl) return;
3099
3163
  let content = "";
3100
3164
  let positionTarget = null;
@@ -3107,17 +3171,21 @@ function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, i
3107
3171
  content = generateTooltipHtml(hoveredNode.userData, parentData, ancestryData);
3108
3172
  }
3109
3173
  } else if (hoveredLink && !isSceneBusy) {
3110
- currentId = `link_${hoveredLink.userData.id}`;
3111
- if (hoveredLink.userData.isCurved) {
3112
- const positions = hoveredLink.geometry.attributes.position.array;
3113
- const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3114
- positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3115
- } else {
3116
- positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3117
- }
3118
- isLink = true;
3119
- if (tooltipEl.dataset.currentId !== currentId) {
3120
- content = hoveredLink.userData.isAncestryLink ? generateLinkTooltipHtml(hoveredLink.userData.relationship || {}, parentData, ancestryData) : generateLinkTooltipHtml(hoveredLink.userData, parentData, ancestryData);
3174
+ const linkData = hoveredLink.userData.isAncestryLink ? hoveredLink.userData.relationship || {} : hoveredLink.userData;
3175
+ const hasContent = ((_a = linkData.name) == null ? void 0 : _a.trim()) || ((_b = linkData.description) == null ? void 0 : _b.trim());
3176
+ if (hasContent) {
3177
+ currentId = `link_${hoveredLink.userData.id}`;
3178
+ if (hoveredLink.userData.isCurved) {
3179
+ const positions = hoveredLink.geometry.attributes.position.array;
3180
+ const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3181
+ positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3182
+ } else {
3183
+ positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3184
+ }
3185
+ isLink = true;
3186
+ if (tooltipEl.dataset.currentId !== currentId) {
3187
+ content = generateLinkTooltipHtml(linkData, parentData, ancestryData);
3188
+ }
3121
3189
  }
3122
3190
  }
3123
3191
  if (positionTarget) {
@@ -3426,9 +3494,9 @@ function CustomPropertyDisplay({
3426
3494
  };
3427
3495
  const handleRemoveListItem = (j) => setTempProp((p) => ({ ...p, value: p.value.filter((_, k) => k !== j) }));
3428
3496
  const handleListItemChange = (j, f, v) => setTempProp((p) => {
3429
- const newValue = [...p.value];
3430
- newValue[j] = { ...newValue[j], [f]: v };
3431
- return { ...p, value: newValue };
3497
+ const newValue2 = [...p.value];
3498
+ newValue2[j] = { ...newValue2[j], [f]: v };
3499
+ return { ...p, value: newValue2 };
3432
3500
  });
3433
3501
  const baseInput = "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400/60 transition-all duration-150";
3434
3502
  const renderEditView = () => {
@@ -3482,14 +3550,14 @@ function CustomPropertyDisplay({
3482
3550
  const inputClass = `${baseInput} ${noSpinnerClass}`;
3483
3551
  const handleDateTypeChange = (newDateType) => {
3484
3552
  var _a3, _b2, _c2;
3485
- let newValue = { type: newDateType };
3553
+ let newValue2 = { type: newDateType };
3486
3554
  if (newDateType === "Date Interval") {
3487
- newValue.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3488
- newValue.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3555
+ newValue2.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3556
+ newValue2.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3489
3557
  } else {
3490
- newValue.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3558
+ newValue2.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3491
3559
  }
3492
- handlePropChange("value", newValue);
3560
+ handlePropChange("value", newValue2);
3493
3561
  };
3494
3562
  const handleDateValueChange = (dateField, dateValue) => {
3495
3563
  handlePropChange("value", { ...tempProp.value, [dateField]: dateValue });
@@ -4337,7 +4405,7 @@ ${space}${bullet} `);
4337
4405
  }
4338
4406
  ),
4339
4407
  /* @__PURE__ */ React5.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0 shrink-0" }),
4340
- /* @__PURE__ */ React5.createElement("div", { className: "px-6 pt-5 pb-3 flex items-center justify-between gap-4 shrink-0" }, /* @__PURE__ */ React5.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React5.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80" }), /* @__PURE__ */ React5.createElement("p", { className: "text-sm font-medium text-slate-200" }, title || "Editar Descri\xE7\xE3o")), /* @__PURE__ */ React5.createElement("button", { onClick: handleClose, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")),
4408
+ /* @__PURE__ */ React5.createElement("div", { className: "px-6 pt-5 pb-3 flex items-center justify-between gap-4 shrink-0" }, /* @__PURE__ */ React5.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React5.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80 shrink-0" }), /* @__PURE__ */ React5.createElement("p", { className: "text-sm font-medium text-slate-200 truncate" }, title || "Editar Descri\xE7\xE3o")), /* @__PURE__ */ React5.createElement("button", { onClick: handleClose, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")),
4341
4409
  /* @__PURE__ */ React5.createElement("div", { className: "px-6 py-3 flex flex-col gap-3 border-b border-white/5 bg-white/5 shrink-0" }, /* @__PURE__ */ React5.createElement("div", { className: "flex items-center gap-2 flex-wrap" }, /* @__PURE__ */ React5.createElement(
4342
4410
  "button",
4343
4411
  {
@@ -5466,7 +5534,7 @@ function AncestryRelationshipPanel({
5466
5534
  onImageClick: handleImageClickFromText,
5467
5535
  onSaveDescription: handleSaveDescriptionInline
5468
5536
  }
5469
- ) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("div", { className: "h-[2px] bg-gradient-to-r from-cyan-400/0 via-cyan-400/70 to-cyan-400/0" }), /* @__PURE__ */ React8.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React8.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-cyan-400/80 shadow-[0_0_18px_2px_rgba(45,212,191,0.55)]" }), /* @__PURE__ */ React8.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o de Ancestralidade")), /* @__PURE__ */ React8.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, "Editar Rela\xE7\xE3o")), /* @__PURE__ */ React8.createElement("button", { onClick: onClose, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React8.createElement("div", { className: "px-6 pb-4 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React8.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o da Rela\xE7\xE3o"), /* @__PURE__ */ React8.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React8.createElement(
5537
+ ) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("div", { className: "h-[2px] bg-gradient-to-r from-cyan-400/0 via-cyan-400/70 to-cyan-400/0" }), /* @__PURE__ */ React8.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React8.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React8.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-cyan-400/80 shadow-[0_0_18px_2px_rgba(45,212,191,0.55)]" }), /* @__PURE__ */ React8.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o de Ancestralidade")), /* @__PURE__ */ React8.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, "Editar Rela\xE7\xE3o")), /* @__PURE__ */ React8.createElement("button", { onClick: onClose, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React8.createElement("div", { className: "px-6 pb-4 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React8.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o da Rela\xE7\xE3o"), /* @__PURE__ */ React8.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React8.createElement(
5470
5538
  DescriptionDisplay,
5471
5539
  {
5472
5540
  description,
@@ -5788,6 +5856,7 @@ function CreateAncestryPanel({
5788
5856
  } = ancestryMode;
5789
5857
  const [isSaving, setIsSaving] = useState11(false);
5790
5858
  const [isLinkCopied, setIsLinkCopied] = useState11(false);
5859
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState11(false);
5791
5860
  const [showDeleteBranchConfirm, setShowDeleteBranchConfirm] = useState11(false);
5792
5861
  const handleCopyLink = (e) => {
5793
5862
  e.stopPropagation();
@@ -5855,12 +5924,14 @@ function CreateAncestryPanel({
5855
5924
  };
5856
5925
  const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5857
5926
  setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5927
+ setHasUnsavedChanges(true);
5858
5928
  };
5859
5929
  const handleToggleAddMode = (isAbstraction = false) => {
5860
5930
  if (isAbstraction && !ancestryMode.isAddingAbstractionNodes) {
5861
5931
  setTargetRenderNodeId(null);
5862
5932
  }
5863
5933
  setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5934
+ setHasUnsavedChanges(true);
5864
5935
  };
5865
5936
  const handleRemoveNode = useCallback2((pathToRemove, isAbstraction = false) => {
5866
5937
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
@@ -5878,6 +5949,7 @@ function CreateAncestryPanel({
5878
5949
  const indexToRemove = pathToRemove[pathToRemove.length - 1];
5879
5950
  if (currentParent.children && currentParent.children.length > indexToRemove) {
5880
5951
  currentParent.children.splice(indexToRemove, 1);
5952
+ setHasUnsavedChanges(true);
5881
5953
  }
5882
5954
  return { ...prev, [treeKey]: newTree };
5883
5955
  });
@@ -5936,6 +6008,7 @@ function CreateAncestryPanel({
5936
6008
  updateGlobalTree(rootTreeClone);
5937
6009
  }
5938
6010
  setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
6011
+ setHasUnsavedChanges(true);
5939
6012
  } else {
5940
6013
  alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5941
6014
  }
@@ -6008,6 +6081,7 @@ function CreateAncestryPanel({
6008
6081
  const handleAddProp = () => {
6009
6082
  const newProp = createNewCustomProperty(customProps);
6010
6083
  setCustomProps((p) => [...p, newProp]);
6084
+ setHasUnsavedChanges(true);
6011
6085
  setTimeout(() => {
6012
6086
  var _a;
6013
6087
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -6016,11 +6090,13 @@ function CreateAncestryPanel({
6016
6090
  const handleRemoveProp = (i) => {
6017
6091
  const newProps = customProps.filter((_, idx) => idx !== i);
6018
6092
  setCustomProps(newProps);
6093
+ setHasUnsavedChanges(true);
6019
6094
  };
6020
6095
  const handleUpdateProp = (index, updatedProp) => {
6021
6096
  const newProps = [...customProps];
6022
6097
  newProps[index] = updatedProp;
6023
6098
  setCustomProps(newProps);
6099
+ setHasUnsavedChanges(true);
6024
6100
  };
6025
6101
  const currentUsedTypes = customProps.map((p) => p.type).filter((t) => UNIQUE_PROP_TYPES.includes(t));
6026
6102
  useEffect10(() => {
@@ -6130,6 +6206,7 @@ function CreateAncestryPanel({
6130
6206
  updateGlobalTree(rootTreeClone);
6131
6207
  setBranchStack([...branchStack]);
6132
6208
  setIsPickerOpen(false);
6209
+ setHasUnsavedChanges(true);
6133
6210
  try {
6134
6211
  setIsSaving(true);
6135
6212
  const rootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6143,6 +6220,7 @@ function CreateAncestryPanel({
6143
6220
  rootExtras
6144
6221
  );
6145
6222
  setLastSavedSnapshot(takeSnapshot(rootTreeClone, ancestryName, description, processedSections, [], isPrivate, ancestryMode.abstraction_tree));
6223
+ setHasUnsavedChanges(false);
6146
6224
  if (onRenderFullAncestry) {
6147
6225
  const fullTreePayload = {
6148
6226
  ancestry_id: ancestryMode.currentAncestryId || "temp_root",
@@ -6185,6 +6263,7 @@ function CreateAncestryPanel({
6185
6263
  if (branchIndex !== -1) {
6186
6264
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6187
6265
  updateGlobalTree(rootTreeClone);
6266
+ setHasUnsavedChanges(true);
6188
6267
  try {
6189
6268
  setIsSaving(true);
6190
6269
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6206,6 +6285,7 @@ function CreateAncestryPanel({
6206
6285
  isPrivate,
6207
6286
  ancestryMode.abstraction_tree
6208
6287
  ));
6288
+ setHasUnsavedChanges(false);
6209
6289
  if (onClearAncestryVisuals) {
6210
6290
  onClearAncestryVisuals(currentStep.branchId);
6211
6291
  }
@@ -6238,6 +6318,7 @@ function CreateAncestryPanel({
6238
6318
  if (branchIndex !== -1) {
6239
6319
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6240
6320
  updateGlobalTree(rootTreeClone);
6321
+ setHasUnsavedChanges(true);
6241
6322
  try {
6242
6323
  setIsSaving(true);
6243
6324
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6259,6 +6340,7 @@ function CreateAncestryPanel({
6259
6340
  isPrivate,
6260
6341
  ancestryMode.abstraction_tree
6261
6342
  ));
6343
+ setHasUnsavedChanges(false);
6262
6344
  if (onClearAncestryVisuals) {
6263
6345
  onClearAncestryVisuals(currentStep.branchId);
6264
6346
  }
@@ -6520,6 +6602,7 @@ function CreateAncestryPanel({
6520
6602
  }
6521
6603
  setBranchStack(parentStack);
6522
6604
  setTargetScrollSectionId(targetFocusId);
6605
+ setHasUnsavedChanges(true);
6523
6606
  if (onRenderFullAncestry) {
6524
6607
  const parentStack2 = currentStack;
6525
6608
  const rotation = parentStack2.reduce((acc, step) => {
@@ -6581,7 +6664,6 @@ function CreateAncestryPanel({
6581
6664
  direction,
6582
6665
  tree: {
6583
6666
  node: nodeData,
6584
- node_id: nodeId,
6585
6667
  children: [],
6586
6668
  relationship: {}
6587
6669
  }
@@ -6603,6 +6685,7 @@ function CreateAncestryPanel({
6603
6685
  savedMaxIndex: parentIndexToSave,
6604
6686
  entryDirection: direction
6605
6687
  }]);
6688
+ setHasUnsavedChanges(true);
6606
6689
  if (branch && branch.tree && onRenderFullAncestry) {
6607
6690
  const branchAncestryObj = {
6608
6691
  ancestry_id: branch.id,
@@ -6653,6 +6736,10 @@ function CreateAncestryPanel({
6653
6736
  const currentInputName = overrides.ancestryName !== void 0 ? overrides.ancestryName : ancestryName;
6654
6737
  const currentInputDesc = overrides.description !== void 0 ? overrides.description : description;
6655
6738
  const currentInputSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
6739
+ if (!keepOpen && !hasUnsavedChanges) {
6740
+ onClose();
6741
+ return;
6742
+ }
6656
6743
  if (!currentInputName.trim()) {
6657
6744
  alert("O nome n\xE3o pode estar vazio.");
6658
6745
  return;
@@ -6660,11 +6747,7 @@ function CreateAncestryPanel({
6660
6747
  setIsSaving(true);
6661
6748
  const processedSections = processDescriptionForSave(currentInputDesc, currentInputSections);
6662
6749
  setExistingSections(processedSections);
6663
- const updatedRootTree = applyDescriptionToTree(
6664
- ancestryMode.tree,
6665
- currentInputDesc,
6666
- processedSections
6667
- );
6750
+ const updatedRootTree = JSON.parse(JSON.stringify(ancestryMode.tree));
6668
6751
  const extrasObj = {
6669
6752
  ...toObjectFromCustomProps(customProps.filter((p) => !p.isEditing)),
6670
6753
  is_private: isPrivate
@@ -6706,6 +6789,7 @@ function CreateAncestryPanel({
6706
6789
  isPrivate,
6707
6790
  ancestryMode.abstraction_tree
6708
6791
  ));
6792
+ setHasUnsavedChanges(false);
6709
6793
  if (onRenderFullAncestry) {
6710
6794
  const rotation = branchStack.reduce((acc, step) => {
6711
6795
  return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
@@ -6757,6 +6841,7 @@ function CreateAncestryPanel({
6757
6841
  updatedRootTree,
6758
6842
  extrasObj
6759
6843
  );
6844
+ setHasUnsavedChanges(false);
6760
6845
  setLastSavedSnapshot(takeSnapshot(
6761
6846
  updatedRootTree,
6762
6847
  currentInputName,
@@ -6779,6 +6864,7 @@ function CreateAncestryPanel({
6779
6864
  const newTreeString = JSON.stringify(newRootTree);
6780
6865
  if (currentTreeString !== newTreeString) {
6781
6866
  updateGlobalTree(newRootTree);
6867
+ setHasUnsavedChanges(true);
6782
6868
  }
6783
6869
  }, [description, existingSections]);
6784
6870
  const handleTriggerFullRender = () => {
@@ -6801,6 +6887,7 @@ function CreateAncestryPanel({
6801
6887
  };
6802
6888
  const handleSaveDescriptionInline = (newDesc) => {
6803
6889
  setDescription(newDesc);
6890
+ setHasUnsavedChanges(true);
6804
6891
  handleLocalSave(true, { description: newDesc });
6805
6892
  };
6806
6893
  const swallow = (e) => e.stopPropagation();
@@ -6930,7 +7017,11 @@ function CreateAncestryPanel({
6930
7017
  {
6931
7018
  type: "text",
6932
7019
  value: ancestryName,
6933
- onChange: (e) => setAncestryName(e.target.value),
7020
+ onChange: (e) => {
7021
+ setAncestryName(e.target.value);
7022
+ setHasUnsavedChanges(true);
7023
+ },
7024
+ readOnly: isContextLinked,
6934
7025
  placeholder: "Nome da Ancestralidade",
6935
7026
  className: "text-xl sm:text-2xl font-semibold tracking-tight bg-transparent border-none p-0 focus:ring-2 focus:ring-indigo-500 rounded-md -ml-1.5 px-1.5 w-full outline-none transition-all focus:bg-slate-800/70"
6936
7027
  }
@@ -7579,9 +7670,9 @@ function InSceneCreationForm({
7579
7670
  };
7580
7671
  const handleToggleImageMode = () => {
7581
7672
  var _a2, _b;
7582
- const newValue = !useImageAsTexture;
7583
- setUseImageAsTexture(newValue);
7584
- if (newValue) {
7673
+ const newValue2 = !useImageAsTexture;
7674
+ setUseImageAsTexture(newValue2);
7675
+ if (newValue2) {
7585
7676
  const firstImageProp = customProps.find((p) => p.type === "images");
7586
7677
  if (firstImageProp && ((_b = (_a2 = firstImageProp.value) == null ? void 0 : _a2[0]) == null ? void 0 : _b.value)) {
7587
7678
  const url = firstImageProp.value[0].value;
@@ -7879,9 +7970,9 @@ function InSceneVersionForm({
7879
7970
  };
7880
7971
  const handleToggleImageMode = () => {
7881
7972
  var _a, _b;
7882
- const newValue = !useImageAsTexture;
7883
- setUseImageAsTexture(newValue);
7884
- if (newValue) {
7973
+ const newValue2 = !useImageAsTexture;
7974
+ setUseImageAsTexture(newValue2);
7975
+ if (newValue2) {
7885
7976
  const firstImageProp = customProps.find((p) => p.type === "images");
7886
7977
  if (firstImageProp && ((_b = (_a = firstImageProp.value) == null ? void 0 : _a[0]) == null ? void 0 : _b.value)) {
7887
7978
  const url = firstImageProp.value[0].value;
@@ -8301,6 +8392,7 @@ function NodeDetailsPanel({
8301
8392
  return !!(node == null ? void 0 : node.useImageAsTexture);
8302
8393
  });
8303
8394
  const [selectedImageUrl, setSelectedImageUrl] = useState17((node == null ? void 0 : node.textureImageUrl) ?? null);
8395
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState17(false);
8304
8396
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8305
8397
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8306
8398
  initialWidth: isReadMode ? 700 : 440,
@@ -8338,6 +8430,7 @@ function NodeDetailsPanel({
8338
8430
  else if ((node == null ? void 0 : node.useImageAsTexture) === "false") setUseImageAsTexture(false);
8339
8431
  else setUseImageAsTexture(!!(node == null ? void 0 : node.useImageAsTexture));
8340
8432
  setSelectedImageUrl((node == null ? void 0 : node.textureImageUrl) ?? null);
8433
+ setHasUnsavedChanges(false);
8341
8434
  }
8342
8435
  }, [node]);
8343
8436
  const hasImages = customProps.some((p) => p.type === "images" && Array.isArray(p.value) && p.value.length > 0 && p.value.some((img) => img.value));
@@ -8364,6 +8457,7 @@ function NodeDetailsPanel({
8364
8457
  setIntensity(val);
8365
8458
  onIntensityChange == null ? void 0 : onIntensityChange(node.id, val);
8366
8459
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, intensity: val });
8460
+ setHasUnsavedChanges(true);
8367
8461
  };
8368
8462
  const handleCopyLink = () => {
8369
8463
  if (!(node == null ? void 0 : node.id)) return;
@@ -8381,14 +8475,17 @@ function NodeDetailsPanel({
8381
8475
  const v = e.target.value;
8382
8476
  setName(v);
8383
8477
  onNameChange == null ? void 0 : onNameChange(node.id, v);
8478
+ setHasUnsavedChanges(true);
8384
8479
  };
8385
8480
  const handleColorChange = (val) => {
8386
8481
  setColor(val);
8387
8482
  onColorChange == null ? void 0 : onColorChange(node.id, val);
8483
+ setHasUnsavedChanges(true);
8388
8484
  };
8389
8485
  const handleSizeChange = (newSize) => {
8390
8486
  setSize(newSize);
8391
8487
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8488
+ setHasUnsavedChanges(true);
8392
8489
  };
8393
8490
  const handleAddType = (newType) => {
8394
8491
  const trimmed = newType.trim();
@@ -8396,10 +8493,12 @@ function NodeDetailsPanel({
8396
8493
  setTypes([...types, trimmed]);
8397
8494
  setTypeInput("");
8398
8495
  setShowTypeSuggestions(false);
8496
+ setHasUnsavedChanges(true);
8399
8497
  }
8400
8498
  };
8401
8499
  const handleRemoveType = (indexToRemove) => {
8402
8500
  setTypes(types.filter((_, index) => index !== indexToRemove));
8501
+ setHasUnsavedChanges(true);
8403
8502
  };
8404
8503
  const handleTypeInputKeyDown = (e) => {
8405
8504
  if (e.key === "Enter") {
@@ -8412,6 +8511,7 @@ function NodeDetailsPanel({
8412
8511
  const handleAddProp = () => {
8413
8512
  const newProp = createNewCustomProperty(customProps);
8414
8513
  setCustomProps((p) => [...p, newProp]);
8514
+ setHasUnsavedChanges(true);
8415
8515
  setTimeout(() => {
8416
8516
  var _a;
8417
8517
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8420,19 +8520,21 @@ function NodeDetailsPanel({
8420
8520
  const handleRemoveProp = (i) => {
8421
8521
  const newProps = customProps.filter((_, idx) => idx !== i);
8422
8522
  setCustomProps(newProps);
8523
+ setHasUnsavedChanges(true);
8423
8524
  triggerAutoSave({ customProps: newProps });
8424
8525
  };
8425
8526
  const handleUpdateProp = (index, updatedProp) => {
8426
8527
  const newProps = [...customProps];
8427
8528
  newProps[index] = updatedProp;
8428
8529
  setCustomProps(newProps);
8530
+ setHasUnsavedChanges(true);
8429
8531
  if (!updatedProp.isEditing) {
8430
8532
  triggerAutoSave({ customProps: newProps });
8431
8533
  }
8432
8534
  };
8433
8535
  const handleToggleImageMode = () => {
8434
- const newValue = !useImageAsTexture;
8435
8536
  setUseImageAsTexture(newValue);
8537
+ setHasUnsavedChanges(true);
8436
8538
  let activeUrl = null;
8437
8539
  if (newValue) {
8438
8540
  const firstImageProp = customProps.find((p) => p.type === "images");
@@ -8454,6 +8556,7 @@ function NodeDetailsPanel({
8454
8556
  };
8455
8557
  const handleSelectTexture = (url) => {
8456
8558
  setSelectedImageUrl(url);
8559
+ setHasUnsavedChanges(true);
8457
8560
  onImageChange == null ? void 0 : onImageChange(true, url, color);
8458
8561
  onDataUpdate == null ? void 0 : onDataUpdate({
8459
8562
  ...node,
@@ -8464,6 +8567,7 @@ function NodeDetailsPanel({
8464
8567
  };
8465
8568
  const handleSaveDescriptionInline = (newDescription) => {
8466
8569
  setDescription(newDescription);
8570
+ setHasUnsavedChanges(true);
8467
8571
  onDataUpdate({ ...node, description: newDescription });
8468
8572
  triggerAutoSave({ description: newDescription });
8469
8573
  };
@@ -8474,6 +8578,10 @@ function NodeDetailsPanel({
8474
8578
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8475
8579
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8476
8580
  const currentIntensity = overrides.intensity !== void 0 ? overrides.intensity : intensity;
8581
+ if (!keepOpen && !hasUnsavedChanges) {
8582
+ onClose();
8583
+ return;
8584
+ }
8477
8585
  if (!currentName.trim() || currentTypes.length === 0) {
8478
8586
  alert("O campo 'Nome' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8479
8587
  return;
@@ -8498,6 +8606,7 @@ function NodeDetailsPanel({
8498
8606
  };
8499
8607
  await onSave(dataToSave, keepOpen);
8500
8608
  onDataUpdate(dataToSave);
8609
+ setHasUnsavedChanges(false);
8501
8610
  if (!keepOpen) {
8502
8611
  onClose();
8503
8612
  }
@@ -8564,7 +8673,7 @@ function NodeDetailsPanel({
8564
8673
  onImageClick: handleImageClickFromText,
8565
8674
  onSaveDescription: handleSaveDescriptionInline
8566
8675
  }
8567
- ) : /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0" }), /* @__PURE__ */ React16.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React16.createElement("div", null, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React16.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80 shadow-[0_0_18px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React16.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes do Node"), /* @__PURE__ */ React16.createElement(
8676
+ ) : /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0" }), /* @__PURE__ */ React16.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React16.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React16.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80 shadow-[0_0_18px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React16.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes do Node"), /* @__PURE__ */ React16.createElement(
8568
8677
  "button",
8569
8678
  {
8570
8679
  onClick: handleCopyLink,
@@ -8572,7 +8681,7 @@ function NodeDetailsPanel({
8572
8681
  title: isLinkCopied ? "Link Copiado!" : "Copiar link para este Node"
8573
8682
  },
8574
8683
  isLinkCopied ? /* @__PURE__ */ React16.createElement(FiCheck10, { size: 12 }) : /* @__PURE__ */ React16.createElement(FiLink5, { size: 12 })
8575
- )), /* @__PURE__ */ React16.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || (node == null ? void 0 : node.name))), /* @__PURE__ */ React16.createElement("button", { onClick: handleCancel, disabled: isSaving, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Cancelar" }, "\xD7")), /* @__PURE__ */ React16.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-xs text-slate-300" }, "Tipos"), /* @__PURE__ */ React16.createElement("div", { className: `relative w-full bg-slate-800/70 p-1.5 min-h-[42px] flex flex-wrap gap-1.5 rounded-lg border border-white/10 ${canEdit ? "focus-within:ring-2 focus-within:ring-indigo-400/60" : ""} transition-all` }, types.map((t, index) => /* @__PURE__ */ React16.createElement("span", { key: index, className: "flex items-center gap-1 bg-indigo-500/30 text-indigo-100 px-1.5 py-0.5 rounded-md text-xs font-medium border border-indigo-500/20" }, t, canEdit && /* @__PURE__ */ React16.createElement(
8684
+ )), /* @__PURE__ */ React16.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || (node == null ? void 0 : node.name))), /* @__PURE__ */ React16.createElement("button", { onClick: handleCancel, disabled: isSaving, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Cancelar" }, "\xD7")), /* @__PURE__ */ React16.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-xs text-slate-300" }, "Tipos"), /* @__PURE__ */ React16.createElement("div", { className: `relative w-full bg-slate-800/70 p-1.5 min-h-[42px] flex flex-wrap gap-1.5 rounded-lg border border-white/10 ${canEdit ? "focus-within:ring-2 focus-within:ring-indigo-400/60" : ""} transition-all` }, types.map((t, index) => /* @__PURE__ */ React16.createElement("span", { key: index, className: "flex items-center gap-1 bg-indigo-500/30 text-indigo-100 px-1.5 py-0.5 rounded-md text-xs font-medium border border-indigo-500/20" }, t, canEdit && /* @__PURE__ */ React16.createElement(
8576
8685
  "button",
8577
8686
  {
8578
8687
  type: "button",
@@ -8734,6 +8843,7 @@ function NodeDetailsPanel({
8734
8843
  initialValue: description,
8735
8844
  onSave: (newDescription) => {
8736
8845
  setDescription(newDescription);
8846
+ setHasUnsavedChanges(true);
8737
8847
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
8738
8848
  triggerAutoSave({ description: newDescription });
8739
8849
  },
@@ -8793,6 +8903,7 @@ function QuestDetailsPanel({
8793
8903
  const [existingSections, setExistingSections] = useState18((node == null ? void 0 : node.description_sections) || []);
8794
8904
  const [isSaving, setIsSaving] = useState18(false);
8795
8905
  const [isLinkCopied, setIsLinkCopied] = useState18(false);
8906
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState18(false);
8796
8907
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8797
8908
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8798
8909
  initialWidth: isReadMode ? 700 : 440,
@@ -8824,6 +8935,7 @@ function QuestDetailsPanel({
8824
8935
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8825
8936
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8826
8937
  setCustomProps(extractCustomPropsFromNode(node || {}));
8938
+ setHasUnsavedChanges(false);
8827
8939
  }
8828
8940
  }, [node]);
8829
8941
  useEffect16(() => {
@@ -8855,16 +8967,19 @@ function QuestDetailsPanel({
8855
8967
  setRawTitle(val);
8856
8968
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8857
8969
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8970
+ setHasUnsavedChanges(true);
8858
8971
  };
8859
8972
  const handleSizeChange = (newSize) => {
8860
8973
  setSize(newSize);
8861
8974
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8975
+ setHasUnsavedChanges(true);
8862
8976
  };
8863
8977
  const handleStatusChange = (newStatus) => {
8864
8978
  setStatus(newStatus);
8865
8979
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8866
8980
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8867
8981
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8982
+ setHasUnsavedChanges(true);
8868
8983
  };
8869
8984
  const handleAddType = (newType) => {
8870
8985
  const trimmed = newType.trim();
@@ -8872,11 +8987,13 @@ function QuestDetailsPanel({
8872
8987
  setTypes([...types, trimmed]);
8873
8988
  setTypeInput("");
8874
8989
  setShowTypeSuggestions(false);
8990
+ setHasUnsavedChanges(true);
8875
8991
  }
8876
8992
  };
8877
8993
  const handleRemoveType = (indexToRemove) => {
8878
8994
  if (types[indexToRemove] === "quest") return;
8879
8995
  setTypes(types.filter((_, index) => index !== indexToRemove));
8996
+ setHasUnsavedChanges(true);
8880
8997
  };
8881
8998
  const handleTypeInputKeyDown = (e) => {
8882
8999
  if (e.key === "Enter") {
@@ -8889,6 +9006,7 @@ function QuestDetailsPanel({
8889
9006
  const handleAddProp = () => {
8890
9007
  const newProp = createNewCustomProperty(customProps);
8891
9008
  setCustomProps((p) => [...p, newProp]);
9009
+ setHasUnsavedChanges(true);
8892
9010
  setTimeout(() => {
8893
9011
  var _a2;
8894
9012
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8897,18 +9015,21 @@ function QuestDetailsPanel({
8897
9015
  const handleRemoveProp = (i) => {
8898
9016
  const newProps = customProps.filter((_, idx) => idx !== i);
8899
9017
  setCustomProps(newProps);
9018
+ setHasUnsavedChanges(true);
8900
9019
  triggerAutoSave({ customProps: newProps });
8901
9020
  };
8902
9021
  const handleUpdateProp = (index, updatedProp) => {
8903
9022
  const newProps = [...customProps];
8904
9023
  newProps[index] = updatedProp;
8905
9024
  setCustomProps(newProps);
9025
+ setHasUnsavedChanges(true);
8906
9026
  if (!updatedProp.isEditing) {
8907
9027
  triggerAutoSave({ customProps: newProps });
8908
9028
  }
8909
9029
  };
8910
9030
  const handleSaveDescriptionInline = (newDescription) => {
8911
9031
  setDescription(newDescription);
9032
+ setHasUnsavedChanges(true);
8912
9033
  onDataUpdate({ ...node, description: newDescription });
8913
9034
  triggerAutoSave({ description: newDescription });
8914
9035
  };
@@ -8920,6 +9041,10 @@ function QuestDetailsPanel({
8920
9041
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8921
9042
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8922
9043
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9044
+ if (!keepOpen && !hasUnsavedChanges) {
9045
+ onClose();
9046
+ return;
9047
+ }
8923
9048
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8924
9049
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8925
9050
  return;
@@ -8949,6 +9074,7 @@ function QuestDetailsPanel({
8949
9074
  };
8950
9075
  await onSave(dataToSave, keepOpen);
8951
9076
  onDataUpdate(dataToSave);
9077
+ setHasUnsavedChanges(false);
8952
9078
  if (!keepOpen) {
8953
9079
  onClose();
8954
9080
  }
@@ -9006,7 +9132,7 @@ function QuestDetailsPanel({
9006
9132
  onImageClick: handleImageClickFromText,
9007
9133
  onSaveDescription: handleSaveDescriptionInline
9008
9134
  }
9009
- ) : /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "h-[2px]", style: { background: `linear-gradient(to right, transparent, ${QUEST_STATUS_COLORS3[status]}, transparent)` } }), /* @__PURE__ */ React17.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React17.createElement(FiTarget2, { className: "text-sky-400", size: 14 }), /* @__PURE__ */ React17.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Quest"), /* @__PURE__ */ React17.createElement("button", { onClick: handleCopyLink, className: `ml-1 p-1 transition-colors ${isLinkCopied ? "text-green-400" : "text-slate-400 hover:text-sky-400"}`, title: isLinkCopied ? "Link Copiado!" : "Copiar link para esta Quest" }, isLinkCopied ? /* @__PURE__ */ React17.createElement(FiCheck11, { size: 12 }) : /* @__PURE__ */ React17.createElement(FiLink6, { size: 12 }))), /* @__PURE__ */ React17.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, standardizedName || (node == null ? void 0 : node.name))), /* @__PURE__ */ React17.createElement("button", { onClick: handleCancel, disabled: isSaving, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Cancelar" }, "\xD7")), /* @__PURE__ */ React17.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React17.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React17.createElement("label", { className: "text-xs text-slate-300" }, "T\xEDtulo da Quest"), /* @__PURE__ */ React17.createElement(
9135
+ ) : /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: "h-[2px]", style: { background: `linear-gradient(to right, transparent, ${QUEST_STATUS_COLORS3[status]}, transparent)` } }), /* @__PURE__ */ React17.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React17.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React17.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React17.createElement(FiTarget2, { className: "text-sky-400", size: 14 }), /* @__PURE__ */ React17.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Quest"), /* @__PURE__ */ React17.createElement("button", { onClick: handleCopyLink, className: `ml-1 p-1 transition-colors ${isLinkCopied ? "text-green-400" : "text-slate-400 hover:text-sky-400"}`, title: isLinkCopied ? "Link Copiado!" : "Copiar link para esta Quest" }, isLinkCopied ? /* @__PURE__ */ React17.createElement(FiCheck11, { size: 12 }) : /* @__PURE__ */ React17.createElement(FiLink6, { size: 12 }))), /* @__PURE__ */ React17.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, standardizedName || (node == null ? void 0 : node.name))), /* @__PURE__ */ React17.createElement("button", { onClick: handleCancel, disabled: isSaving, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Cancelar" }, "\xD7")), /* @__PURE__ */ React17.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React17.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React17.createElement("label", { className: "text-xs text-slate-300" }, "T\xEDtulo da Quest"), /* @__PURE__ */ React17.createElement(
9010
9136
  "input",
9011
9137
  {
9012
9138
  type: "text",
@@ -9180,9 +9306,12 @@ function RelationshipDetailsPanel({
9180
9306
  const [description, setDescription] = useState20((link == null ? void 0 : link.description) ?? "");
9181
9307
  const [customProps, setCustomProps] = useState20(() => extractCustomPropsFromNode(link || {}));
9182
9308
  const [existingSections, setExistingSections] = useState20((link == null ? void 0 : link.description_sections) || []);
9309
+ const [sourceLabel, setSourceLabel] = useState20((link == null ? void 0 : link.source_label) ?? "");
9310
+ const [targetLabel, setTargetLabel] = useState20((link == null ? void 0 : link.target_label) ?? "");
9183
9311
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState20(false);
9184
9312
  const [isSaving, setIsSaving] = useState20(false);
9185
9313
  const [isReadMode, setIsReadMode] = useState20(false);
9314
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState20(false);
9186
9315
  const propsEndRef = useRef16(null);
9187
9316
  const canEdit = useMemo9(() => {
9188
9317
  const ability = defineAbilityFor(userRole);
@@ -9193,12 +9322,16 @@ function RelationshipDetailsPanel({
9193
9322
  setDescription((link == null ? void 0 : link.description) ?? "");
9194
9323
  setExistingSections((link == null ? void 0 : link.description_sections) || []);
9195
9324
  setCustomProps(extractCustomPropsFromNode(link || {}));
9325
+ setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9326
+ setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9327
+ setHasUnsavedChanges(false);
9196
9328
  }, [link]);
9197
9329
  const swallow = (e) => e.stopPropagation();
9198
9330
  const handleAddProp = () => {
9199
9331
  if (!canEdit) return;
9200
9332
  const newProp = createNewCustomProperty(customProps);
9201
9333
  setCustomProps((p) => [...p, newProp]);
9334
+ setHasUnsavedChanges(true);
9202
9335
  setTimeout(() => {
9203
9336
  var _a;
9204
9337
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9210,6 +9343,12 @@ function RelationshipDetailsPanel({
9210
9343
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
9211
9344
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
9212
9345
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9346
+ const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9347
+ const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9348
+ if (!keepOpen && !hasUnsavedChanges) {
9349
+ onClose();
9350
+ return;
9351
+ }
9213
9352
  setIsSaving(true);
9214
9353
  try {
9215
9354
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9225,8 +9364,11 @@ function RelationshipDetailsPanel({
9225
9364
  isCurved: link.isCurved,
9226
9365
  curveOffset: link.curveOffset
9227
9366
  };
9367
+ if (currentSourceLabel.trim()) dataToSave.source_label = currentSourceLabel.trim();
9368
+ if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9228
9369
  await onSave(dataToSave, keepOpen);
9229
9370
  onDataUpdate(dataToSave);
9371
+ setHasUnsavedChanges(false);
9230
9372
  if (!keepOpen) {
9231
9373
  onClose();
9232
9374
  }
@@ -9240,18 +9382,21 @@ function RelationshipDetailsPanel({
9240
9382
  const handleSaveDescriptionInline = (newDescription) => {
9241
9383
  if (!canEdit) return;
9242
9384
  setDescription(newDescription);
9385
+ setHasUnsavedChanges(true);
9243
9386
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9244
9387
  triggerAutoSave({ description: newDescription });
9245
9388
  };
9246
9389
  const handleRemoveProp = (i) => {
9247
9390
  const newProps = customProps.filter((_, idx) => idx !== i);
9248
9391
  setCustomProps(newProps);
9392
+ setHasUnsavedChanges(true);
9249
9393
  triggerAutoSave({ customProps: newProps });
9250
9394
  };
9251
9395
  const handleUpdateProp = (index, updatedProp) => {
9252
9396
  const newProps = [...customProps];
9253
9397
  newProps[index] = updatedProp;
9254
9398
  setCustomProps(newProps);
9399
+ setHasUnsavedChanges(true);
9255
9400
  if (!updatedProp.isEditing) {
9256
9401
  triggerAutoSave({ customProps: newProps });
9257
9402
  }
@@ -9299,19 +9444,52 @@ function RelationshipDetailsPanel({
9299
9444
  onImageClick: handleImageClickFromText,
9300
9445
  onSaveDescription: handleSaveDescriptionInline
9301
9446
  }
9302
- ) : /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("div", { className: "h-[2px] bg-gradient-to-r from-teal-400/0 via-teal-400/70 to-teal-400/0" }), /* @__PURE__ */ React19.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React19.createElement("div", null, /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React19.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-teal-400/80 shadow-[0_0_18px_2px_rgba(45,212,191,0.55)]" }), /* @__PURE__ */ React19.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o")), /* @__PURE__ */ React19.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || "Rela\xE7\xE3o")), /* @__PURE__ */ React19.createElement("button", { onClick: onClose, disabled: isSaving, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React19.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Rela\xE7\xE3o (Opcional)"), /* @__PURE__ */ React19.createElement(
9447
+ ) : /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("div", { className: "h-[2px] bg-gradient-to-r from-teal-400/0 via-teal-400/70 to-teal-400/0" }), /* @__PURE__ */ React19.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React19.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React19.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-teal-400/80 shadow-[0_0_18px_2px_rgba(45,212,191,0.55)]" }), /* @__PURE__ */ React19.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o")), /* @__PURE__ */ React19.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || "Rela\xE7\xE3o")), /* @__PURE__ */ React19.createElement("button", { onClick: onClose, disabled: isSaving, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl disabled:opacity-50", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React19.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Rela\xE7\xE3o (Opcional)"), /* @__PURE__ */ React19.createElement(
9303
9448
  "input",
9304
9449
  {
9305
9450
  type: "text",
9306
9451
  value: name,
9307
- onChange: (e) => setName(e.target.value),
9452
+ onChange: (e) => {
9453
+ setName(e.target.value);
9454
+ setHasUnsavedChanges(true);
9455
+ },
9308
9456
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9309
9457
  disabled: !canEdit,
9310
9458
  className: `w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400/60
9311
9459
  ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9312
9460
  `
9313
9461
  }
9314
- )), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React19.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React19.createElement(
9462
+ )), /* @__PURE__ */ React19.createElement("div", { className: "rounded-xl border border-white/8 bg-slate-800/30 p-3 space-y-3" }, /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React19.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ React19.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React19.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ React19.createElement("p", { className: "text-xs font-medium text-violet-300/80 uppercase tracking-wider" }, "Labels de Agrupamento")), /* @__PURE__ */ React19.createElement("p", { className: "text-[11px] text-slate-400 leading-relaxed" }, "Labels agrupam conex\xF5es no menu de Conex\xF5es. Cada lado da conex\xE3o pode ter sua pr\xF3pria label."), /* @__PURE__ */ React19.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ React19.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React19.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Source"), /* @__PURE__ */ React19.createElement(
9463
+ "input",
9464
+ {
9465
+ type: "text",
9466
+ value: sourceLabel,
9467
+ onChange: (e) => {
9468
+ setSourceLabel(e.target.value);
9469
+ setHasUnsavedChanges(true);
9470
+ },
9471
+ placeholder: "Ex: Conceitos",
9472
+ disabled: !canEdit,
9473
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9474
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9475
+ `
9476
+ }
9477
+ )), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React19.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Target"), /* @__PURE__ */ React19.createElement(
9478
+ "input",
9479
+ {
9480
+ type: "text",
9481
+ value: targetLabel,
9482
+ onChange: (e) => {
9483
+ setTargetLabel(e.target.value);
9484
+ setHasUnsavedChanges(true);
9485
+ },
9486
+ placeholder: "Ex: Refer\xEAncias",
9487
+ disabled: !canEdit,
9488
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9489
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9490
+ `
9491
+ }
9492
+ )))), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React19.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React19.createElement(
9315
9493
  DescriptionDisplay,
9316
9494
  {
9317
9495
  description,
@@ -9381,6 +9559,7 @@ function RelationshipDetailsPanel({
9381
9559
  onSave: (newDescription) => {
9382
9560
  if (!canEdit) return;
9383
9561
  setDescription(newDescription);
9562
+ setHasUnsavedChanges(true);
9384
9563
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9385
9564
  triggerAutoSave({ description: newDescription });
9386
9565
  },
@@ -9828,7 +10007,7 @@ function AncestryLinkDetailsPanel({ data, onClose, onOpenImageViewer, onOpenRefe
9828
10007
  onMentionClick,
9829
10008
  onImageClick: handleImageClickFromText
9830
10009
  }
9831
- ) : /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement("div", { className: "h-[2px] bg-gradient-to-r from-blue-500/0 via-blue-500/70 to-blue-500/0" }), /* @__PURE__ */ React23.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React23.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-blue-500/80 shadow-[0_0_18px_2px_rgba(59,130,246,0.55)]" }), /* @__PURE__ */ React23.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Ancestralidade")), /* @__PURE__ */ React23.createElement("h2", { className: "text-lg font-semibold tracking-tight flex items-center gap-2" }, /* @__PURE__ */ React23.createElement("span", { className: "truncate max-w-[150px]" }, sourceName), /* @__PURE__ */ React23.createElement("span", { className: "text-slate-500 text-sm" }, "\u2794"), /* @__PURE__ */ React23.createElement("span", { className: "truncate max-w-[150px]" }, targetName))), /* @__PURE__ */ React23.createElement("button", { onClick: onClose, className: "w-9 h-9 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React23.createElement("div", { className: "px-6 pb-6 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, description && /* @__PURE__ */ React23.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React23.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React23.createElement("label", { className: "text-xs text-slate-300 font-medium" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React23.createElement(
10010
+ ) : /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement("div", { className: "h-[2px] bg-gradient-to-r from-blue-500/0 via-blue-500/70 to-blue-500/0" }), /* @__PURE__ */ React23.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ React23.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React23.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React23.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-blue-500/80 shadow-[0_0_18px_2px_rgba(59,130,246,0.55)]" }), /* @__PURE__ */ React23.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Ancestralidade")), /* @__PURE__ */ React23.createElement("h2", { className: "text-lg font-semibold tracking-tight flex items-center gap-2" }, /* @__PURE__ */ React23.createElement("span", { className: "truncate max-w-[150px]" }, sourceName), /* @__PURE__ */ React23.createElement("span", { className: "text-slate-500 text-sm" }, "\u2794"), /* @__PURE__ */ React23.createElement("span", { className: "truncate max-w-[150px]" }, targetName))), /* @__PURE__ */ React23.createElement("button", { onClick: onClose, className: "w-9 h-9 flex-shrink-0 grid place-content-center rounded-lg border border-white/15 bg-transparent hover:bg-white/5 transition-colors text-xl", title: "Fechar" }, "\xD7")), /* @__PURE__ */ React23.createElement("div", { className: "px-6 pb-6 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, description && /* @__PURE__ */ React23.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React23.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React23.createElement("label", { className: "text-xs text-slate-300 font-medium" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React23.createElement(
9832
10011
  "button",
9833
10012
  {
9834
10013
  onClick: () => setIsReadMode(true),
@@ -9890,7 +10069,6 @@ var GroupItem = ({
9890
10069
  onPlayAncestry,
9891
10070
  availableIds,
9892
10071
  canEdit
9893
- // [NOVO] Recebe permissão de edição
9894
10072
  }) => {
9895
10073
  const canIndent = index > 0;
9896
10074
  const isPickingForThisGroup = pickingGroupId === group.id;
@@ -9905,107 +10083,126 @@ var GroupItem = ({
9905
10083
  useEffect21(() => {
9906
10084
  adjustHeight();
9907
10085
  }, [group.text]);
9908
- return /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col gap-2 mb-3 pl-3 border-l border-white/10 relative group/item animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React24.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ React24.createElement("div", { className: `
10086
+ return /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col gap-2 mb-3 pl-3 border-l border-white/10 relative group/item animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React24.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ React24.createElement(
10087
+ "div",
10088
+ {
10089
+ className: `
9909
10090
  flex flex-col gap-2 py-2 px-3 transition-all duration-200
9910
10091
  ${isPickingForThisGroup ? "bg-indigo-500/10 border-l-2 border-indigo-500" : "hover:bg-white/5 border-l-2 border-transparent hover:border-white/20"}
9911
- ` }, /* @__PURE__ */ React24.createElement(
9912
- "textarea",
9913
- {
9914
- ref: textareaRef,
9915
- className: `w-full bg-transparent text-sm text-slate-200 placeholder-slate-600 resize-none focus:outline-none focus:placeholder-slate-500 overflow-y-auto ${!canEdit ? "cursor-default" : ""}`,
9916
- rows: 1,
9917
- style: {
9918
- minHeight: "1.5rem",
9919
- maxHeight: "250px"
9920
- },
9921
- placeholder: canEdit ? "Escreva sobre este grupo..." : "",
9922
- value: group.text,
9923
- readOnly: !canEdit,
9924
- onChange: (e) => {
9925
- if (canEdit) onUpdate(group.id, { ...group, text: e.target.value });
9926
- }
9927
- }
9928
- ), group.ancestries && group.ancestries.length > 0 && /* @__PURE__ */ React24.createElement("div", { className: "flex flex-wrap gap-2 mt-1" }, group.ancestries.map((anc) => {
9929
- const isValid = availableIds.has(String(anc.ancestry_id));
9930
- return /* @__PURE__ */ React24.createElement(
9931
- "div",
10092
+ `
10093
+ },
10094
+ /* @__PURE__ */ React24.createElement(
10095
+ "textarea",
9932
10096
  {
9933
- key: anc.ancestry_id,
9934
- className: `
10097
+ ref: textareaRef,
10098
+ className: `w-full bg-transparent text-sm text-slate-200 placeholder-slate-600 resize-none focus:outline-none focus:placeholder-slate-500 overflow-y-auto ${!canEdit ? "cursor-default" : ""}`,
10099
+ rows: 1,
10100
+ style: {
10101
+ minHeight: "1.5rem",
10102
+ maxHeight: "250px"
10103
+ },
10104
+ placeholder: canEdit ? "Escreva sobre este grupo..." : "",
10105
+ value: group.text,
10106
+ readOnly: !canEdit,
10107
+ onChange: (e) => {
10108
+ if (canEdit) onUpdate(group.id, { ...group, text: e.target.value });
10109
+ }
10110
+ }
10111
+ ),
10112
+ group.ancestries && group.ancestries.length > 0 && /* @__PURE__ */ React24.createElement("div", { className: "flex flex-wrap gap-2 mt-1" }, group.ancestries.map((anc) => {
10113
+ const isValid = availableIds.has(String(anc.ancestry_id));
10114
+ return /* @__PURE__ */ React24.createElement(
10115
+ "div",
10116
+ {
10117
+ key: anc.ancestry_id,
10118
+ className: `
9935
10119
  flex items-center gap-2 rounded-md px-3 py-1.5 text-xs transition-all group/card border
9936
10120
  ${isValid ? "bg-slate-800/60 border-white/10 hover:border-indigo-500/30 text-slate-200" : "bg-red-900/10 border-red-500/30 text-red-300/70 hover:bg-red-900/20"}
9937
10121
  `,
9938
- title: isValid ? "" : "Esta ancestralidade foi removida ou n\xE3o existe mais."
9939
- },
9940
- isValid ? (
9941
- // [MANTIDO] Botão Play visível para todos
10122
+ title: isValid ? "" : "Esta ancestralidade foi removida ou n\xE3o existe mais."
10123
+ },
10124
+ isValid ? (
10125
+ // [MANTIDO] Botão Play visível para todos
10126
+ /* @__PURE__ */ React24.createElement(
10127
+ "button",
10128
+ {
10129
+ onClick: () => onPlayAncestry(anc.ancestry_id),
10130
+ className: "text-indigo-400 hover:text-white hover:bg-indigo-500 p-1 rounded-full transition-colors",
10131
+ title: "Renderizar no cen\xE1rio"
10132
+ },
10133
+ /* @__PURE__ */ React24.createElement(FiPlay, { size: 10, className: "ml-0.5 fill-current" })
10134
+ )
10135
+ ) : /* @__PURE__ */ React24.createElement("div", { className: "p-1 text-red-500 cursor-not-allowed" }, /* @__PURE__ */ React24.createElement(FiAlertTriangle, { size: 10 })),
9942
10136
  /* @__PURE__ */ React24.createElement(
10137
+ "span",
10138
+ {
10139
+ className: `font-medium truncate max-w-[150px] ${!isValid && "line-through decoration-red-500/50"}`
10140
+ },
10141
+ anc.name
10142
+ ),
10143
+ canEdit && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(
10144
+ "div",
10145
+ {
10146
+ className: `w-px h-3 mx-0.5 ${isValid ? "bg-white/10" : "bg-red-500/20"}`
10147
+ }
10148
+ ), /* @__PURE__ */ React24.createElement(
9943
10149
  "button",
9944
10150
  {
9945
- onClick: () => onPlayAncestry(anc.ancestry_id),
9946
- className: "text-indigo-400 hover:text-white hover:bg-indigo-500 p-1 rounded-full transition-colors",
9947
- title: "Renderizar no cen\xE1rio"
10151
+ onClick: () => onRemoveAncestry(group.id, anc.ancestry_id),
10152
+ className: `${isValid ? "text-slate-500 hover:text-red-400" : "text-red-400 hover:text-red-200"} p-0.5 rounded transition-colors`,
10153
+ title: "Remover men\xE7\xE3o"
9948
10154
  },
9949
- /* @__PURE__ */ React24.createElement(FiPlay, { size: 10, className: "ml-0.5 fill-current" })
9950
- )
9951
- ) : /* @__PURE__ */ React24.createElement("div", { className: "p-1 text-red-500 cursor-not-allowed" }, /* @__PURE__ */ React24.createElement(FiAlertTriangle, { size: 10 })),
9952
- /* @__PURE__ */ React24.createElement("span", { className: `font-medium truncate max-w-[150px] ${!isValid && "line-through decoration-red-500/50"}` }, anc.name),
9953
- canEdit && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement("div", { className: `w-px h-3 mx-0.5 ${isValid ? "bg-white/10" : "bg-red-500/20"}` }), /* @__PURE__ */ React24.createElement(
9954
- "button",
9955
- {
9956
- onClick: () => onRemoveAncestry(group.id, anc.ancestry_id),
9957
- className: `${isValid ? "text-slate-500 hover:text-red-400" : "text-red-400 hover:text-red-200"} p-0.5 rounded transition-colors`,
9958
- title: "Remover men\xE7\xE3o"
9959
- },
9960
- /* @__PURE__ */ React24.createElement(FiX7, { size: 12 })
9961
- ))
9962
- );
9963
- })), canEdit && /* @__PURE__ */ React24.createElement("div", { className: "flex items-center justify-between pt-2 mt-1 border-t border-white/5 opacity-40 group-hover/item:opacity-100 transition-opacity" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React24.createElement(
9964
- "button",
9965
- {
9966
- onClick: () => onRequestPickAncestry(group.id),
9967
- className: `
10155
+ /* @__PURE__ */ React24.createElement(FiX7, { size: 12 })
10156
+ ))
10157
+ );
10158
+ })),
10159
+ canEdit && /* @__PURE__ */ React24.createElement("div", { className: "flex items-center justify-between pt-2 mt-1 border-t border-white/5 opacity-40 group-hover/item:opacity-100 transition-opacity" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React24.createElement(
10160
+ "button",
10161
+ {
10162
+ onClick: () => onRequestPickAncestry(group.id),
10163
+ className: `
9968
10164
  flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors
9969
10165
  ${isPickingForThisGroup ? "text-indigo-300 animate-pulse" : "text-slate-500 hover:text-indigo-300 hover:bg-indigo-500/10"}
9970
10166
  `,
9971
- title: "Adicionar Ancestralidade a este grupo"
9972
- },
9973
- isPickingForThisGroup ? /* @__PURE__ */ React24.createElement(FiCheckCircle, { size: 12 }) : /* @__PURE__ */ React24.createElement(FiSearch4, { size: 12 }),
9974
- isPickingForThisGroup ? "Selecionando..." : "Adicionar"
9975
- ), /* @__PURE__ */ React24.createElement(
9976
- "button",
9977
- {
9978
- onClick: () => onAddSubgroup(group.id),
9979
- className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
9980
- title: "Criar Subgrupo"
9981
- },
9982
- /* @__PURE__ */ React24.createElement(FiPlus9, { size: 14 })
9983
- )), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React24.createElement(
9984
- "button",
9985
- {
9986
- onClick: () => onIndent(group.id),
9987
- disabled: !canIndent,
9988
- className: `p-1.5 rounded transition-colors ${!canIndent ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`,
9989
- title: "Aninhar no grupo acima"
9990
- },
9991
- /* @__PURE__ */ React24.createElement(FiArrowRight, { size: 14 })
9992
- ), /* @__PURE__ */ React24.createElement(
9993
- "button",
9994
- {
9995
- onClick: () => onOutdent(group.id),
9996
- className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
9997
- title: "Desaninhar"
9998
- },
9999
- /* @__PURE__ */ React24.createElement(FiArrowLeft3, { size: 14 })
10000
- ), /* @__PURE__ */ React24.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ React24.createElement(
10001
- "button",
10002
- {
10003
- onClick: () => onDelete(group.id),
10004
- className: "p-1.5 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors",
10005
- title: "Remover Grupo"
10006
- },
10007
- /* @__PURE__ */ React24.createElement(FiTrash23, { size: 14 })
10008
- )))), group.children && group.children.length > 0 && /* @__PURE__ */ React24.createElement("div", { className: "ml-2" }, group.children.map((childGroup, idx) => /* @__PURE__ */ React24.createElement(
10167
+ title: "Adicionar Ancestralidade a este grupo"
10168
+ },
10169
+ isPickingForThisGroup ? /* @__PURE__ */ React24.createElement(FiCheckCircle, { size: 12 }) : /* @__PURE__ */ React24.createElement(FiSearch4, { size: 12 }),
10170
+ isPickingForThisGroup ? "Selecionando..." : "Adicionar"
10171
+ ), /* @__PURE__ */ React24.createElement(
10172
+ "button",
10173
+ {
10174
+ onClick: () => onAddSubgroup(group.id),
10175
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
10176
+ title: "Criar Subgrupo"
10177
+ },
10178
+ /* @__PURE__ */ React24.createElement(FiPlus9, { size: 14 })
10179
+ )), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React24.createElement(
10180
+ "button",
10181
+ {
10182
+ onClick: () => onIndent(group.id),
10183
+ disabled: !canIndent,
10184
+ className: `p-1.5 rounded transition-colors ${!canIndent ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`,
10185
+ title: "Aninhar no grupo acima"
10186
+ },
10187
+ /* @__PURE__ */ React24.createElement(FiArrowRight, { size: 14 })
10188
+ ), /* @__PURE__ */ React24.createElement(
10189
+ "button",
10190
+ {
10191
+ onClick: () => onOutdent(group.id),
10192
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
10193
+ title: "Desaninhar"
10194
+ },
10195
+ /* @__PURE__ */ React24.createElement(FiArrowLeft3, { size: 14 })
10196
+ ), /* @__PURE__ */ React24.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ React24.createElement(
10197
+ "button",
10198
+ {
10199
+ onClick: () => onDelete(group.id),
10200
+ className: "p-1.5 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors",
10201
+ title: "Remover Grupo"
10202
+ },
10203
+ /* @__PURE__ */ React24.createElement(FiTrash23, { size: 14 })
10204
+ )))
10205
+ ), group.children && group.children.length > 0 && /* @__PURE__ */ React24.createElement("div", { className: "ml-2" }, group.children.map((childGroup, idx) => /* @__PURE__ */ React24.createElement(
10009
10206
  GroupItem,
10010
10207
  {
10011
10208
  key: childGroup.id,
@@ -10214,7 +10411,9 @@ function AncestryBoard({
10214
10411
  const addRecursive = (list) => {
10215
10412
  return list.map((g) => {
10216
10413
  if (g.id === pickingGroupId) {
10217
- const exists = g.ancestries.some((a) => a.ancestry_id === ancestry.ancestry_id);
10414
+ const exists = g.ancestries.some(
10415
+ (a) => a.ancestry_id === ancestry.ancestry_id
10416
+ );
10218
10417
  if (exists) return g;
10219
10418
  return { ...g, ancestries: [...g.ancestries, ancestry] };
10220
10419
  }
@@ -10225,12 +10424,16 @@ function AncestryBoard({
10225
10424
  });
10226
10425
  setPickingGroupId(null);
10227
10426
  } else {
10228
- const fullAncestry = availableAncestries.find((a) => a.ancestry_id === ancestry.ancestry_id) || ancestry;
10427
+ const fullAncestry = availableAncestries.find(
10428
+ (a) => a.ancestry_id === ancestry.ancestry_id
10429
+ ) || ancestry;
10229
10430
  onSelect(fullAncestry);
10230
10431
  }
10231
10432
  };
10232
10433
  const handlePlayFromGroup = (ancestryId) => {
10233
- const fullAncestry = availableAncestries.find((a) => a.ancestry_id === ancestryId);
10434
+ const fullAncestry = availableAncestries.find(
10435
+ (a) => a.ancestry_id === ancestryId
10436
+ );
10234
10437
  if (fullAncestry && onSelect) {
10235
10438
  onSelect(fullAncestry);
10236
10439
  }
@@ -10240,7 +10443,12 @@ function AncestryBoard({
10240
10443
  const removeRecursive = (list) => {
10241
10444
  return list.map((g) => {
10242
10445
  if (g.id === groupId) {
10243
- return { ...g, ancestries: g.ancestries.filter((a) => a.ancestry_id !== ancestryId) };
10446
+ return {
10447
+ ...g,
10448
+ ancestries: g.ancestries.filter(
10449
+ (a) => a.ancestry_id !== ancestryId
10450
+ )
10451
+ };
10244
10452
  }
10245
10453
  return { ...g, children: removeRecursive(g.children) };
10246
10454
  });
@@ -10261,7 +10469,13 @@ function AncestryBoard({
10261
10469
  className: "bg-slate-950 border border-white/10 rounded-xl w-[98vw] h-[97vh] flex flex-col shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200",
10262
10470
  onClick: (e) => e.stopPropagation()
10263
10471
  },
10264
- /* @__PURE__ */ React24.createElement("div", { className: "h-14 px-4 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React24.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ React24.createElement(FiLayers6, { className: "text-indigo-400" }), "Ancestry Board"), saveStatus !== "idle" && /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React24.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-900/50 border border-white/5" }, saveStatus === "saving" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(FiLoader5, { className: "animate-spin text-indigo-400", size: 12 }), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-3" }, pickingGroupId && /* @__PURE__ */ React24.createElement("span", { className: "text-xs text-indigo-300 font-medium animate-pulse hidden sm:inline-block mr-2" }, "Selecione na lateral..."), canEdit && /* @__PURE__ */ React24.createElement(
10472
+ /* @__PURE__ */ React24.createElement("div", { className: "h-14 px-4 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React24.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ React24.createElement(FiLayers6, { className: "text-indigo-400" }), "Ancestry Board"), saveStatus !== "idle" && /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React24.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-900/50 border border-white/5" }, saveStatus === "saving" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(
10473
+ FiLoader5,
10474
+ {
10475
+ className: "animate-spin text-indigo-400",
10476
+ size: 12
10477
+ }
10478
+ ), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ React24.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-3" }, pickingGroupId && /* @__PURE__ */ React24.createElement("span", { className: "text-xs text-indigo-300 font-medium animate-pulse hidden sm:inline-block mr-2" }, "Selecione na lateral..."), canEdit && /* @__PURE__ */ React24.createElement(
10265
10479
  "button",
10266
10480
  {
10267
10481
  onClick: handleAddRootGroup,
@@ -10277,57 +10491,75 @@ function AncestryBoard({
10277
10491
  },
10278
10492
  "\xD7"
10279
10493
  ))),
10280
- /* @__PURE__ */ React24.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ React24.createElement("div", { className: `
10494
+ /* @__PURE__ */ React24.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ React24.createElement(
10495
+ "div",
10496
+ {
10497
+ className: `
10281
10498
  flex flex-col border-r border-white/10 transition-all duration-300 flex-none
10282
10499
  ${pickingGroupId ? "w-[25%] border-indigo-500/30" : "w-[20%]"}
10283
10500
  min-w-[280px] max-w-[500px] bg-slate-900
10284
- ` }, /* @__PURE__ */ React24.createElement("div", { className: "p-3 border-b border-white/5 bg-slate-900/50" }, /* @__PURE__ */ React24.createElement("div", { className: "relative group" }, /* @__PURE__ */ React24.createElement(FiSearch4, { className: `absolute left-3 top-1/2 -translate-y-1/2 transition-colors ${pickingGroupId ? "text-indigo-400" : "text-slate-500 group-focus-within:text-indigo-400"}` }), /* @__PURE__ */ React24.createElement(
10285
- "input",
10286
- {
10287
- type: "text",
10288
- placeholder: pickingGroupId ? "Pesquise para adicionar..." : "Pesquisar ancestralidade...",
10289
- className: `
10501
+ `
10502
+ },
10503
+ /* @__PURE__ */ React24.createElement("div", { className: "p-3 border-b border-white/5 bg-slate-900/50" }, /* @__PURE__ */ React24.createElement("div", { className: "relative group" }, /* @__PURE__ */ React24.createElement(
10504
+ FiSearch4,
10505
+ {
10506
+ className: `absolute left-3 top-1/2 -translate-y-1/2 transition-colors ${pickingGroupId ? "text-indigo-400" : "text-slate-500 group-focus-within:text-indigo-400"}`
10507
+ }
10508
+ ), /* @__PURE__ */ React24.createElement(
10509
+ "input",
10510
+ {
10511
+ type: "text",
10512
+ placeholder: pickingGroupId ? "Pesquise para adicionar..." : "Pesquisar ancestralidade...",
10513
+ className: `
10290
10514
  w-full rounded-md pl-9 pr-4 py-2 text-sm transition-all focus:outline-none focus:ring-1 focus:ring-indigo-500 border
10291
10515
  ${pickingGroupId ? "bg-indigo-950/30 border-indigo-500/30 text-white placeholder-indigo-300/50" : "bg-slate-950 border-white/10 text-slate-200 placeholder-slate-600"}
10292
10516
  `,
10293
- value: searchTerm,
10294
- onChange: (e) => setSearchTerm(e.target.value),
10295
- autoFocus: !pickingGroupId
10296
- }
10297
- ))), /* @__PURE__ */ React24.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2" }, filtered.map((anc) => {
10298
- const parentNodeName = nodeNamesMap.get(String(anc.ancestral_node)) || "Node Desconhecido";
10299
- const isPicking = !!pickingGroupId;
10300
- return /* @__PURE__ */ React24.createElement(
10301
- "div",
10302
- {
10303
- key: anc.ancestry_id,
10304
- onClick: () => {
10305
- if (isPicking) handleSelectAncestry(anc);
10306
- },
10307
- className: `
10517
+ value: searchTerm,
10518
+ onChange: (e) => setSearchTerm(e.target.value),
10519
+ autoFocus: !pickingGroupId
10520
+ }
10521
+ ))),
10522
+ /* @__PURE__ */ React24.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2" }, filtered.map((anc) => {
10523
+ const parentNodeName = nodeNamesMap.get(String(anc.ancestral_node)) || "Node Desconhecido";
10524
+ const isPicking = !!pickingGroupId;
10525
+ return /* @__PURE__ */ React24.createElement(
10526
+ "div",
10527
+ {
10528
+ key: anc.ancestry_id,
10529
+ onClick: () => {
10530
+ if (isPicking) handleSelectAncestry(anc);
10531
+ },
10532
+ className: `
10308
10533
  group relative flex items-start gap-3 p-3 text-left rounded-lg border transition-all duration-200
10309
10534
  ${isPicking ? "border-indigo-500/30 bg-indigo-500/5 hover:bg-indigo-500/20 hover:border-indigo-400 cursor-pointer" : "border-white/5 bg-slate-800/40 hover:bg-indigo-600/10 hover:border-indigo-500/30 cursor-default"}
10310
10535
  `
10311
- },
10312
- /* @__PURE__ */ React24.createElement("div", { className: `
10536
+ },
10537
+ /* @__PURE__ */ React24.createElement(
10538
+ "div",
10539
+ {
10540
+ className: `
10313
10541
  mt-0.5 w-8 h-8 rounded-md grid place-content-center shrink-0 border transition-all shadow-lg
10314
10542
  ${isPicking ? "bg-indigo-500 text-white border-indigo-400" : "bg-slate-800 text-indigo-400 border-white/5 group-hover:bg-indigo-500 group-hover:text-white"}
10315
- ` }, isPicking ? /* @__PURE__ */ React24.createElement(FiPlus9, { size: 16 }) : /* @__PURE__ */ React24.createElement(FiLayers6, { size: 14 })),
10316
- /* @__PURE__ */ React24.createElement("div", { className: "flex-1 min-w-0 pb-2" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center justify-between gap-2" }, /* @__PURE__ */ React24.createElement("h4", { className: "text-sm font-medium text-slate-200 group-hover:text-white truncate transition-colors" }, anc.name || "Sem Nome"), anc.is_private && /* @__PURE__ */ React24.createElement("span", { className: "text-[9px] px-1 py-0.5 rounded bg-amber-500/10 text-amber-300 border border-amber-500/20" }, "Priv")), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1.5 mt-0.5 text-[11px] text-slate-500 group-hover:text-indigo-200/70 transition-colors" }, /* @__PURE__ */ React24.createElement(FiCornerUpRight4, { size: 10 }), /* @__PURE__ */ React24.createElement("span", { className: "truncate max-w-[120px]" }, parentNodeName)), anc.description && /* @__PURE__ */ React24.createElement("p", { className: "mt-1.5 text-[11px] text-slate-400 line-clamp-2 leading-relaxed opacity-80" }, anc.description)),
10317
- !isPicking && /* @__PURE__ */ React24.createElement(
10318
- "button",
10319
- {
10320
- onClick: (e) => {
10321
- e.stopPropagation();
10322
- handleSelectAncestry(anc);
10543
+ `
10323
10544
  },
10324
- className: "absolute right-2 bottom-2 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-y-2 group-hover:translate-y-0 z-10",
10325
- title: "Renderizar Ancestralidade"
10326
- },
10327
- /* @__PURE__ */ React24.createElement("div", { className: "bg-indigo-500 text-white p-2 rounded-full shadow-lg hover:bg-indigo-400 hover:scale-110 transition-all" }, /* @__PURE__ */ React24.createElement(FiPlay, { size: 14, className: "ml-0.5" }))
10328
- )
10329
- );
10330
- }))), /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col flex-1 bg-slate-950/30" }, /* @__PURE__ */ React24.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4" }, groups.length === 0 ? /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col items-center justify-center h-full text-slate-500 gap-3 border-2 border-dashed border-white/5 rounded-xl m-4 bg-slate-900/20" }, /* @__PURE__ */ React24.createElement(FiLayers6, { size: 24, className: "opacity-20" }), /* @__PURE__ */ React24.createElement("p", { className: "text-xs text-center px-4" }, canEdit ? /* @__PURE__ */ React24.createElement(React24.Fragment, null, "Nenhum grupo criado.", /* @__PURE__ */ React24.createElement("br", null), 'Use o bot\xE3o "Novo Grupo" acima.') : /* @__PURE__ */ React24.createElement(React24.Fragment, null, "Nenhum grupo dispon\xEDvel para visualiza\xE7\xE3o."))) : groups.map((group, index) => /* @__PURE__ */ React24.createElement(
10545
+ isPicking ? /* @__PURE__ */ React24.createElement(FiPlus9, { size: 16 }) : /* @__PURE__ */ React24.createElement(FiLayers6, { size: 14 })
10546
+ ),
10547
+ /* @__PURE__ */ React24.createElement("div", { className: "flex-1 min-w-0 pb-2" }, /* @__PURE__ */ React24.createElement("div", { className: "flex items-center justify-between gap-2" }, /* @__PURE__ */ React24.createElement("h4", { className: "text-sm font-medium text-slate-200 group-hover:text-white truncate transition-colors" }, anc.name || "Sem Nome"), anc.is_private && /* @__PURE__ */ React24.createElement("span", { className: "text-[9px] px-1 py-0.5 rounded bg-amber-500/10 text-amber-300 border border-amber-500/20" }, "Priv")), /* @__PURE__ */ React24.createElement("div", { className: "flex items-center gap-1.5 mt-0.5 text-[11px] text-slate-500 group-hover:text-indigo-200/70 transition-colors" }, /* @__PURE__ */ React24.createElement(FiCornerUpRight4, { size: 10 }), /* @__PURE__ */ React24.createElement("span", { className: "truncate max-w-[120px]" }, parentNodeName)), anc.description && /* @__PURE__ */ React24.createElement("p", { className: "mt-1.5 text-[11px] text-slate-400 line-clamp-2 leading-relaxed opacity-80" }, anc.description)),
10548
+ !isPicking && /* @__PURE__ */ React24.createElement(
10549
+ "button",
10550
+ {
10551
+ onClick: (e) => {
10552
+ e.stopPropagation();
10553
+ handleSelectAncestry(anc);
10554
+ },
10555
+ className: "absolute right-2 bottom-2 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-y-2 group-hover:translate-y-0 z-10",
10556
+ title: "Renderizar Ancestralidade"
10557
+ },
10558
+ /* @__PURE__ */ React24.createElement("div", { className: "bg-indigo-500 text-white p-2 rounded-full shadow-lg hover:bg-indigo-400 hover:scale-110 transition-all" }, /* @__PURE__ */ React24.createElement(FiPlay, { size: 14, className: "ml-0.5" }))
10559
+ )
10560
+ );
10561
+ }))
10562
+ ), /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col flex-1 bg-slate-950/30" }, /* @__PURE__ */ React24.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4" }, groups.length === 0 ? /* @__PURE__ */ React24.createElement("div", { className: "flex flex-col items-center justify-center h-full text-slate-500 gap-3 border-2 border-dashed border-white/5 rounded-xl m-4 bg-slate-900/20" }, /* @__PURE__ */ React24.createElement(FiLayers6, { size: 24, className: "opacity-20" }), /* @__PURE__ */ React24.createElement("p", { className: "text-xs text-center px-4" }, canEdit ? /* @__PURE__ */ React24.createElement(React24.Fragment, null, "Nenhum grupo criado.", /* @__PURE__ */ React24.createElement("br", null), 'Use o bot\xE3o "Novo Grupo" acima.') : /* @__PURE__ */ React24.createElement(React24.Fragment, null, "Nenhum grupo dispon\xEDvel para visualiza\xE7\xE3o."))) : groups.map((group, index) => /* @__PURE__ */ React24.createElement(
10331
10563
  GroupItem,
10332
10564
  {
10333
10565
  key: group.id,
@@ -10406,7 +10638,10 @@ var findNodePath3 = (tree, targetNodeId, currentPath = []) => {
10406
10638
  }
10407
10639
  if (tree.children) {
10408
10640
  for (let i = 0; i < tree.children.length; i++) {
10409
- const res = findNodePath3(tree.children[i], targetNodeId, [...currentPath, i]);
10641
+ const res = findNodePath3(tree.children[i], targetNodeId, [
10642
+ ...currentPath,
10643
+ i
10644
+ ]);
10410
10645
  if (res) return res;
10411
10646
  }
10412
10647
  }
@@ -10499,29 +10734,73 @@ function XViewScene({
10499
10734
  const [userPermissionRole, setUserPermissionRole] = useState25(null);
10500
10735
  const [isInitialized, setIsInitialized] = useState25(false);
10501
10736
  const [sceneVersion, setSceneVersion] = useState25(0);
10502
- const [contextMenu, setContextMenu] = useState25({ visible: false, x: 0, y: 0, nodeData: null });
10503
- const [multiContextMenu, setMultiContextMenu] = useState25({ visible: false, x: 0, y: 0, nodeIds: null });
10504
- const [relationshipMenu, setRelationshipMenu] = useState25({ visible: false, x: 0, y: 0, linkObject: null });
10505
- const [creationMode, setCreationMode] = useState25({ isActive: false, sourceNodeData: null });
10506
- const [versionMode, setVersionMode] = useState25({ isActive: false, sourceNodeData: null });
10737
+ const [contextMenu, setContextMenu] = useState25({
10738
+ visible: false,
10739
+ x: 0,
10740
+ y: 0,
10741
+ nodeData: null
10742
+ });
10743
+ const [multiContextMenu, setMultiContextMenu] = useState25({
10744
+ visible: false,
10745
+ x: 0,
10746
+ y: 0,
10747
+ nodeIds: null
10748
+ });
10749
+ const [relationshipMenu, setRelationshipMenu] = useState25({
10750
+ visible: false,
10751
+ x: 0,
10752
+ y: 0,
10753
+ linkObject: null
10754
+ });
10755
+ const [creationMode, setCreationMode] = useState25({
10756
+ isActive: false,
10757
+ sourceNodeData: null
10758
+ });
10759
+ const [versionMode, setVersionMode] = useState25({
10760
+ isActive: false,
10761
+ sourceNodeData: null
10762
+ });
10507
10763
  const [questMode, setQuestMode] = useState25({ isActive: false });
10508
10764
  const [hasFocusedInitial, setHasFocusedInitial] = useState25(false);
10509
10765
  const [hasOpenedInitialAncestry, setHasOpenedInitialAncestry] = useState25(false);
10510
- const [ancestryMode, setAncestryMode] = useState25({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
10766
+ const [ancestryMode, setAncestryMode] = useState25({
10767
+ isActive: false,
10768
+ tree: null,
10769
+ selectedParentId: null,
10770
+ isEditMode: false,
10771
+ currentAncestryId: null,
10772
+ ancestryName: "",
10773
+ ancestryDescription: "",
10774
+ ancestryDescriptionSections: [],
10775
+ isAddingNodes: false
10776
+ });
10511
10777
  const [readingMode, setReadingMode] = useState25({
10512
10778
  isActive: false,
10513
10779
  ancestry: null,
10514
10780
  branchStack: [],
10515
10781
  autoAbstraction: false
10516
10782
  });
10517
- const [formPosition, setFormPosition] = useState25({ left: 16, top: 16, opacity: 0 });
10783
+ const [formPosition, setFormPosition] = useState25({
10784
+ left: 16,
10785
+ top: 16,
10786
+ opacity: 0
10787
+ });
10518
10788
  const [detailsNode, setDetailsNode] = useState25(null);
10519
10789
  const [detailsLink, setDetailsLink] = useState25(null);
10520
10790
  const [ancestryLinkDetails, setAncestryLinkDetails] = useState25(null);
10521
- const [imageViewer, setImageViewer] = useState25({ visible: false, images: [], startIndex: 0 });
10522
- const [editingAncestryRel, setEditingAncestryRel] = useState25({ visible: false, data: null, path: null });
10791
+ const [imageViewer, setImageViewer] = useState25({
10792
+ visible: false,
10793
+ images: [],
10794
+ startIndex: 0
10795
+ });
10796
+ const [editingAncestryRel, setEditingAncestryRel] = useState25({
10797
+ visible: false,
10798
+ data: null,
10799
+ path: null
10800
+ });
10523
10801
  const [isImportModalOpen, setIsImportModalOpen] = useState25(false);
10524
10802
  const [importSuccessMessage, setImportSuccessMessage] = useState25("");
10803
+ const [invalidTargetError, setInvalidTargetError] = useState25(null);
10525
10804
  const [highlightedNodeId, setHighlightedNodeId] = useState25(null);
10526
10805
  const [isAncestryBoardOpen, setIsAncestryBoardOpen] = useState25(false);
10527
10806
  const [ancestryBoardData, setAncestryBoardData] = useState25([]);
@@ -10559,8 +10838,23 @@ function XViewScene({
10559
10838
  ghostElements: { node: null, line: null, aura: null },
10560
10839
  creation: { isActive: false, sourceNodeData: null },
10561
10840
  connection: { isActive: false, sourceNodeData: null, line: null },
10562
- relink: { isActive: false, end: null, fixedNodeId: null, originalLine: null, line: null },
10563
- ancestry: { isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", isAddingNodes: false },
10841
+ relink: {
10842
+ isActive: false,
10843
+ end: null,
10844
+ fixedNodeId: null,
10845
+ originalLine: null,
10846
+ line: null
10847
+ },
10848
+ ancestry: {
10849
+ isActive: false,
10850
+ tree: null,
10851
+ selectedParentId: null,
10852
+ isEditMode: false,
10853
+ currentAncestryId: null,
10854
+ ancestryName: "",
10855
+ ancestryDescription: "",
10856
+ isAddingNodes: false
10857
+ },
10564
10858
  glowTexture: null,
10565
10859
  nodeIdToParentFileMap: null,
10566
10860
  maxAncestryRenderIndex: 0,
@@ -10569,7 +10863,11 @@ function XViewScene({
10569
10863
  highlightedNodeId: null
10570
10864
  });
10571
10865
  const maxReadPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
10572
- const { width: readModeWidth, isResizing: isReadModeResizing, handlePointerDown: handleReadModeResize } = useResizablePanel({
10866
+ const {
10867
+ width: readModeWidth,
10868
+ isResizing: isReadModeResizing,
10869
+ handlePointerDown: handleReadModeResize
10870
+ } = useResizablePanel({
10573
10871
  initialWidth: 700,
10574
10872
  minWidth: 320,
10575
10873
  maxWidth: maxReadPanelW
@@ -10586,7 +10884,9 @@ function XViewScene({
10586
10884
  for (const parentFileId in allParentData) {
10587
10885
  if (allParentData.hasOwnProperty(parentFileId)) {
10588
10886
  const parentFile = allParentData[parentFileId];
10589
- const parentDbInfo = parentDbsArray.find((db) => String(db.db_id) === String(parentFileId));
10887
+ const parentDbInfo = parentDbsArray.find(
10888
+ (db) => String(db.db_id) === String(parentFileId)
10889
+ );
10590
10890
  const ownerId2 = (parentDbInfo == null ? void 0 : parentDbInfo.owner_id) || null;
10591
10891
  const datasetName = parentFile.dataset_name || `Dataset #${parentFileId.substring(0, 6)}`;
10592
10892
  if (parentFile.nodes && ownerId2) {
@@ -10622,7 +10922,10 @@ function XViewScene({
10622
10922
  if (files.length > 0 && get_single_parent_file && save_view_data) {
10623
10923
  for (const file of files) {
10624
10924
  try {
10625
- const parentFileData = await get_single_parent_file(file.id, session);
10925
+ const parentFileData = await get_single_parent_file(
10926
+ file.id,
10927
+ session
10928
+ );
10626
10929
  if (parentFileData.success && parentFileData.data) {
10627
10930
  parentDataRef.current = {
10628
10931
  ...parentDataRef.current,
@@ -10633,7 +10936,11 @@ function XViewScene({
10633
10936
  owner_id: session.user.id
10634
10937
  };
10635
10938
  updatedParentDbs.push(newParentDbObject);
10636
- await add_new_parent_file_to_scene_at_firebase_action(sceneConfigId, session, file.id);
10939
+ await add_new_parent_file_to_scene_at_firebase_action(
10940
+ sceneConfigId,
10941
+ session,
10942
+ file.id
10943
+ );
10637
10944
  importedIds.push(file.id);
10638
10945
  successCount++;
10639
10946
  }
@@ -10645,7 +10952,10 @@ function XViewScene({
10645
10952
  if (viewToImport && get_ancestry_file) {
10646
10953
  try {
10647
10954
  const targetViewOwnerId = ((_b2 = (_a2 = viewToImport.members) == null ? void 0 : _a2.find((m) => m.permission === "owner")) == null ? void 0 : _b2.id) || session.user.id;
10648
- const ancestryResponse = await get_ancestry_file(viewToImport.id, targetViewOwnerId);
10955
+ const ancestryResponse = await get_ancestry_file(
10956
+ viewToImport.id,
10957
+ targetViewOwnerId
10958
+ );
10649
10959
  if (ancestryResponse.success && Array.isArray(ancestryResponse.data)) {
10650
10960
  const viewSpecificAncestries = ancestryResponse.data.filter(
10651
10961
  (anc) => anc._source_file_id === viewToImport.id && !anc.is_private
@@ -10656,14 +10966,21 @@ function XViewScene({
10656
10966
  _imported_from_view_owner_id: targetViewOwnerId,
10657
10967
  _source_file_id: viewToImport.id,
10658
10968
  _source_owner_id: targetViewOwnerId,
10659
- _origin_db_ids: (viewToImport.selected_databases || []).map((db) => db.db_id)
10969
+ _origin_db_ids: (viewToImport.selected_databases || []).map(
10970
+ (db) => db.db_id
10971
+ )
10660
10972
  }));
10661
10973
  const currentAncestries = ancestryDataRef.current || [];
10662
10974
  const newAncestries = processedAncestries.filter(
10663
- (newAnc) => !currentAncestries.some((curr) => String(curr.ancestry_id) === String(newAnc.ancestry_id))
10975
+ (newAnc) => !currentAncestries.some(
10976
+ (curr) => String(curr.ancestry_id) === String(newAnc.ancestry_id)
10977
+ )
10664
10978
  );
10665
10979
  if (newAncestries.length > 0) {
10666
- ancestryDataRef.current = [...currentAncestries, ...newAncestries];
10980
+ ancestryDataRef.current = [
10981
+ ...currentAncestries,
10982
+ ...newAncestries
10983
+ ];
10667
10984
  ancestriesWereImported = true;
10668
10985
  }
10669
10986
  }
@@ -10683,7 +11000,9 @@ function XViewScene({
10683
11000
  if (ancestry_save_url) {
10684
11001
  await save_view_data(ancestry_save_url, ancestryDataRef.current);
10685
11002
  } else {
10686
- console.error("Erro: URL de salvamento de ancestralidade n\xE3o definida.");
11003
+ console.error(
11004
+ "Erro: URL de salvamento de ancestralidade n\xE3o definida."
11005
+ );
10687
11006
  }
10688
11007
  }
10689
11008
  setSceneVersion((v) => v + 1);
@@ -10691,114 +11010,137 @@ function XViewScene({
10691
11010
  setImportSuccessMessage("Importa\xE7\xE3o conclu\xEDda com sucesso.");
10692
11011
  setTimeout(() => setImportSuccessMessage(""), 5e3);
10693
11012
  } else if (viewToImport && !ancestriesWereImported) {
10694
- console.warn("Importa\xE7\xE3o finalizada, mas nenhum dado novo foi adicionado.");
11013
+ console.warn(
11014
+ "Importa\xE7\xE3o finalizada, mas nenhum dado novo foi adicionado."
11015
+ );
10695
11016
  }
10696
11017
  },
10697
- [get_single_parent_file, get_ancestry_file, save_view_data, session, sceneSaveUrl, ancestry_save_url, sceneConfigId, add_new_parent_file_to_scene_at_firebase_action]
11018
+ [
11019
+ get_single_parent_file,
11020
+ get_ancestry_file,
11021
+ save_view_data,
11022
+ session,
11023
+ sceneSaveUrl,
11024
+ ancestry_save_url,
11025
+ sceneConfigId,
11026
+ add_new_parent_file_to_scene_at_firebase_action
11027
+ ]
10698
11028
  );
10699
11029
  const handleOpenImageViewer = (images, startIndex) => {
10700
11030
  setImageViewer({ visible: true, images, startIndex });
10701
11031
  };
10702
- const tweenToTarget = useCallback4((target, zoomFactor = 1, forcedDirection = null) => {
10703
- const { camera, controls, tweenGroup } = stateRef.current;
10704
- if (!camera || !controls || !tweenGroup) return;
10705
- const targetPos = target instanceof THREE3.Mesh ? target.getWorldPosition(new THREE3.Vector3()) : target;
10706
- const controlsTween = new Tween2(controls.target).to(targetPos, 1500).easing(Easing2.Cubic.Out);
10707
- tweenGroup.add(controlsTween);
10708
- controlsTween.start();
10709
- let offset;
10710
- if (forcedDirection) {
10711
- offset = forcedDirection.clone().normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
10712
- } else {
10713
- offset = camera.position.clone().sub(controls.target).normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
10714
- }
10715
- const targetCameraPos = targetPos.clone().add(offset);
10716
- const cameraTween = new Tween2(camera.position).to(targetCameraPos, 1500).easing(Easing2.Cubic.Out);
10717
- tweenGroup.add(cameraTween);
10718
- cameraTween.start();
10719
- }, []);
11032
+ const tweenToTarget = useCallback4(
11033
+ (target, zoomFactor = 1, forcedDirection = null) => {
11034
+ const { camera, controls, tweenGroup } = stateRef.current;
11035
+ if (!camera || !controls || !tweenGroup) return;
11036
+ const targetPos = target instanceof THREE3.Mesh ? target.getWorldPosition(new THREE3.Vector3()) : target;
11037
+ const controlsTween = new Tween2(controls.target).to(targetPos, 1500).easing(Easing2.Cubic.Out);
11038
+ tweenGroup.add(controlsTween);
11039
+ controlsTween.start();
11040
+ let offset;
11041
+ if (forcedDirection) {
11042
+ offset = forcedDirection.clone().normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
11043
+ } else {
11044
+ offset = camera.position.clone().sub(controls.target).normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
11045
+ }
11046
+ const targetCameraPos = targetPos.clone().add(offset);
11047
+ const cameraTween = new Tween2(camera.position).to(targetCameraPos, 1500).easing(Easing2.Cubic.Out);
11048
+ tweenGroup.add(cameraTween);
11049
+ cameraTween.start();
11050
+ },
11051
+ []
11052
+ );
10720
11053
  const isFromUiOverlay = (event) => {
10721
11054
  const t = event == null ? void 0 : event.target;
10722
11055
  if (!t || typeof t.closest !== "function") return false;
10723
11056
  return !!t.closest(".ui-overlay");
10724
11057
  };
10725
- const buildFullAncestryTree = useCallback4((idTree, nodes, ancestries = []) => {
10726
- if (!idTree) return null;
10727
- const nodeMap = new Map(nodes.map((n) => [String(n.id), n]));
10728
- const ancestryMap = new Map(ancestries.map((a) => [String(a.ancestry_id), a]));
10729
- const recursiveBuild = (treeItem) => {
10730
- if (!treeItem) return null;
10731
- if (treeItem.is_section) {
10732
- return {
10733
- ...treeItem,
10734
- children: (treeItem.children || []).map(recursiveBuild).filter(Boolean)
10735
- };
10736
- }
10737
- let nodeId = treeItem.node_id;
10738
- if (!nodeId && treeItem.node) nodeId = treeItem.node.id;
10739
- const fullNode = nodeMap.get(String(nodeId));
10740
- const effectiveNode = fullNode || treeItem.node;
10741
- let processedBranches = [];
10742
- if (treeItem.parallel_branches && Array.isArray(treeItem.parallel_branches)) {
10743
- processedBranches = treeItem.parallel_branches.map((branch) => {
10744
- if (branch.linked_ancestry_id) {
10745
- const linkedAncestry = ancestryMap.get(String(branch.linked_ancestry_id));
10746
- if (linkedAncestry && linkedAncestry.tree) {
10747
- const graftedTree = recursiveBuild(linkedAncestry.tree);
10748
- return {
10749
- ...branch,
10750
- name: linkedAncestry.name,
10751
- description: linkedAncestry.description,
10752
- description_sections: linkedAncestry.description_sections,
10753
- tree: graftedTree,
10754
- isLinked: true
10755
- };
10756
- }
10757
- }
11058
+ const buildFullAncestryTree = useCallback4(
11059
+ (idTree, nodes, ancestries = []) => {
11060
+ if (!idTree) return null;
11061
+ const nodeMap = new Map(nodes.map((n) => [String(n.id), n]));
11062
+ const ancestryMap = new Map(
11063
+ ancestries.map((a) => [String(a.ancestry_id), a])
11064
+ );
11065
+ const recursiveBuild = (treeItem) => {
11066
+ if (!treeItem) return null;
11067
+ if (treeItem.is_section) {
10758
11068
  return {
10759
- ...branch,
10760
- tree: recursiveBuild(branch.tree)
11069
+ ...treeItem,
11070
+ children: (treeItem.children || []).map(recursiveBuild).filter(Boolean)
10761
11071
  };
10762
- });
10763
- }
10764
- return {
10765
- ...effectiveNode ? { node: effectiveNode } : { node: { id: nodeId, name: "Unknown" } },
10766
- relationship: treeItem.relationship || {},
10767
- children: (treeItem.children || []).map(recursiveBuild).filter(Boolean),
10768
- parallel_branches: processedBranches
10769
- };
10770
- };
10771
- let rootId = idTree.node_id;
10772
- if (!rootId && idTree.node) rootId = idTree.node.id;
10773
- if (rootId) {
10774
- const rootNode = nodeMap.get(String(rootId));
10775
- const effectiveRoot = rootNode || idTree.node;
10776
- if (!effectiveRoot) return null;
10777
- return {
10778
- node: effectiveRoot,
10779
- relationship: idTree.relationship || {},
10780
- children: (idTree.children || []).map(recursiveBuild).filter(Boolean),
10781
- parallel_branches: (idTree.parallel_branches || []).map((branch) => {
10782
- if (branch.linked_ancestry_id) {
10783
- const linkedAncestry = ancestryMap.get(String(branch.linked_ancestry_id));
10784
- if (linkedAncestry && linkedAncestry.tree) {
10785
- return {
10786
- ...branch,
10787
- name: linkedAncestry.name,
10788
- description: linkedAncestry.description,
10789
- description_sections: linkedAncestry.description_sections,
10790
- tree: recursiveBuild(linkedAncestry.tree),
10791
- isLinked: true
10792
- };
11072
+ }
11073
+ let nodeId = treeItem.node_id;
11074
+ if (!nodeId && treeItem.node) nodeId = treeItem.node.id;
11075
+ const fullNode = nodeMap.get(String(nodeId));
11076
+ const effectiveNode = fullNode || treeItem.node;
11077
+ let processedBranches = [];
11078
+ if (treeItem.parallel_branches && Array.isArray(treeItem.parallel_branches)) {
11079
+ processedBranches = treeItem.parallel_branches.map((branch) => {
11080
+ if (branch.linked_ancestry_id) {
11081
+ const linkedAncestry = ancestryMap.get(
11082
+ String(branch.linked_ancestry_id)
11083
+ );
11084
+ if (linkedAncestry && linkedAncestry.tree) {
11085
+ const graftedTree = recursiveBuild(linkedAncestry.tree);
11086
+ return {
11087
+ ...branch,
11088
+ name: linkedAncestry.name,
11089
+ description: linkedAncestry.description,
11090
+ description_sections: linkedAncestry.description_sections,
11091
+ tree: graftedTree,
11092
+ isLinked: true
11093
+ };
11094
+ }
10793
11095
  }
10794
- }
10795
- return { ...branch, tree: recursiveBuild(branch.tree) };
10796
- })
11096
+ return {
11097
+ ...branch,
11098
+ tree: recursiveBuild(branch.tree)
11099
+ };
11100
+ });
11101
+ }
11102
+ return {
11103
+ ...effectiveNode ? { node: effectiveNode } : { node: { id: nodeId, name: "Unknown" } },
11104
+ relationship: treeItem.relationship || {},
11105
+ children: (treeItem.children || []).map(recursiveBuild).filter(Boolean),
11106
+ parallel_branches: processedBranches
11107
+ };
10797
11108
  };
10798
- }
10799
- return recursiveBuild(idTree);
10800
- }, []);
10801
- const handleActivateTimeline = useCallback4(() => {
11109
+ let rootId = idTree.node_id;
11110
+ if (!rootId && idTree.node) rootId = idTree.node.id;
11111
+ if (rootId) {
11112
+ const rootNode = nodeMap.get(String(rootId));
11113
+ const effectiveRoot = rootNode || idTree.node;
11114
+ if (!effectiveRoot) return null;
11115
+ return {
11116
+ node: effectiveRoot,
11117
+ relationship: idTree.relationship || {},
11118
+ children: (idTree.children || []).map(recursiveBuild).filter(Boolean),
11119
+ parallel_branches: (idTree.parallel_branches || []).map((branch) => {
11120
+ if (branch.linked_ancestry_id) {
11121
+ const linkedAncestry = ancestryMap.get(
11122
+ String(branch.linked_ancestry_id)
11123
+ );
11124
+ if (linkedAncestry && linkedAncestry.tree) {
11125
+ return {
11126
+ ...branch,
11127
+ name: linkedAncestry.name,
11128
+ description: linkedAncestry.description,
11129
+ description_sections: linkedAncestry.description_sections,
11130
+ tree: recursiveBuild(linkedAncestry.tree),
11131
+ isLinked: true
11132
+ };
11133
+ }
11134
+ }
11135
+ return { ...branch, tree: recursiveBuild(branch.tree) };
11136
+ })
11137
+ };
11138
+ }
11139
+ return recursiveBuild(idTree);
11140
+ },
11141
+ []
11142
+ );
11143
+ const handleActivateTimeline = useCallback4(() => {
10802
11144
  const { nodeObjects, tweenGroup, timelineIntervalsGroup } = stateRef.current;
10803
11145
  if (!nodeObjects || !tweenGroup || !timelineIntervalsGroup) return;
10804
11146
  while (timelineIntervalsGroup.children.length > 0) {
@@ -10878,10 +11220,12 @@ function XViewScene({
10878
11220
  if (timelineNodes.length === 0) return;
10879
11221
  const sortedTimePoints = Array.from(allTimePoints).sort((a, b) => a - b);
10880
11222
  const maxTimeIndex = sortedTimePoints.length - 1;
10881
- const timeToYMap = new Map(sortedTimePoints.map((time, index) => [
10882
- time,
10883
- (index - maxTimeIndex) * TIMELINE_LAYER_SPACING_Y
10884
- ]));
11223
+ const timeToYMap = new Map(
11224
+ sortedTimePoints.map((time, index) => [
11225
+ time,
11226
+ (index - maxTimeIndex) * TIMELINE_LAYER_SPACING_Y
11227
+ ])
11228
+ );
10885
11229
  timelineNodes.sort((a, b) => {
10886
11230
  if (a.isUndated && b.isUndated) return 0;
10887
11231
  if (a.isUndated) return 1;
@@ -10924,7 +11268,12 @@ function XViewScene({
10924
11268
  if (type === "interval") {
10925
11269
  const endY = timeToYMap.get(endDate.getTime());
10926
11270
  if (endY > y) {
10927
- const barGeometry = new THREE3.CylinderGeometry(TIMELINE_INTERVAL_BAR_RADIUS, TIMELINE_INTERVAL_BAR_RADIUS, 1, 16);
11271
+ const barGeometry = new THREE3.CylinderGeometry(
11272
+ TIMELINE_INTERVAL_BAR_RADIUS,
11273
+ TIMELINE_INTERVAL_BAR_RADIUS,
11274
+ 1,
11275
+ 16
11276
+ );
10928
11277
  const barMaterial = new THREE3.MeshStandardMaterial({
10929
11278
  color: TIMELINE_GOLD_COLOR,
10930
11279
  emissive: 12092939,
@@ -10953,7 +11302,8 @@ function XViewScene({
10953
11302
  }, []);
10954
11303
  const handleVersionTimeline = useCallback4((sourceMesh, versionMeshes) => {
10955
11304
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
10956
- if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0) return;
11305
+ if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
11306
+ return;
10957
11307
  versionMeshes.forEach((mesh) => {
10958
11308
  const oldLabel = mesh.getObjectByName("timelineLabel");
10959
11309
  if (oldLabel) {
@@ -10969,7 +11319,8 @@ function XViewScene({
10969
11319
  }
10970
11320
  if (mesh.userData.timelineEndLabel) {
10971
11321
  timelineIntervalsGroup.remove(mesh.userData.timelineEndLabel);
10972
- if (mesh.userData.timelineEndLabel.material.map) mesh.userData.timelineEndLabel.material.map.dispose();
11322
+ if (mesh.userData.timelineEndLabel.material.map)
11323
+ mesh.userData.timelineEndLabel.material.map.dispose();
10973
11324
  mesh.userData.timelineEndLabel.material.dispose();
10974
11325
  delete mesh.userData.timelineEndLabel;
10975
11326
  }
@@ -11029,8 +11380,15 @@ function XViewScene({
11029
11380
  });
11030
11381
  if (timelineNodes.length === 0) return;
11031
11382
  const sortedTimePoints = Array.from(allTimePoints).sort((a, b) => a - b);
11032
- const timeToYMap = new Map(sortedTimePoints.map((time, index) => [time, index * TIMELINE_LAYER_SPACING_Y]));
11033
- timelineNodes.sort((a, b) => a.startDate - b.startDate || a.endDate - b.endDate);
11383
+ const timeToYMap = new Map(
11384
+ sortedTimePoints.map((time, index) => [
11385
+ time,
11386
+ index * TIMELINE_LAYER_SPACING_Y
11387
+ ])
11388
+ );
11389
+ timelineNodes.sort(
11390
+ (a, b) => a.startDate - b.startDate || a.endDate - b.endDate
11391
+ );
11034
11392
  const baseX = sourceMesh.position.x + TIMELINE_NODE_SPACING_X;
11035
11393
  const baseY = sourceMesh.position.y;
11036
11394
  const maxNodeIndex = timelineNodes.length - 1;
@@ -11048,7 +11406,12 @@ function XViewScene({
11048
11406
  if (type === "interval") {
11049
11407
  const relativeEndY = timeToYMap.get(endDate.getTime()) - timeToYMap.get(startDate.getTime());
11050
11408
  if (relativeEndY > 0) {
11051
- const barGeometry = new THREE3.CylinderGeometry(TIMELINE_INTERVAL_BAR_RADIUS, TIMELINE_INTERVAL_BAR_RADIUS, 1, 16);
11409
+ const barGeometry = new THREE3.CylinderGeometry(
11410
+ TIMELINE_INTERVAL_BAR_RADIUS,
11411
+ TIMELINE_INTERVAL_BAR_RADIUS,
11412
+ 1,
11413
+ 16
11414
+ );
11052
11415
  const barMaterial = new THREE3.MeshStandardMaterial({
11053
11416
  color: TIMELINE_GOLD_COLOR,
11054
11417
  emissive: 12092939,
@@ -11085,15 +11448,31 @@ function XViewScene({
11085
11448
  try {
11086
11449
  const typeStr = (viewParams == null ? void 0 : viewParams.type) || "";
11087
11450
  const sceneType = typeStr.toLowerCase().includes("database") ? "database" : "view";
11088
- const scenePromise = get_scene_view_data(configPath, ownerId2, typeStr, session, focusNodeId, focusAncestryId);
11451
+ const scenePromise = get_scene_view_data(
11452
+ configPath,
11453
+ ownerId2,
11454
+ typeStr,
11455
+ session,
11456
+ focusNodeId,
11457
+ focusAncestryId
11458
+ );
11089
11459
  const boardPromise = get_ancestry_board_action && session ? get_ancestry_board_action(configPath, sceneType, session, ownerId2) : Promise.resolve({ success: false, data: [] });
11090
- const [sceneResponse, boardResponse] = await Promise.all([scenePromise, boardPromise]);
11460
+ const [sceneResponse, boardResponse] = await Promise.all([
11461
+ scenePromise,
11462
+ boardPromise
11463
+ ]);
11091
11464
  if ((sceneResponse == null ? void 0 : sceneResponse.success) && ((_a2 = sceneResponse.data) == null ? void 0 : _a2.scene) && ((_b2 = sceneResponse.data) == null ? void 0 : _b2.parent)) {
11092
11465
  if (focusNodeId) {
11093
- let targetNode = sceneResponse.data.scene.nodes.find((n) => String(n.id) === String(focusNodeId));
11466
+ let targetNode = sceneResponse.data.scene.nodes.find(
11467
+ (n) => String(n.id) === String(focusNodeId)
11468
+ );
11094
11469
  if (!targetNode) {
11095
- const allParentNodes = Object.values(sceneResponse.data.parent).flatMap((f) => f.nodes || []);
11096
- targetNode = allParentNodes.find((n) => String(n.id) === String(focusNodeId));
11470
+ const allParentNodes = Object.values(
11471
+ sceneResponse.data.parent
11472
+ ).flatMap((f) => f.nodes || []);
11473
+ targetNode = allParentNodes.find(
11474
+ (n) => String(n.id) === String(focusNodeId)
11475
+ );
11097
11476
  }
11098
11477
  if (targetNode) {
11099
11478
  sceneResponse.data.scene.nodes = [targetNode];
@@ -11106,12 +11485,24 @@ function XViewScene({
11106
11485
  sceneDataRef.current = sceneResponse.data.scene;
11107
11486
  parentDataRef.current = sceneResponse.data.parent;
11108
11487
  ancestryDataRef.current = sceneResponse.data.ancestry;
11109
- console.log("Console de sceneResponse.data.scene:", sceneResponse.data.scene);
11110
- console.log("Console de sceneResponse.data.parent:", sceneResponse.data.parent);
11111
- console.log("Console de sceneResponse.data.ancestry:", sceneResponse.data.ancestry);
11488
+ console.log(
11489
+ "Console de sceneResponse.data.scene:",
11490
+ sceneResponse.data.scene
11491
+ );
11492
+ console.log(
11493
+ "Console de sceneResponse.data.parent:",
11494
+ sceneResponse.data.parent
11495
+ );
11496
+ console.log(
11497
+ "Console de sceneResponse.data.ancestry:",
11498
+ sceneResponse.data.ancestry
11499
+ );
11112
11500
  setIsInitialized(true);
11113
11501
  } else {
11114
- console.error("Falha ao buscar dados da cena:", (sceneResponse == null ? void 0 : sceneResponse.error) || "Resposta inv\xE1lida.");
11502
+ console.error(
11503
+ "Falha ao buscar dados da cena:",
11504
+ (sceneResponse == null ? void 0 : sceneResponse.error) || "Resposta inv\xE1lida."
11505
+ );
11115
11506
  }
11116
11507
  if (boardResponse == null ? void 0 : boardResponse.success) {
11117
11508
  setAncestryBoardData(boardResponse.data);
@@ -11130,7 +11521,9 @@ function XViewScene({
11130
11521
  console.error("Usu\xE1rio n\xE3o autenticado. Acesso negado.");
11131
11522
  setIsLoading(false);
11132
11523
  } else if (!sceneConfigId && status !== "loading") {
11133
- console.warn("Nenhum par\xE2metro de cena encontrado na URL ou falha na decripta\xE7\xE3o.");
11524
+ console.warn(
11525
+ "Nenhum par\xE2metro de cena encontrado na URL ou falha na decripta\xE7\xE3o."
11526
+ );
11134
11527
  setIsLoading(false);
11135
11528
  }
11136
11529
  }, [
@@ -11151,33 +11544,46 @@ function XViewScene({
11151
11544
  const objs = stateRef.current.nodeObjects || {};
11152
11545
  return !!objs[key];
11153
11546
  }, []);
11154
- const addOrUpdateNodeMesh = useCallback4((nodeData, position, suppressVersionUpdate = false) => {
11155
- const { graphGroup, nodeObjects, clickableNodes, glowTexture, tweenGroup } = stateRef.current;
11156
- const nodeId = String(nodeData.id);
11157
- if (nodeObjects[nodeId]) {
11158
- const existingMesh = nodeObjects[nodeId];
11159
- if (position) {
11160
- const updateTween = new Tween2(existingMesh.position).to(position, 800).easing(Easing2.Cubic.Out);
11161
- tweenGroup.add(updateTween);
11162
- updateTween.start();
11163
- }
11164
- return existingMesh;
11165
- }
11166
- const mesh = createNodeMesh(nodeData, position || new THREE3.Vector3(), glowTexture);
11167
- graphGroup.add(mesh);
11168
- if (mesh.userData.labelObject) {
11169
- if (mesh.userData.labelOffset) {
11170
- mesh.userData.labelObject.position.copy(mesh.position).add(mesh.userData.labelOffset);
11547
+ const addOrUpdateNodeMesh = useCallback4(
11548
+ (nodeData, position, suppressVersionUpdate = false) => {
11549
+ const {
11550
+ graphGroup,
11551
+ nodeObjects,
11552
+ clickableNodes,
11553
+ glowTexture,
11554
+ tweenGroup
11555
+ } = stateRef.current;
11556
+ const nodeId = String(nodeData.id);
11557
+ if (nodeObjects[nodeId]) {
11558
+ const existingMesh = nodeObjects[nodeId];
11559
+ if (position) {
11560
+ const updateTween = new Tween2(existingMesh.position).to(position, 800).easing(Easing2.Cubic.Out);
11561
+ tweenGroup.add(updateTween);
11562
+ updateTween.start();
11563
+ }
11564
+ return existingMesh;
11565
+ }
11566
+ const mesh = createNodeMesh(
11567
+ nodeData,
11568
+ position || new THREE3.Vector3(),
11569
+ glowTexture
11570
+ );
11571
+ graphGroup.add(mesh);
11572
+ if (mesh.userData.labelObject) {
11573
+ if (mesh.userData.labelOffset) {
11574
+ mesh.userData.labelObject.position.copy(mesh.position).add(mesh.userData.labelOffset);
11575
+ }
11576
+ graphGroup.add(mesh.userData.labelObject);
11171
11577
  }
11172
- graphGroup.add(mesh.userData.labelObject);
11173
- }
11174
- nodeObjects[nodeId] = mesh;
11175
- clickableNodes.push(mesh);
11176
- if (!suppressVersionUpdate) {
11177
- setSceneVersion((v) => v + 1);
11178
- }
11179
- return mesh;
11180
- }, []);
11578
+ nodeObjects[nodeId] = mesh;
11579
+ clickableNodes.push(mesh);
11580
+ if (!suppressVersionUpdate) {
11581
+ setSceneVersion((v) => v + 1);
11582
+ }
11583
+ return mesh;
11584
+ },
11585
+ []
11586
+ );
11181
11587
  useEffect22(() => {
11182
11588
  if (!isInitialized || !sceneDataRef.current) return;
11183
11589
  const currentMount = mountRef.current;
@@ -11192,7 +11598,12 @@ function XViewScene({
11192
11598
  const scene = new THREE3.Scene();
11193
11599
  scene.background = new THREE3.Color(0);
11194
11600
  stateRef.current.scene = scene;
11195
- const camera = new THREE3.PerspectiveCamera(75, currentMount.clientWidth / currentMount.clientHeight, 0.1, 2e3);
11601
+ const camera = new THREE3.PerspectiveCamera(
11602
+ 75,
11603
+ currentMount.clientWidth / currentMount.clientHeight,
11604
+ 0.1,
11605
+ 2e3
11606
+ );
11196
11607
  camera.position.set(0, 60, 120);
11197
11608
  stateRef.current.camera = camera;
11198
11609
  const renderer = new THREE3.WebGLRenderer({ antialias: true });
@@ -11213,7 +11624,12 @@ function XViewScene({
11213
11624
  directionalLight.position.set(50, 50, 50);
11214
11625
  scene.add(directionalLight);
11215
11626
  const renderScene = new RenderPass(scene, camera);
11216
- const bloomPass = new UnrealBloomPass(new THREE3.Vector2(currentMount.clientWidth, currentMount.clientHeight), x_view_config.BLOOM_EFFECT.strength, x_view_config.BLOOM_EFFECT.radius, x_view_config.BLOOM_EFFECT.threshold);
11627
+ const bloomPass = new UnrealBloomPass(
11628
+ new THREE3.Vector2(currentMount.clientWidth, currentMount.clientHeight),
11629
+ x_view_config.BLOOM_EFFECT.strength,
11630
+ x_view_config.BLOOM_EFFECT.radius,
11631
+ x_view_config.BLOOM_EFFECT.threshold
11632
+ );
11217
11633
  const composer = new EffectComposer(renderer);
11218
11634
  composer.addPass(renderScene);
11219
11635
  composer.addPass(bloomPass);
@@ -11256,8 +11672,16 @@ function XViewScene({
11256
11672
  const sourceNode = nodeObjects[String(linksArray[0].source)];
11257
11673
  const targetNode = nodeObjects[String(linksArray[0].target)];
11258
11674
  if (sourceNode && targetNode) {
11259
- const resolution = new THREE3.Vector2(currentMount.clientWidth, currentMount.clientHeight);
11260
- const newLinks = createMultipleLinkLines(linksArray, sourceNode, targetNode, resolution);
11675
+ const resolution = new THREE3.Vector2(
11676
+ currentMount.clientWidth,
11677
+ currentMount.clientHeight
11678
+ );
11679
+ const newLinks = createMultipleLinkLines(
11680
+ linksArray,
11681
+ sourceNode,
11682
+ targetNode,
11683
+ resolution
11684
+ );
11261
11685
  newLinks.forEach((line, idx) => {
11262
11686
  const meta = linksArray[idx];
11263
11687
  if (meta) {
@@ -11298,7 +11722,10 @@ function XViewScene({
11298
11722
  function tryPickNode() {
11299
11723
  raycaster.setFromCamera(mouse, camera);
11300
11724
  raycaster.layers.enable(GHOST_BLOOM_LAYER);
11301
- const intersects = raycaster.intersectObjects(stateRef.current.clickableNodes, false);
11725
+ const intersects = raycaster.intersectObjects(
11726
+ stateRef.current.clickableNodes,
11727
+ false
11728
+ );
11302
11729
  return intersects.length > 0 ? intersects[0].object : null;
11303
11730
  }
11304
11731
  function findClosestLinkToRay() {
@@ -11310,7 +11737,10 @@ function XViewScene({
11310
11737
  x: (mouse.x * 0.5 + 0.5) * clientWidth,
11311
11738
  y: (-mouse.y * 0.5 + 0.5) * clientHeight
11312
11739
  };
11313
- const allVisibleLinks = [...stateRef.current.allLinks, ...stateRef.current.ancestryLinks];
11740
+ const allVisibleLinks = [
11741
+ ...stateRef.current.allLinks,
11742
+ ...stateRef.current.ancestryLinks
11743
+ ];
11314
11744
  const THRESH = x_view_config.LINE_HOVER_THRESHOLD_PX + 4;
11315
11745
  const tmpP1 = new THREE3.Vector3();
11316
11746
  const tmpP2 = new THREE3.Vector3();
@@ -11325,19 +11755,35 @@ function XViewScene({
11325
11755
  const up = new THREE3.Vector3(0, 1, 0);
11326
11756
  const normal = new THREE3.Vector3().crossVectors(dir, up).normalize();
11327
11757
  const controlPoint = mid.add(normal.multiplyScalar(curveOffset || 0));
11328
- const curve = new THREE3.QuadraticBezierCurve3(start, controlPoint, end);
11758
+ const curve = new THREE3.QuadraticBezierCurve3(
11759
+ start,
11760
+ controlPoint,
11761
+ end
11762
+ );
11329
11763
  const points = curve.getPoints(x_view_config.CURVE_SEGMENTS || 32);
11330
11764
  for (let i = 0; i < points.length - 1; i++) {
11331
11765
  tmpP1.copy(points[i]).project(camera);
11332
11766
  tmpP2.copy(points[i + 1]).project(camera);
11333
- const p1_px = { x: (tmpP1.x * 0.5 + 0.5) * clientWidth, y: (-tmpP1.y * 0.5 + 0.5) * clientHeight };
11334
- const p2_px = { x: (tmpP2.x * 0.5 + 0.5) * clientWidth, y: (-tmpP2.y * 0.5 + 0.5) * clientHeight };
11767
+ const p1_px = {
11768
+ x: (tmpP1.x * 0.5 + 0.5) * clientWidth,
11769
+ y: (-tmpP1.y * 0.5 + 0.5) * clientHeight
11770
+ };
11771
+ const p2_px = {
11772
+ x: (tmpP2.x * 0.5 + 0.5) * clientWidth,
11773
+ y: (-tmpP2.y * 0.5 + 0.5) * clientHeight
11774
+ };
11335
11775
  const L2 = (p2_px.x - p1_px.x) ** 2 + (p2_px.y - p1_px.y) ** 2;
11336
11776
  if (L2 === 0) continue;
11337
11777
  let t = ((mousePixels.x - p1_px.x) * (p2_px.x - p1_px.x) + (mousePixels.y - p1_px.y) * (p2_px.y - p1_px.y)) / L2;
11338
11778
  t = Math.max(0, Math.min(1, t));
11339
- const closestPoint = { x: p1_px.x + t * (p2_px.x - p1_px.x), y: p1_px.y + t * (p2_px.y - p1_px.y) };
11340
- const dist = Math.hypot(mousePixels.x - closestPoint.x, mousePixels.y - closestPoint.y);
11779
+ const closestPoint = {
11780
+ x: p1_px.x + t * (p2_px.x - p1_px.x),
11781
+ y: p1_px.y + t * (p2_px.y - p1_px.y)
11782
+ };
11783
+ const dist = Math.hypot(
11784
+ mousePixels.x - closestPoint.x,
11785
+ mousePixels.y - closestPoint.y
11786
+ );
11341
11787
  if (dist < THRESH && dist < minDistance) {
11342
11788
  minDistance = dist;
11343
11789
  closestLink = link;
@@ -11346,14 +11792,26 @@ function XViewScene({
11346
11792
  } else {
11347
11793
  const p1 = new THREE3.Vector3().copy(sourceNode.position).project(camera);
11348
11794
  const p2 = new THREE3.Vector3().copy(targetNode.position).project(camera);
11349
- const p1_px = { x: (p1.x * 0.5 + 0.5) * clientWidth, y: (-p1.y * 0.5 + 0.5) * clientHeight };
11350
- const p2_px = { x: (p2.x * 0.5 + 0.5) * clientWidth, y: (-p2.y * 0.5 + 0.5) * clientHeight };
11795
+ const p1_px = {
11796
+ x: (p1.x * 0.5 + 0.5) * clientWidth,
11797
+ y: (-p1.y * 0.5 + 0.5) * clientHeight
11798
+ };
11799
+ const p2_px = {
11800
+ x: (p2.x * 0.5 + 0.5) * clientWidth,
11801
+ y: (-p2.y * 0.5 + 0.5) * clientHeight
11802
+ };
11351
11803
  const L2 = (p2_px.x - p1_px.x) ** 2 + (p2_px.y - p1_px.y) ** 2;
11352
11804
  if (L2 === 0) return;
11353
11805
  let t = ((mousePixels.x - p1_px.x) * (p2_px.x - p1_px.x) + (mousePixels.y - p1_px.y) * (p2_px.y - p1_px.y)) / L2;
11354
11806
  t = Math.max(0, Math.min(1, t));
11355
- const closestPoint = { x: p1_px.x + t * (p2_px.x - p1_px.x), y: p1_px.y + t * (p2_px.y - p1_px.y) };
11356
- const dist = Math.hypot(mousePixels.x - closestPoint.x, mousePixels.y - closestPoint.y);
11807
+ const closestPoint = {
11808
+ x: p1_px.x + t * (p2_px.x - p1_px.x),
11809
+ y: p1_px.y + t * (p2_px.y - p1_px.y)
11810
+ };
11811
+ const dist = Math.hypot(
11812
+ mousePixels.x - closestPoint.x,
11813
+ mousePixels.y - closestPoint.y
11814
+ );
11357
11815
  if (dist < THRESH && dist < minDistance) {
11358
11816
  minDistance = dist;
11359
11817
  closestLink = link;
@@ -11388,7 +11846,11 @@ function XViewScene({
11388
11846
  if (picked && !stateRef.current.ancestry.isActive) {
11389
11847
  stateRef.current.controls.enabled = false;
11390
11848
  }
11391
- stateRef.current.pointerDown = { isDown: true, x: event.clientX, y: event.clientY };
11849
+ stateRef.current.pointerDown = {
11850
+ isDown: true,
11851
+ x: event.clientX,
11852
+ y: event.clientY
11853
+ };
11392
11854
  stateRef.current.dragCandidate = picked;
11393
11855
  }
11394
11856
  function onPointerMove(event) {
@@ -11399,7 +11861,10 @@ function XViewScene({
11399
11861
  const raycaster2 = new THREE3.Raycaster();
11400
11862
  raycaster2.setFromCamera(mouse, camera);
11401
11863
  camera.getWorldDirection(plane.normal);
11402
- plane.setFromNormalAndCoplanarPoint(plane.normal, stateRef.current.draggedNode.position);
11864
+ plane.setFromNormalAndCoplanarPoint(
11865
+ plane.normal,
11866
+ stateRef.current.draggedNode.position
11867
+ );
11403
11868
  if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) {
11404
11869
  const draggedNode = stateRef.current.draggedNode;
11405
11870
  draggedNode.position.copy(intersectionPoint);
@@ -11408,7 +11873,8 @@ function XViewScene({
11408
11873
  }
11409
11874
  if (stateRef.current.connection.isActive || stateRef.current.relink.isActive || stateRef.current.ancestry.isActive) {
11410
11875
  const newHoveredNode2 = tryPickNode();
11411
- if (stateRef.current.hoveredNode !== newHoveredNode2) stateRef.current.hoveredNode = newHoveredNode2;
11876
+ if (stateRef.current.hoveredNode !== newHoveredNode2)
11877
+ stateRef.current.hoveredNode = newHoveredNode2;
11412
11878
  if (currentMount) {
11413
11879
  let fixedId = null;
11414
11880
  if (stateRef.current.connection.isActive) {
@@ -11439,14 +11905,20 @@ function XViewScene({
11439
11905
  stateRef.current.controls.enabled = false;
11440
11906
  if (currentMount) currentMount.style.cursor = "grabbing";
11441
11907
  camera.getWorldDirection(plane.normal);
11442
- plane.setFromNormalAndCoplanarPoint(plane.normal, stateRef.current.draggedNode.position);
11908
+ plane.setFromNormalAndCoplanarPoint(
11909
+ plane.normal,
11910
+ stateRef.current.draggedNode.position
11911
+ );
11443
11912
  }
11444
11913
  }
11445
11914
  const newHoveredNode = tryPickNode();
11446
11915
  const newHoveredLink = !newHoveredNode ? findClosestLinkToRay() : null;
11447
- if (stateRef.current.hoveredNode !== newHoveredNode) stateRef.current.hoveredNode = newHoveredNode;
11448
- if (stateRef.current.hoveredLink !== newHoveredLink) stateRef.current.hoveredLink = newHoveredLink;
11449
- if (currentMount) currentMount.style.cursor = newHoveredNode || newHoveredLink ? "pointer" : "grab";
11916
+ if (stateRef.current.hoveredNode !== newHoveredNode)
11917
+ stateRef.current.hoveredNode = newHoveredNode;
11918
+ if (stateRef.current.hoveredLink !== newHoveredLink)
11919
+ stateRef.current.hoveredLink = newHoveredLink;
11920
+ if (currentMount)
11921
+ currentMount.style.cursor = newHoveredNode || newHoveredLink ? "pointer" : "grab";
11450
11922
  }
11451
11923
  const isNodeInTree = (tree, nodeId) => {
11452
11924
  if (!tree) return false;
@@ -11461,7 +11933,10 @@ function XViewScene({
11461
11933
  const context = actionHandlerContext;
11462
11934
  if (connection.isActive) {
11463
11935
  if (hoveredNode && String(hoveredNode.userData.id) !== String(connection.sourceNodeData.id)) {
11464
- await userActionHandlers.handleCompleteConnection(context, hoveredNode.userData);
11936
+ await userActionHandlers.handleCompleteConnection(
11937
+ context,
11938
+ hoveredNode.userData
11939
+ );
11465
11940
  } else {
11466
11941
  userActionHandlers.handleCancelConnection(context);
11467
11942
  }
@@ -11469,7 +11944,10 @@ function XViewScene({
11469
11944
  }
11470
11945
  if (relink.isActive) {
11471
11946
  if (hoveredNode && String(hoveredNode.userData.id) !== String(relink.fixedNodeId)) {
11472
- await userActionHandlers.handleCompleteRelink(context, hoveredNode.userData);
11947
+ await userActionHandlers.handleCompleteRelink(
11948
+ context,
11949
+ hoveredNode.userData
11950
+ );
11473
11951
  } else {
11474
11952
  userActionHandlers.handleCancelRelink(context);
11475
11953
  }
@@ -11487,7 +11965,9 @@ function XViewScene({
11487
11965
  const clickedNodeId = String(clickedNode.userData.id);
11488
11966
  const parentId = String(currentSelectedParent);
11489
11967
  if (clickedNodeId === parentId) {
11490
- alert("Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo.");
11968
+ alert(
11969
+ "Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo."
11970
+ );
11491
11971
  return;
11492
11972
  }
11493
11973
  const parentInfo = stateRef.current.nodeIdToParentFileMap.get(clickedNodeId);
@@ -11497,16 +11977,33 @@ function XViewScene({
11497
11977
  const addChildToNode = (current, targetParentId, childNode) => {
11498
11978
  const currentId = current.is_section ? current.id || current.section_id : String(current.node.id);
11499
11979
  if (String(currentId) === String(targetParentId)) {
11500
- const alreadyExists = current.children.some((child) => !child.is_section && String(child.node.id) === String(childNode.id));
11980
+ const alreadyExists = current.children.some(
11981
+ (child) => !child.is_section && String(child.node.id) === String(childNode.id)
11982
+ );
11501
11983
  if (alreadyExists) return current;
11502
- return { ...current, children: [...current.children, { node: childNode, children: [], relationship: {} }] };
11984
+ return {
11985
+ ...current,
11986
+ children: [
11987
+ ...current.children,
11988
+ { node: childNode, children: [], relationship: {} }
11989
+ ]
11990
+ };
11503
11991
  }
11504
- return { ...current, children: current.children.map((c) => addChildToNode(c, targetParentId, childNode)) };
11992
+ return {
11993
+ ...current,
11994
+ children: current.children.map(
11995
+ (c) => addChildToNode(c, targetParentId, childNode)
11996
+ )
11997
+ };
11505
11998
  };
11506
11999
  setAncestryMode((prev) => {
11507
12000
  const treeKey = isAbstraction ? "abstraction_tree" : "tree";
11508
12001
  if (!prev[treeKey]) return prev;
11509
- const newTree = addChildToNode(prev[treeKey], parentId, fullNodeData);
12002
+ const newTree = addChildToNode(
12003
+ prev[treeKey],
12004
+ parentId,
12005
+ fullNodeData
12006
+ );
11510
12007
  return { ...prev, [treeKey]: newTree };
11511
12008
  });
11512
12009
  }
@@ -11520,7 +12017,8 @@ function XViewScene({
11520
12017
  stateRef.current.dragCandidate = null;
11521
12018
  stateRef.current.pointerDown.isDown = false;
11522
12019
  stateRef.current.controls.enabled = true;
11523
- if (currentMount) currentMount.style.cursor = stateRef.current.hoveredNode || stateRef.current.hoveredLink ? "pointer" : "grab";
12020
+ if (currentMount)
12021
+ currentMount.style.cursor = stateRef.current.hoveredNode || stateRef.current.hoveredLink ? "pointer" : "grab";
11524
12022
  return;
11525
12023
  }
11526
12024
  const dragDistance = Math.hypot(
@@ -11568,16 +12066,21 @@ function XViewScene({
11568
12066
  }
11569
12067
  function handleDoubleClick(event) {
11570
12068
  if (stateRef.current.camera) stateRef.current.camera.layers.enableAll();
11571
- if (isFromUiOverlay(event) || stateRef.current.isDragging || stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive) return;
11572
- if (stateRef.current.hoveredNode) tweenToTarget(stateRef.current.hoveredNode);
12069
+ if (isFromUiOverlay(event) || stateRef.current.isDragging || stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive)
12070
+ return;
12071
+ if (stateRef.current.hoveredNode)
12072
+ tweenToTarget(stateRef.current.hoveredNode);
11573
12073
  }
11574
12074
  function handleContextMenu(event) {
11575
12075
  if (stateRef.current.camera) stateRef.current.camera.layers.enableAll();
11576
12076
  if (isFromUiOverlay(event)) return;
11577
12077
  event.preventDefault();
11578
- if (stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive) return;
12078
+ if (stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive)
12079
+ return;
11579
12080
  setMouseFromEvent(event);
11580
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12081
+ setContextMenu(
12082
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12083
+ );
11581
12084
  setMultiContextMenu((prev) => ({ ...prev, visible: false }));
11582
12085
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
11583
12086
  const pickedNode = tryPickNode();
@@ -11608,7 +12111,12 @@ function XViewScene({
11608
12111
  return;
11609
12112
  }
11610
12113
  stateRef.current.selectedNodes.clear();
11611
- setRelationshipMenu({ visible: true, x: event.clientX, y: event.clientY, linkObject: pickedLink });
12114
+ setRelationshipMenu({
12115
+ visible: true,
12116
+ x: event.clientX,
12117
+ y: event.clientY,
12118
+ linkObject: pickedLink
12119
+ });
11612
12120
  return;
11613
12121
  }
11614
12122
  stateRef.current.selectedNodes.clear();
@@ -11640,7 +12148,10 @@ function XViewScene({
11640
12148
  }
11641
12149
  });
11642
12150
  }
11643
- const allRenderedLinks = [...stateRef.current.allLinks, ...stateRef.current.ancestryLinks];
12151
+ const allRenderedLinks = [
12152
+ ...stateRef.current.allLinks,
12153
+ ...stateRef.current.ancestryLinks
12154
+ ];
11644
12155
  allRenderedLinks.forEach((line) => {
11645
12156
  const { sourceNode, targetNode, isCurved, curveOffset } = line.userData;
11646
12157
  if (sourceNode && targetNode) {
@@ -11652,13 +12163,20 @@ function XViewScene({
11652
12163
  const up = new THREE3.Vector3(0, 1, 0);
11653
12164
  const normal = new THREE3.Vector3().crossVectors(dir, up).normalize();
11654
12165
  const controlPoint = mid.add(normal.multiplyScalar(curveOffset));
11655
- const curve = new THREE3.QuadraticBezierCurve3(start, controlPoint, end);
12166
+ const curve = new THREE3.QuadraticBezierCurve3(
12167
+ start,
12168
+ controlPoint,
12169
+ end
12170
+ );
11656
12171
  const points = curve.getPoints(x_view_config.CURVE_SEGMENTS);
11657
12172
  const positions = [];
11658
12173
  points.forEach((p) => positions.push(p.x, p.y, p.z));
11659
12174
  line.geometry.setPositions(positions);
11660
12175
  } else {
11661
- line.geometry.setPositions([...sourceNode.position.toArray(), ...targetNode.position.toArray()]);
12176
+ line.geometry.setPositions([
12177
+ ...sourceNode.position.toArray(),
12178
+ ...targetNode.position.toArray()
12179
+ ]);
11662
12180
  }
11663
12181
  }
11664
12182
  });
@@ -11671,7 +12189,11 @@ function XViewScene({
11671
12189
  const startPos = node.position;
11672
12190
  const distance = startPos.distanceTo(endPos);
11673
12191
  bar.scale.y = distance;
11674
- const midpoint = new THREE3.Vector3().lerpVectors(startPos, endPos, 0.5);
12192
+ const midpoint = new THREE3.Vector3().lerpVectors(
12193
+ startPos,
12194
+ endPos,
12195
+ 0.5
12196
+ );
11675
12197
  bar.position.copy(midpoint);
11676
12198
  const direction = new THREE3.Vector3().subVectors(endPos, startPos).normalize();
11677
12199
  const upVector = new THREE3.Vector3(0, 1, 0);
@@ -11682,7 +12204,11 @@ function XViewScene({
11682
12204
  const { ghostElements, creation, connection, relink } = stateRef.current;
11683
12205
  if (creation.isActive && ghostElements.node && ghostElements.line) {
11684
12206
  const srcMesh = stateRef.current.nodeObjects[String(creation.sourceNodeData.id)];
11685
- if (srcMesh) ghostElements.line.geometry.setPositions([...srcMesh.position.toArray(), ...ghostElements.node.position.toArray()]);
12207
+ if (srcMesh)
12208
+ ghostElements.line.geometry.setPositions([
12209
+ ...srcMesh.position.toArray(),
12210
+ ...ghostElements.node.position.toArray()
12211
+ ]);
11686
12212
  }
11687
12213
  if (connection.isActive && connection.line) {
11688
12214
  const srcMesh = stateRef.current.nodeObjects[String(connection.sourceNodeData.id)];
@@ -11691,7 +12217,11 @@ function XViewScene({
11691
12217
  raycaster2.setFromCamera(mouse, camera);
11692
12218
  camera.getWorldDirection(plane.normal);
11693
12219
  plane.setFromNormalAndCoplanarPoint(plane.normal, srcMesh.position);
11694
- if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) connection.line.geometry.setPositions([...srcMesh.position.toArray(), ...intersectionPoint.toArray()]);
12220
+ if (raycaster2.ray.intersectPlane(plane, intersectionPoint))
12221
+ connection.line.geometry.setPositions([
12222
+ ...srcMesh.position.toArray(),
12223
+ ...intersectionPoint.toArray()
12224
+ ]);
11695
12225
  }
11696
12226
  }
11697
12227
  if (relink.isActive && relink.line) {
@@ -11701,7 +12231,11 @@ function XViewScene({
11701
12231
  raycaster2.setFromCamera(mouse, camera);
11702
12232
  camera.getWorldDirection(plane.normal);
11703
12233
  plane.setFromNormalAndCoplanarPoint(plane.normal, fixedMesh.position);
11704
- if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) relink.line.geometry.setPositions([...fixedMesh.position.toArray(), ...intersectionPoint.toArray()]);
12234
+ if (raycaster2.ray.intersectPlane(plane, intersectionPoint))
12235
+ relink.line.geometry.setPositions([
12236
+ ...fixedMesh.position.toArray(),
12237
+ ...intersectionPoint.toArray()
12238
+ ]);
11705
12239
  }
11706
12240
  }
11707
12241
  Object.values(stateRef.current.nodeObjects).forEach((node) => {
@@ -11745,11 +12279,18 @@ function XViewScene({
11745
12279
  renderer.setSize(clientWidth, clientHeight);
11746
12280
  composer.setSize(clientWidth, clientHeight);
11747
12281
  const resVec = new THREE3.Vector2(clientWidth, clientHeight);
11748
- stateRef.current.allLinks.forEach((line) => line.material.resolution.copy(resVec));
11749
- stateRef.current.ancestryLinks.forEach((line) => line.material.resolution.copy(resVec));
11750
- if (stateRef.current.ghostElements.line) stateRef.current.ghostElements.line.material.resolution.copy(resVec);
11751
- if (stateRef.current.connection.line) stateRef.current.connection.line.material.resolution.copy(resVec);
11752
- if (stateRef.current.relink.line) stateRef.current.relink.line.material.resolution.copy(resVec);
12282
+ stateRef.current.allLinks.forEach(
12283
+ (line) => line.material.resolution.copy(resVec)
12284
+ );
12285
+ stateRef.current.ancestryLinks.forEach(
12286
+ (line) => line.material.resolution.copy(resVec)
12287
+ );
12288
+ if (stateRef.current.ghostElements.line)
12289
+ stateRef.current.ghostElements.line.material.resolution.copy(resVec);
12290
+ if (stateRef.current.connection.line)
12291
+ stateRef.current.connection.line.material.resolution.copy(resVec);
12292
+ if (stateRef.current.relink.line)
12293
+ stateRef.current.relink.line.material.resolution.copy(resVec);
11753
12294
  }
11754
12295
  window.addEventListener("resize", handleResize);
11755
12296
  return () => {
@@ -11769,14 +12310,16 @@ function XViewScene({
11769
12310
  ancestryGroup.traverse((obj) => {
11770
12311
  if (obj.geometry) obj.geometry.dispose();
11771
12312
  if (obj.material) {
11772
- if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
12313
+ if (Array.isArray(obj.material))
12314
+ obj.material.forEach((m) => m.dispose());
11773
12315
  else obj.material.dispose();
11774
12316
  }
11775
12317
  });
11776
12318
  graphGroup.traverse((obj) => {
11777
12319
  if (obj.geometry) obj.geometry.dispose();
11778
12320
  if (obj.material) {
11779
- if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
12321
+ if (Array.isArray(obj.material))
12322
+ obj.material.forEach((m) => m.dispose());
11780
12323
  else obj.material.dispose();
11781
12324
  }
11782
12325
  });
@@ -11787,9 +12330,22 @@ function XViewScene({
11787
12330
  currentMount.removeChild(renderer.domElement);
11788
12331
  }
11789
12332
  };
11790
- }, [isInitialized, tweenToTarget, dbSaveUrl, isNodeInView, addOrUpdateNodeMesh, handleActivateTimeline, get_scene_view_data, save_view_data]);
12333
+ }, [
12334
+ isInitialized,
12335
+ tweenToTarget,
12336
+ dbSaveUrl,
12337
+ isNodeInView,
12338
+ addOrUpdateNodeMesh,
12339
+ handleActivateTimeline,
12340
+ get_scene_view_data,
12341
+ save_view_data
12342
+ ]);
11791
12343
  const handleGhostNodeImageChange = useCallback4((useImage, imageUrl) => {
11792
- const { node: ghostNode, line: ghostLine, aura: ghostAura } = stateRef.current.ghostElements;
12344
+ const {
12345
+ node: ghostNode,
12346
+ line: ghostLine,
12347
+ aura: ghostAura
12348
+ } = stateRef.current.ghostElements;
11793
12349
  const { graphGroup, glowTexture } = stateRef.current;
11794
12350
  if (!ghostNode || !graphGroup) return;
11795
12351
  const currentData = {
@@ -11800,13 +12356,15 @@ function XViewScene({
11800
12356
  const position = ghostNode.position.clone();
11801
12357
  if (ghostNode.userData.labelObject) {
11802
12358
  graphGroup.remove(ghostNode.userData.labelObject);
11803
- if (ghostNode.userData.labelObject.material.map) ghostNode.userData.labelObject.material.map.dispose();
12359
+ if (ghostNode.userData.labelObject.material.map)
12360
+ ghostNode.userData.labelObject.material.map.dispose();
11804
12361
  ghostNode.userData.labelObject.material.dispose();
11805
12362
  }
11806
12363
  graphGroup.remove(ghostNode);
11807
12364
  if (ghostNode.geometry) ghostNode.geometry.dispose();
11808
12365
  if (ghostNode.material) {
11809
- if (Array.isArray(ghostNode.material)) ghostNode.material.forEach((m) => m.dispose());
12366
+ if (Array.isArray(ghostNode.material))
12367
+ ghostNode.material.forEach((m) => m.dispose());
11810
12368
  else ghostNode.material.dispose();
11811
12369
  }
11812
12370
  const newGhostNode = createNodeMesh(currentData, position, glowTexture);
@@ -11851,28 +12409,31 @@ function XViewScene({
11851
12409
  ghostAura.material.opacity = Math.min(0.8, newIntensity * 0.15);
11852
12410
  }
11853
12411
  }, []);
11854
- const handleDetailNodeIntensityChange = useCallback4((nodeId, newIntensity) => {
11855
- const mesh = stateRef.current.nodeObjects[String(nodeId)];
11856
- if (!mesh) return;
11857
- const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
11858
- mesh.userData.intensity = newIntensity;
11859
- mesh.userData._baseEmissiveIntensity = adjustedIntensity;
11860
- const isImageNode = mesh.userData.useImageAsTexture === true;
11861
- if (isImageNode) {
11862
- const borderMesh = mesh.getObjectByName("borderRing");
11863
- if (borderMesh && borderMesh.material) {
11864
- borderMesh.material.emissiveIntensity = adjustedIntensity;
12412
+ const handleDetailNodeIntensityChange = useCallback4(
12413
+ (nodeId, newIntensity) => {
12414
+ const mesh = stateRef.current.nodeObjects[String(nodeId)];
12415
+ if (!mesh) return;
12416
+ const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
12417
+ mesh.userData.intensity = newIntensity;
12418
+ mesh.userData._baseEmissiveIntensity = adjustedIntensity;
12419
+ const isImageNode = mesh.userData.useImageAsTexture === true;
12420
+ if (isImageNode) {
12421
+ const borderMesh = mesh.getObjectByName("borderRing");
12422
+ if (borderMesh && borderMesh.material) {
12423
+ borderMesh.material.emissiveIntensity = adjustedIntensity;
12424
+ }
12425
+ } else {
12426
+ if (mesh.material) {
12427
+ mesh.material.emissiveIntensity = adjustedIntensity;
12428
+ }
11865
12429
  }
11866
- } else {
11867
- if (mesh.material) {
11868
- mesh.material.emissiveIntensity = adjustedIntensity;
12430
+ const aura = mesh.getObjectByName("aura");
12431
+ if (aura && aura.material) {
12432
+ aura.material.opacity = Math.min(0.8, newIntensity * 0.15);
11869
12433
  }
11870
- }
11871
- const aura = mesh.getObjectByName("aura");
11872
- if (aura && aura.material) {
11873
- aura.material.opacity = Math.min(0.8, newIntensity * 0.15);
11874
- }
11875
- }, []);
12434
+ },
12435
+ []
12436
+ );
11876
12437
  const handleGhostNodeColorChange = (newColor) => {
11877
12438
  const { node: ghostNode, aura: ghostAura } = stateRef.current.ghostElements;
11878
12439
  if (!ghostNode) return;
@@ -11983,7 +12544,9 @@ function XViewScene({
11983
12544
  mesh.userData.size = newSize;
11984
12545
  };
11985
12546
  const handleStartAncestryCreation = (nodeData) => {
11986
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12547
+ setContextMenu(
12548
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12549
+ );
11987
12550
  stateRef.current.maxAncestryRenderIndex = 0;
11988
12551
  setAncestryMode({
11989
12552
  isActive: true,
@@ -12008,9 +12571,12 @@ function XViewScene({
12008
12571
  const newTreeStr = JSON.stringify(newTree);
12009
12572
  let metaChanged = false;
12010
12573
  if (extraData) {
12011
- if (extraData.ancestryName !== void 0 && extraData.ancestryName !== prev.ancestryName) metaChanged = true;
12012
- if (extraData.ancestryDescription !== void 0 && extraData.ancestryDescription !== prev.ancestryDescription) metaChanged = true;
12013
- if (extraData.ancestryDescriptionSections !== void 0 && JSON.stringify(extraData.ancestryDescriptionSections) !== JSON.stringify(prev.ancestryDescriptionSections)) metaChanged = true;
12574
+ if (extraData.ancestryName !== void 0 && extraData.ancestryName !== prev.ancestryName)
12575
+ metaChanged = true;
12576
+ if (extraData.ancestryDescription !== void 0 && extraData.ancestryDescription !== prev.ancestryDescription)
12577
+ metaChanged = true;
12578
+ if (extraData.ancestryDescriptionSections !== void 0 && JSON.stringify(extraData.ancestryDescriptionSections) !== JSON.stringify(prev.ancestryDescriptionSections))
12579
+ metaChanged = true;
12014
12580
  }
12015
12581
  if (prevTreeStr === newTreeStr && !metaChanged) {
12016
12582
  return prev;
@@ -12088,13 +12654,15 @@ function XViewScene({
12088
12654
  if (ghostElements.node && graphGroup) {
12089
12655
  if (ghostElements.node.userData.labelObject) {
12090
12656
  graphGroup.remove(ghostElements.node.userData.labelObject);
12091
- if (ghostElements.node.userData.labelObject.material.map) ghostElements.node.userData.labelObject.material.map.dispose();
12657
+ if (ghostElements.node.userData.labelObject.material.map)
12658
+ ghostElements.node.userData.labelObject.material.map.dispose();
12092
12659
  ghostElements.node.userData.labelObject.material.dispose();
12093
12660
  }
12094
12661
  graphGroup.remove(ghostElements.node);
12095
12662
  ghostElements.node.traverse((child) => {
12096
12663
  if (child.material) {
12097
- if (Array.isArray(child.material)) child.material.forEach((m) => m.dispose());
12664
+ if (Array.isArray(child.material))
12665
+ child.material.forEach((m) => m.dispose());
12098
12666
  else child.material.dispose();
12099
12667
  }
12100
12668
  if (child.geometry) child.geometry.dispose();
@@ -12104,7 +12672,17 @@ function XViewScene({
12104
12672
  setQuestMode({ isActive: false });
12105
12673
  }, []);
12106
12674
  const handleSaveQuestNode = async (context, newQuestData) => {
12107
- const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
12675
+ const {
12676
+ graphDataRef,
12677
+ sceneDataRef: sceneDataRef2,
12678
+ stateRef: stateRef2,
12679
+ setters,
12680
+ actions,
12681
+ sceneSaveUrl: sceneSaveUrl2,
12682
+ viewType,
12683
+ sceneConfigId: sceneConfigId2,
12684
+ ownerId: ownerId2
12685
+ } = context;
12108
12686
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
12109
12687
  const currentCounter = sceneDataRef2.current.quest_counter || 1;
12110
12688
  const newNode = {
@@ -12137,7 +12715,8 @@ function XViewScene({
12137
12715
  const finalPosition = stateRef2.current.ghostElements.node ? stateRef2.current.ghostElements.node.position.clone() : stateRef2.current.controls.target.clone();
12138
12716
  const { graphGroup, ghostElements } = stateRef2.current;
12139
12717
  if (ghostElements.node && graphGroup) {
12140
- if (ghostElements.node.userData.labelObject) graphGroup.remove(ghostElements.node.userData.labelObject);
12718
+ if (ghostElements.node.userData.labelObject)
12719
+ graphGroup.remove(ghostElements.node.userData.labelObject);
12141
12720
  graphGroup.remove(ghostElements.node);
12142
12721
  }
12143
12722
  stateRef2.current.ghostElements = { node: null, line: null, aura: null };
@@ -12151,14 +12730,33 @@ function XViewScene({
12151
12730
  }
12152
12731
  };
12153
12732
  userActionHandlers.handleCompleteConnection = async (context, targetNodeData) => {
12154
- const { stateRef: stateRef2, graphDataRef, sceneDataRef: sceneDataRef2, sceneConfigId: sceneConfigId2, sceneSaveUrl: sceneSaveUrl2, ownerId: ownerId2 } = context;
12733
+ const {
12734
+ stateRef: stateRef2,
12735
+ graphDataRef,
12736
+ sceneDataRef: sceneDataRef2,
12737
+ sceneConfigId: sceneConfigId2,
12738
+ sceneSaveUrl: sceneSaveUrl2,
12739
+ ownerId: ownerId2
12740
+ } = context;
12155
12741
  const { sourceNodeData } = stateRef2.current.connection;
12156
12742
  if (!graphDataRef.current || !sceneDataRef2.current || !sourceNodeData || !targetNodeData) {
12157
12743
  userActionHandlers.handleCancelConnection(context);
12158
12744
  return;
12159
12745
  }
12160
- const sourceParentInfo = getParentFileInfoForNode(graphDataRef.current, sceneDataRef2.current, sourceNodeData.id, sceneConfigId2, ownerId2);
12161
- const targetParentInfo = getParentFileInfoForNode(graphDataRef.current, sceneDataRef2.current, targetNodeData.id, sceneConfigId2, ownerId2);
12746
+ const sourceParentInfo = getParentFileInfoForNode(
12747
+ graphDataRef.current,
12748
+ sceneDataRef2.current,
12749
+ sourceNodeData.id,
12750
+ sceneConfigId2,
12751
+ ownerId2
12752
+ );
12753
+ const targetParentInfo = getParentFileInfoForNode(
12754
+ graphDataRef.current,
12755
+ sceneDataRef2.current,
12756
+ targetNodeData.id,
12757
+ sceneConfigId2,
12758
+ ownerId2
12759
+ );
12162
12760
  let parentInfoToSave = sourceParentInfo;
12163
12761
  const isSourceQuest = sourceParentInfo.parentFileId === sceneConfigId2;
12164
12762
  const isTargetQuest = targetParentInfo.parentFileId === sceneConfigId2;
@@ -12184,10 +12782,15 @@ function XViewScene({
12184
12782
  };
12185
12783
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
12186
12784
  } else {
12187
- const specificParentData = JSON.parse(JSON.stringify(graphDataRef.current[parentFileIdToSave]));
12785
+ const specificParentData = JSON.parse(
12786
+ JSON.stringify(graphDataRef.current[parentFileIdToSave])
12787
+ );
12188
12788
  specificParentData.links.push(newLink);
12189
12789
  const filenameForSpecificParent = `x_view_dbs/${ownerIdToSave}/${parentFileIdToSave}`;
12190
- await context.actions.save_view_data(filenameForSpecificParent, specificParentData);
12790
+ await context.actions.save_view_data(
12791
+ filenameForSpecificParent,
12792
+ specificParentData
12793
+ );
12191
12794
  graphDataRef.current[parentFileIdToSave] = specificParentData;
12192
12795
  }
12193
12796
  addNewLinkToScene(stateRef2.current, newLink);
@@ -12199,7 +12802,9 @@ function XViewScene({
12199
12802
  };
12200
12803
  const handleClearAncestryVisuals = useCallback4((ancestryId) => {
12201
12804
  const { renderedAncestries, ancestryGroup } = stateRef.current;
12202
- const renderIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryId));
12805
+ const renderIndex = renderedAncestries.findIndex(
12806
+ (a) => String(a.id) === String(ancestryId)
12807
+ );
12203
12808
  if (renderIndex !== -1) {
12204
12809
  const toRemove = renderedAncestries[renderIndex];
12205
12810
  toRemove.lines.forEach((line) => {
@@ -12208,12 +12813,16 @@ function XViewScene({
12208
12813
  if (line.material) line.material.dispose();
12209
12814
  });
12210
12815
  renderedAncestries.splice(renderIndex, 1);
12211
- stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
12816
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
12817
+ (a) => a.lines
12818
+ );
12212
12819
  }
12213
12820
  }, []);
12214
12821
  const handleRenderAncestry = useCallback4(
12215
12822
  async (ancestryObject, allowedSectionIds = null, activeSectionIdForFocus = null, baseRotation = 0, forceReprocess = true) => {
12216
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12823
+ setContextMenu(
12824
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12825
+ );
12217
12826
  if (!ancestryObject || !ancestryObject.tree) {
12218
12827
  return;
12219
12828
  }
@@ -12240,14 +12849,16 @@ function XViewScene({
12240
12849
  if (numId !== void 0 && normalizedAllowedIds.has(numId)) return true;
12241
12850
  if (strId && normalizedAllowedIds.has(strId)) return true;
12242
12851
  if (strSecId && normalizedAllowedIds.has(strSecId)) return true;
12243
- if (numId !== void 0 && normalizedAllowedIds.has(String(numId))) return true;
12852
+ if (numId !== void 0 && normalizedAllowedIds.has(String(numId)))
12853
+ return true;
12244
12854
  return false;
12245
12855
  };
12246
12856
  const checkIsActive = (section, targetId) => {
12247
12857
  if (targetId === null || targetId === void 0) return false;
12248
12858
  const sTarget = String(targetId);
12249
12859
  if (section.id && String(section.id) === sTarget) return true;
12250
- if (section.section_id && String(section.section_id) === sTarget) return true;
12860
+ if (section.section_id && String(section.section_id) === sTarget)
12861
+ return true;
12251
12862
  if (section.section_numeric_id !== void 0) {
12252
12863
  if (String(section.section_numeric_id) === sTarget) return true;
12253
12864
  }
@@ -12263,7 +12874,9 @@ function XViewScene({
12263
12874
  traverse(sectionTree);
12264
12875
  return ids;
12265
12876
  };
12266
- const existingIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryObject.ancestry_id));
12877
+ const existingIndex = renderedAncestries.findIndex(
12878
+ (a) => String(a.id) === String(ancestryObject.ancestry_id)
12879
+ );
12267
12880
  let skipGeneration = false;
12268
12881
  let ancestryEntry = null;
12269
12882
  if (existingIndex !== -1) {
@@ -12318,9 +12931,15 @@ function XViewScene({
12318
12931
  if (line.material) line.material.dispose();
12319
12932
  });
12320
12933
  }
12321
- const allParentNodes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes);
12934
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
12935
+ (fileData) => fileData.nodes
12936
+ );
12322
12937
  const allAncestries = ancestryDataRef.current || [];
12323
- const fullTree = buildFullAncestryTree(ancestryObject.tree, allParentNodes, allAncestries);
12938
+ const fullTree = buildFullAncestryTree(
12939
+ ancestryObject.tree,
12940
+ allParentNodes,
12941
+ allAncestries
12942
+ );
12324
12943
  if (!fullTree) return;
12325
12944
  const rootNodeId = String(ancestryObject.ancestral_node);
12326
12945
  let rootNodeMesh = nodeObjects[rootNodeId];
@@ -12363,7 +12982,10 @@ function XViewScene({
12363
12982
  const colorIndex = stateRef.current.ancestryRenderCounter % 3;
12364
12983
  colorHex = ANCESTRY_COLORS[colorIndex];
12365
12984
  }
12366
- const resolution = new THREE3.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
12985
+ const resolution = new THREE3.Vector2(
12986
+ renderer.domElement.clientWidth,
12987
+ renderer.domElement.clientHeight
12988
+ );
12367
12989
  const cleanupLinesForNode = (nodeId) => {
12368
12990
  if (!ancestryEntry || !ancestryEntry.lines) return;
12369
12991
  const linesKeep = [];
@@ -12403,7 +13025,13 @@ function XViewScene({
12403
13025
  }
12404
13026
  if (originMesh && rootNodeMesh) {
12405
13027
  cleanupLinesForNode(rootNodeId);
12406
- const branchLine = createAncestryLinkLine(originMesh, rootNodeMesh, resolution, {}, colorHex);
13028
+ const branchLine = createAncestryLinkLine(
13029
+ originMesh,
13030
+ rootNodeMesh,
13031
+ resolution,
13032
+ {},
13033
+ colorHex
13034
+ );
12407
13035
  ancestryGroup.add(branchLine);
12408
13036
  ancestryEntry.lines.push(branchLine);
12409
13037
  }
@@ -12414,8 +13042,14 @@ function XViewScene({
12414
13042
  if (!children || children.length === 0) return null;
12415
13043
  let lastRenderedMesh = null;
12416
13044
  const numChildren = children.length;
12417
- const forwardVec = new THREE3.Vector3(0, 0, 1).applyAxisAngle(new THREE3.Vector3(0, 1, 0), currentAngle);
12418
- const rightVec = new THREE3.Vector3(1, 0, 0).applyAxisAngle(new THREE3.Vector3(0, 1, 0), currentAngle);
13045
+ const forwardVec = new THREE3.Vector3(0, 0, 1).applyAxisAngle(
13046
+ new THREE3.Vector3(0, 1, 0),
13047
+ currentAngle
13048
+ );
13049
+ const rightVec = new THREE3.Vector3(1, 0, 0).applyAxisAngle(
13050
+ new THREE3.Vector3(0, 1, 0),
13051
+ currentAngle
13052
+ );
12419
13053
  const angleRange = numChildren > 3 ? Math.PI : Math.PI / 1.5;
12420
13054
  children.forEach((childItem, index) => {
12421
13055
  if (childItem.is_section) return;
@@ -12436,14 +13070,30 @@ function XViewScene({
12436
13070
  targetPositionsCache.set(sNodeId, childPosition.clone());
12437
13071
  cleanupLinesForNode(sNodeId);
12438
13072
  }
12439
- const childMesh = addOrUpdateNodeMesh(childItem.node, childPosition, true);
13073
+ const childMesh = addOrUpdateNodeMesh(
13074
+ childItem.node,
13075
+ childPosition,
13076
+ true
13077
+ );
12440
13078
  allRenderedNodePositions.push(childPosition);
12441
- const line = createAncestryLinkLine(parentMesh, childMesh, resolution, childItem.relationship, colorHex);
13079
+ const line = createAncestryLinkLine(
13080
+ parentMesh,
13081
+ childMesh,
13082
+ resolution,
13083
+ childItem.relationship,
13084
+ colorHex
13085
+ );
12442
13086
  ancestryGroup.add(line);
12443
13087
  ancestryEntry.lines.push(line);
12444
13088
  lastRenderedMesh = childMesh;
12445
13089
  if (childItem.children && childItem.children.length > 0) {
12446
- const lastDescendant = renderCluster(childItem.children, childMesh, childPosition, level + 1, currentAngle);
13090
+ const lastDescendant = renderCluster(
13091
+ childItem.children,
13092
+ childMesh,
13093
+ childPosition,
13094
+ level + 1,
13095
+ currentAngle
13096
+ );
12447
13097
  if (lastDescendant) lastRenderedMesh = lastDescendant;
12448
13098
  }
12449
13099
  });
@@ -12457,9 +13107,13 @@ function XViewScene({
12457
13107
  else looseNodes.push(child);
12458
13108
  });
12459
13109
  }
12460
- sections.sort((a, b) => (a.section_numeric_id || 0) - (b.section_numeric_id || 0));
13110
+ sections.sort(
13111
+ (a, b) => (a.section_numeric_id || 0) - (b.section_numeric_id || 0)
13112
+ );
12461
13113
  let combinedStartNodes = [...looseNodes];
12462
- let session0Index = sections.findIndex((s) => s.section_numeric_id === 0 || s.name === "Sess\xE3o 0");
13114
+ let session0Index = sections.findIndex(
13115
+ (s) => s.section_numeric_id === 0 || s.name === "Sess\xE3o 0"
13116
+ );
12463
13117
  if (session0Index !== -1) {
12464
13118
  const session0 = sections[session0Index];
12465
13119
  if (checkShouldRender(session0) && session0.children) {
@@ -12468,7 +13122,13 @@ function XViewScene({
12468
13122
  sections.splice(session0Index, 1);
12469
13123
  }
12470
13124
  const rootTargetPos2 = targetPositionsCache.get(rootNodeId) || rootNodeMesh.position;
12471
- const lastStartMesh = renderCluster(combinedStartNodes, rootNodeMesh, rootTargetPos2, 1, baseRotation);
13125
+ const lastStartMesh = renderCluster(
13126
+ combinedStartNodes,
13127
+ rootNodeMesh,
13128
+ rootTargetPos2,
13129
+ 1,
13130
+ baseRotation
13131
+ );
12472
13132
  if (lastStartMesh) {
12473
13133
  currentAnchorMesh = lastStartMesh;
12474
13134
  }
@@ -12479,10 +13139,18 @@ function XViewScene({
12479
13139
  const sectionNodes = section.children || [];
12480
13140
  if (sectionNodes.length > 0) {
12481
13141
  const parentAngle = nodeRotationMap.get(String(currentAnchorMesh.userData.id)) ?? baseRotation;
12482
- const lastMeshOfThisSection = renderCluster(sectionNodes, currentAnchorMesh, currentAnchorPosition, 1, parentAngle);
13142
+ const lastMeshOfThisSection = renderCluster(
13143
+ sectionNodes,
13144
+ currentAnchorMesh,
13145
+ currentAnchorPosition,
13146
+ 1,
13147
+ parentAngle
13148
+ );
12483
13149
  if (lastMeshOfThisSection) {
12484
13150
  currentAnchorMesh = lastMeshOfThisSection;
12485
- currentAnchorPosition = targetPositionsCache.get(String(lastMeshOfThisSection.userData.id)) || lastMeshOfThisSection.position;
13151
+ currentAnchorPosition = targetPositionsCache.get(
13152
+ String(lastMeshOfThisSection.userData.id)
13153
+ ) || lastMeshOfThisSection.position;
12486
13154
  }
12487
13155
  }
12488
13156
  }
@@ -12511,7 +13179,10 @@ function XViewScene({
12511
13179
  } else {
12512
13180
  const directChildren = fullTree.children ? fullTree.children.filter((c) => !c.is_section) : [];
12513
13181
  if (directChildren.length > 0) {
12514
- targetSectionForZone = { name: "In\xEDcio", children: directChildren };
13182
+ targetSectionForZone = {
13183
+ name: "In\xEDcio",
13184
+ children: directChildren
13185
+ };
12515
13186
  }
12516
13187
  }
12517
13188
  } else {
@@ -12585,7 +13256,10 @@ function XViewScene({
12585
13256
  stateRef.current.ancestryLinks = stateRef.current.renderedAncestries.flatMap((a) => a.lines);
12586
13257
  focusTargetPosition = center;
12587
13258
  focusTargetRotation = baseRotation;
12588
- const desiredDistance = Math.max(x_view_config.CAMERA_ZOOM_DISTANCE, radius * 2.8);
13259
+ const desiredDistance = Math.max(
13260
+ x_view_config.CAMERA_ZOOM_DISTANCE,
13261
+ radius * 2.8
13262
+ );
12589
13263
  focusZoomFactor = x_view_config.CAMERA_ZOOM_DISTANCE / desiredDistance;
12590
13264
  } else {
12591
13265
  const anchorId = String(currentAnchorMesh.userData.id);
@@ -12600,13 +13274,18 @@ function XViewScene({
12600
13274
  }
12601
13275
  if (!focusTargetPosition && !allowedSectionIds) {
12602
13276
  if (allRenderedNodePositions.length > 0) {
12603
- const boundingBox = new THREE3.Box3().setFromPoints(allRenderedNodePositions);
13277
+ const boundingBox = new THREE3.Box3().setFromPoints(
13278
+ allRenderedNodePositions
13279
+ );
12604
13280
  const center = new THREE3.Vector3();
12605
13281
  boundingBox.getCenter(center);
12606
13282
  const size = new THREE3.Vector3();
12607
13283
  boundingBox.getSize(size);
12608
13284
  const maxDim = Math.max(size.x, size.y, size.z);
12609
- const fitZoom = Math.min(1, x_view_config.CAMERA_ZOOM_DISTANCE / (maxDim * 1.5));
13285
+ const fitZoom = Math.min(
13286
+ 1,
13287
+ x_view_config.CAMERA_ZOOM_DISTANCE / (maxDim * 1.5)
13288
+ );
12610
13289
  const defaultBase = new THREE3.Vector3(-50, 40, 20);
12611
13290
  const rotatedCinematicAngle = defaultBase.clone().applyAxisAngle(new THREE3.Vector3(0, 1, 0), baseRotation);
12612
13291
  tweenToTarget(center, fitZoom, rotatedCinematicAngle);
@@ -12625,210 +13304,280 @@ function XViewScene({
12625
13304
  tweenToTarget(targetPos, focusZoomFactor, rotatedCinematicAngle);
12626
13305
  }
12627
13306
  },
12628
- [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, readingMode.isActive, ancestryMode.isActive]
13307
+ [
13308
+ addOrUpdateNodeMesh,
13309
+ tweenToTarget,
13310
+ buildFullAncestryTree,
13311
+ readingMode.isActive,
13312
+ ancestryMode.isActive
13313
+ ]
12629
13314
  );
12630
- const handleRenderAbstractionTree = useCallback4((ancestryObject, targetNodeId = null) => {
12631
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12632
- if (!ancestryObject || !ancestryObject.abstraction_tree) return;
12633
- const { ancestryGroup, nodeObjects, renderer, renderedAncestries } = stateRef.current;
12634
- const allParentNodes = Object.values(parentDataRef.current).flatMap((f) => f.nodes);
12635
- let fullTree = buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, ancestryDataRef.current);
12636
- if (!fullTree || !fullTree.node) return;
12637
- if (targetNodeId) {
12638
- const pruneTreeToPath = (treeNode, targetId) => {
12639
- var _a2;
12640
- if (!treeNode) return null;
12641
- const currentId = treeNode.is_section ? treeNode.section_id : String((_a2 = treeNode.node) == null ? void 0 : _a2.id);
12642
- if (String(currentId) === String(targetId)) {
12643
- return { ...treeNode, children: [] };
12644
- }
12645
- if (treeNode.children && treeNode.children.length > 0) {
12646
- for (let child of treeNode.children) {
12647
- const prunedChild = pruneTreeToPath(child, targetId);
12648
- if (prunedChild) {
12649
- return { ...treeNode, children: [prunedChild] };
12650
- }
13315
+ const handleRenderAbstractionTree = useCallback4(
13316
+ (ancestryObject, targetNodeId = null) => {
13317
+ setContextMenu(
13318
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13319
+ );
13320
+ if (!ancestryObject || !ancestryObject.abstraction_tree) return;
13321
+ const { ancestryGroup, nodeObjects, renderer, renderedAncestries } = stateRef.current;
13322
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
13323
+ (f) => f.nodes
13324
+ );
13325
+ let fullTree = buildFullAncestryTree(
13326
+ ancestryObject.abstraction_tree,
13327
+ allParentNodes,
13328
+ ancestryDataRef.current
13329
+ );
13330
+ if (!fullTree || !fullTree.node) return;
13331
+ if (targetNodeId) {
13332
+ const pruneTreeToPath = (treeNode, targetId) => {
13333
+ var _a2;
13334
+ if (!treeNode) return null;
13335
+ const currentId = treeNode.is_section ? treeNode.section_id : String((_a2 = treeNode.node) == null ? void 0 : _a2.id);
13336
+ if (String(currentId) === String(targetId)) {
13337
+ return { ...treeNode, children: [] };
12651
13338
  }
12652
- }
12653
- return null;
12654
- };
12655
- const pruned = pruneTreeToPath(fullTree, targetNodeId);
12656
- if (pruned) fullTree = pruned;
12657
- }
12658
- const absId = ancestryObject.ancestry_id + "_abs";
12659
- handleClearAncestryVisuals(absId);
12660
- const colorHex = 9133302;
12661
- const resolution = new THREE3.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
12662
- const ancestryEntry = { id: absId, lines: [], isFullRender: true };
12663
- renderedAncestries.push(ancestryEntry);
12664
- const rootNodeId = String(fullTree.node.id);
12665
- let rootNodeMesh = nodeObjects[rootNodeId];
12666
- let rootTargetPos = rootNodeMesh ? rootNodeMesh.position.clone() : new THREE3.Vector3(0, 0, 0);
12667
- if (!rootNodeMesh) {
12668
- rootNodeMesh = addOrUpdateNodeMesh(fullTree.node, rootTargetPos, true);
12669
- }
12670
- const SPACING_Y = -40;
12671
- const SPACING_X = 55;
12672
- const renderVertical = (treeNode, parentMesh, parentPos, level) => {
12673
- if (!treeNode.children || treeNode.children.length === 0) return;
12674
- const totalSiblings = treeNode.children.length;
12675
- treeNode.children.forEach((childItem, i) => {
12676
- if (!childItem.node) return;
12677
- const childX = parentPos.x + (i - (totalSiblings - 1) / 2) * (SPACING_X / Math.max(1, level * 0.4));
12678
- const childY = parentPos.y + SPACING_Y;
12679
- const childPos = new THREE3.Vector3(childX, childY, parentPos.z);
12680
- const childMesh = addOrUpdateNodeMesh(childItem.node, childPos, true);
12681
- const line = createAncestryLinkLine(parentMesh, childMesh, resolution, {}, colorHex);
12682
- ancestryGroup.add(line);
12683
- ancestryEntry.lines.push(line);
12684
- renderVertical(childItem, childMesh, childPos, level + 1);
12685
- });
12686
- };
12687
- renderVertical(fullTree, rootNodeMesh, rootTargetPos, 1);
12688
- stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
12689
- tweenToTarget(rootTargetPos, 0.7);
12690
- }, [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, handleClearAncestryVisuals]);
12691
- const handleReadModeBranchNav = useCallback4((nodeId, action, direction = "right") => {
12692
- const { ancestry, branchStack } = readingMode;
12693
- if (!ancestry || !ancestry.tree) return;
12694
- const allAncestries = ancestryDataRef.current || [];
12695
- const fullTree = buildFullAncestryTree(ancestry.tree, Object.values(parentDataRef.current).flatMap((f) => f.nodes), allAncestries);
12696
- if (action === "open") {
12697
- let currentPtr = fullTree;
12698
- for (const step of branchStack) {
12699
- const found = findNodePath3(currentPtr, step.nodeId);
12700
- if (found && found.node.parallel_branches) {
12701
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
12702
- if (branch) currentPtr = branch.tree;
12703
- }
12704
- }
12705
- const foundTarget = findNodePath3(currentPtr, nodeId);
12706
- if (foundTarget && foundTarget.node && foundTarget.node.parallel_branches && foundTarget.node.parallel_branches.length > 0) {
12707
- const branchToOpen = foundTarget.node.parallel_branches.find((b) => (b.direction || "right") === direction);
12708
- if (!branchToOpen) return;
12709
- if (branchToOpen && branchToOpen.tree) {
12710
- const parentIndexToSave = stateRef.current.readMode.currentMaxIndex;
12711
- const savedBranchProgress = 0;
12712
- stateRef.current.readMode.currentMaxIndex = savedBranchProgress;
12713
- stateRef.current.maxAncestryRenderIndex = savedBranchProgress;
12714
- const newStack = [...branchStack, {
12715
- nodeId,
12716
- branchId: branchToOpen.id,
12717
- savedMaxIndex: parentIndexToSave,
12718
- entryDirection: direction
12719
- }];
12720
- setReadingMode((prev) => ({ ...prev, branchStack: newStack, initialSectionId: null }));
12721
- const branchAncestryObj = {
12722
- ancestry_id: branchToOpen.id,
12723
- ancestral_node: branchToOpen.tree.node.id,
12724
- name: branchToOpen.name,
12725
- description: branchToOpen.description,
12726
- description_sections: branchToOpen.description_sections,
12727
- tree: branchToOpen.tree,
12728
- _originNodeId: nodeId,
12729
- _branchDirection: direction
12730
- };
12731
- const allowedIds = /* @__PURE__ */ new Set(["preamble", 0, "0"]);
12732
- const branchSections = parseDescriptionSections(branchToOpen.description || "", branchToOpen.description_sections || []);
12733
- for (let i = 0; i <= savedBranchProgress; i++) {
12734
- if (branchSections[i]) {
12735
- if (branchSections[i].id) allowedIds.add(String(branchSections[i].id));
12736
- if (branchSections[i].numericId !== void 0 && branchSections[i].numericId !== null) {
12737
- allowedIds.add(branchSections[i].numericId);
12738
- allowedIds.add(String(branchSections[i].numericId));
13339
+ if (treeNode.children && treeNode.children.length > 0) {
13340
+ for (let child of treeNode.children) {
13341
+ const prunedChild = pruneTreeToPath(child, targetId);
13342
+ if (prunedChild) {
13343
+ return { ...treeNode, children: [prunedChild] };
12739
13344
  }
12740
13345
  }
12741
13346
  }
12742
- const rotation = newStack.reduce((acc, step) => {
12743
- return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
12744
- }, 0);
12745
- const initialFocusId = "preamble";
12746
- handleRenderAncestry(
12747
- branchAncestryObj,
12748
- allowedIds,
12749
- initialFocusId,
12750
- rotation,
12751
- false
12752
- );
12753
- }
13347
+ return null;
13348
+ };
13349
+ const pruned = pruneTreeToPath(fullTree, targetNodeId);
13350
+ if (pruned) fullTree = pruned;
13351
+ }
13352
+ const absId = ancestryObject.ancestry_id + "_abs";
13353
+ handleClearAncestryVisuals(absId);
13354
+ const colorHex = 9133302;
13355
+ const resolution = new THREE3.Vector2(
13356
+ renderer.domElement.clientWidth,
13357
+ renderer.domElement.clientHeight
13358
+ );
13359
+ const ancestryEntry = { id: absId, lines: [], isFullRender: true };
13360
+ renderedAncestries.push(ancestryEntry);
13361
+ const rootNodeId = String(fullTree.node.id);
13362
+ let rootNodeMesh = nodeObjects[rootNodeId];
13363
+ let rootTargetPos = rootNodeMesh ? rootNodeMesh.position.clone() : new THREE3.Vector3(0, 0, 0);
13364
+ if (!rootNodeMesh) {
13365
+ rootNodeMesh = addOrUpdateNodeMesh(fullTree.node, rootTargetPos, true);
12754
13366
  }
12755
- } else if (action === "back") {
12756
- if (branchStack.length === 0) return;
12757
- const newStack = [...branchStack];
12758
- const popped = newStack.pop();
12759
- if (popped && popped.branchId) {
12760
- stateRef.current.readMode.progressMap[popped.branchId] = stateRef.current.readMode.currentMaxIndex;
12761
- }
12762
- const parentSavedIndex = popped.savedMaxIndex !== void 0 ? popped.savedMaxIndex : 0;
12763
- stateRef.current.readMode.currentMaxIndex = parentSavedIndex;
12764
- stateRef.current.maxAncestryRenderIndex = parentSavedIndex;
12765
- let targetTreeToRender = fullTree;
12766
- let targetAncestryInfo = ancestry;
12767
- if (newStack.length > 0) {
12768
- let ptr = fullTree;
12769
- for (const step of newStack) {
12770
- const found = findNodePath3(ptr, step.nodeId);
13367
+ const SPACING_Y = -40;
13368
+ const SPACING_X = 55;
13369
+ const renderVertical = (treeNode, parentMesh, parentPos, level) => {
13370
+ if (!treeNode.children || treeNode.children.length === 0) return;
13371
+ const totalSiblings = treeNode.children.length;
13372
+ treeNode.children.forEach((childItem, i) => {
13373
+ if (!childItem.node) return;
13374
+ const childX = parentPos.x + (i - (totalSiblings - 1) / 2) * (SPACING_X / Math.max(1, level * 0.4));
13375
+ const childY = parentPos.y + SPACING_Y;
13376
+ const childPos = new THREE3.Vector3(childX, childY, parentPos.z);
13377
+ const childMesh = addOrUpdateNodeMesh(childItem.node, childPos, true);
13378
+ const line = createAncestryLinkLine(
13379
+ parentMesh,
13380
+ childMesh,
13381
+ resolution,
13382
+ {},
13383
+ colorHex
13384
+ );
13385
+ ancestryGroup.add(line);
13386
+ ancestryEntry.lines.push(line);
13387
+ renderVertical(childItem, childMesh, childPos, level + 1);
13388
+ });
13389
+ };
13390
+ renderVertical(fullTree, rootNodeMesh, rootTargetPos, 1);
13391
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
13392
+ (a) => a.lines
13393
+ );
13394
+ tweenToTarget(rootTargetPos, 0.7);
13395
+ },
13396
+ [
13397
+ addOrUpdateNodeMesh,
13398
+ tweenToTarget,
13399
+ buildFullAncestryTree,
13400
+ handleClearAncestryVisuals
13401
+ ]
13402
+ );
13403
+ const handleReadModeBranchNav = useCallback4(
13404
+ (nodeId, action, direction = "right") => {
13405
+ const { ancestry, branchStack } = readingMode;
13406
+ if (!ancestry || !ancestry.tree) return;
13407
+ const allAncestries = ancestryDataRef.current || [];
13408
+ const fullTree = buildFullAncestryTree(
13409
+ ancestry.tree,
13410
+ Object.values(parentDataRef.current).flatMap((f) => f.nodes),
13411
+ allAncestries
13412
+ );
13413
+ if (action === "open") {
13414
+ let currentPtr = fullTree;
13415
+ for (const step of branchStack) {
13416
+ const found = findNodePath3(currentPtr, step.nodeId);
12771
13417
  if (found && found.node.parallel_branches) {
12772
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
12773
- if (branch) {
12774
- ptr = branch.tree;
12775
- targetAncestryInfo = { ...branch, ancestry_id: branch.id, ancestral_node: branch.tree.node.id };
13418
+ const branch = found.node.parallel_branches.find(
13419
+ (b) => b.id === step.branchId
13420
+ );
13421
+ if (branch) currentPtr = branch.tree;
13422
+ }
13423
+ }
13424
+ const foundTarget = findNodePath3(currentPtr, nodeId);
13425
+ if (foundTarget && foundTarget.node && foundTarget.node.parallel_branches && foundTarget.node.parallel_branches.length > 0) {
13426
+ const branchToOpen = foundTarget.node.parallel_branches.find(
13427
+ (b) => (b.direction || "right") === direction
13428
+ );
13429
+ if (!branchToOpen) return;
13430
+ if (branchToOpen && branchToOpen.tree) {
13431
+ const parentIndexToSave = stateRef.current.readMode.currentMaxIndex;
13432
+ const savedBranchProgress = 0;
13433
+ stateRef.current.readMode.currentMaxIndex = savedBranchProgress;
13434
+ stateRef.current.maxAncestryRenderIndex = savedBranchProgress;
13435
+ const newStack = [
13436
+ ...branchStack,
13437
+ {
13438
+ nodeId,
13439
+ branchId: branchToOpen.id,
13440
+ savedMaxIndex: parentIndexToSave,
13441
+ entryDirection: direction
13442
+ }
13443
+ ];
13444
+ setReadingMode((prev) => ({
13445
+ ...prev,
13446
+ branchStack: newStack,
13447
+ initialSectionId: null
13448
+ }));
13449
+ const branchAncestryObj = {
13450
+ ancestry_id: branchToOpen.id,
13451
+ ancestral_node: branchToOpen.tree.node.id,
13452
+ name: branchToOpen.name,
13453
+ description: branchToOpen.description,
13454
+ description_sections: branchToOpen.description_sections,
13455
+ tree: branchToOpen.tree,
13456
+ _originNodeId: nodeId,
13457
+ _branchDirection: direction
13458
+ };
13459
+ const allowedIds = /* @__PURE__ */ new Set(["preamble", 0, "0"]);
13460
+ const branchSections = parseDescriptionSections(
13461
+ branchToOpen.description || "",
13462
+ branchToOpen.description_sections || []
13463
+ );
13464
+ for (let i = 0; i <= savedBranchProgress; i++) {
13465
+ if (branchSections[i]) {
13466
+ if (branchSections[i].id)
13467
+ allowedIds.add(String(branchSections[i].id));
13468
+ if (branchSections[i].numericId !== void 0 && branchSections[i].numericId !== null) {
13469
+ allowedIds.add(branchSections[i].numericId);
13470
+ allowedIds.add(String(branchSections[i].numericId));
13471
+ }
13472
+ }
12776
13473
  }
13474
+ const rotation = newStack.reduce((acc, step) => {
13475
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13476
+ }, 0);
13477
+ const initialFocusId = "preamble";
13478
+ handleRenderAncestry(
13479
+ branchAncestryObj,
13480
+ allowedIds,
13481
+ initialFocusId,
13482
+ rotation,
13483
+ false
13484
+ );
12777
13485
  }
12778
13486
  }
12779
- targetTreeToRender = ptr;
12780
- }
12781
- const activeParentStackItem = newStack.length > 0 ? newStack[newStack.length - 1] : null;
12782
- const parentAncestryObj = {
12783
- ...targetAncestryInfo,
12784
- tree: targetTreeToRender,
12785
- _originNodeId: activeParentStackItem ? activeParentStackItem.nodeId : null,
12786
- _branchDirection: activeParentStackItem ? activeParentStackItem.entryDirection : null
12787
- };
12788
- const descriptionText = targetAncestryInfo.description || "";
12789
- const savedSections = targetAncestryInfo.description_sections || [];
12790
- const sections = parseDescriptionSections(descriptionText, savedSections);
12791
- let focusTargetId = null;
12792
- if (popped && popped.nodeId) {
12793
- const mentionTag = `[[MENTION:node:${popped.nodeId}]]`;
12794
- for (let i = 0; i < sections.length; i++) {
12795
- if (sections[i].content && sections[i].content.includes(mentionTag)) {
12796
- const section = sections[i];
12797
- focusTargetId = section.numericId !== void 0 ? section.numericId : section.id;
12798
- break;
13487
+ } else if (action === "back") {
13488
+ if (branchStack.length === 0) return;
13489
+ const newStack = [...branchStack];
13490
+ const popped = newStack.pop();
13491
+ if (popped && popped.branchId) {
13492
+ stateRef.current.readMode.progressMap[popped.branchId] = stateRef.current.readMode.currentMaxIndex;
13493
+ }
13494
+ const parentSavedIndex = popped.savedMaxIndex !== void 0 ? popped.savedMaxIndex : 0;
13495
+ stateRef.current.readMode.currentMaxIndex = parentSavedIndex;
13496
+ stateRef.current.maxAncestryRenderIndex = parentSavedIndex;
13497
+ let targetTreeToRender = fullTree;
13498
+ let targetAncestryInfo = ancestry;
13499
+ if (newStack.length > 0) {
13500
+ let ptr = fullTree;
13501
+ for (const step of newStack) {
13502
+ const found = findNodePath3(ptr, step.nodeId);
13503
+ if (found && found.node.parallel_branches) {
13504
+ const branch = found.node.parallel_branches.find(
13505
+ (b) => b.id === step.branchId
13506
+ );
13507
+ if (branch) {
13508
+ ptr = branch.tree;
13509
+ targetAncestryInfo = {
13510
+ ...branch,
13511
+ ancestry_id: branch.id,
13512
+ ancestral_node: branch.tree.node.id
13513
+ };
13514
+ }
13515
+ }
13516
+ }
13517
+ targetTreeToRender = ptr;
13518
+ }
13519
+ const activeParentStackItem = newStack.length > 0 ? newStack[newStack.length - 1] : null;
13520
+ const parentAncestryObj = {
13521
+ ...targetAncestryInfo,
13522
+ tree: targetTreeToRender,
13523
+ _originNodeId: activeParentStackItem ? activeParentStackItem.nodeId : null,
13524
+ _branchDirection: activeParentStackItem ? activeParentStackItem.entryDirection : null
13525
+ };
13526
+ const descriptionText = targetAncestryInfo.description || "";
13527
+ const savedSections = targetAncestryInfo.description_sections || [];
13528
+ const sections = parseDescriptionSections(
13529
+ descriptionText,
13530
+ savedSections
13531
+ );
13532
+ let focusTargetId = null;
13533
+ if (popped && popped.nodeId) {
13534
+ const mentionTag = `[[MENTION:node:${popped.nodeId}]]`;
13535
+ for (let i = 0; i < sections.length; i++) {
13536
+ if (sections[i].content && sections[i].content.includes(mentionTag)) {
13537
+ const section = sections[i];
13538
+ focusTargetId = section.numericId !== void 0 ? section.numericId : section.id;
13539
+ break;
13540
+ }
12799
13541
  }
12800
13542
  }
12801
- }
12802
- const allowedIds = /* @__PURE__ */ new Set();
12803
- allowedIds.add("preamble");
12804
- allowedIds.add(0);
12805
- allowedIds.add("0");
12806
- for (let i = 0; i <= parentSavedIndex; i++) {
12807
- if (sections[i]) {
12808
- if (sections[i].id) allowedIds.add(String(sections[i].id));
12809
- if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
12810
- allowedIds.add(sections[i].numericId);
12811
- allowedIds.add(String(sections[i].numericId));
13543
+ const allowedIds = /* @__PURE__ */ new Set();
13544
+ allowedIds.add("preamble");
13545
+ allowedIds.add(0);
13546
+ allowedIds.add("0");
13547
+ for (let i = 0; i <= parentSavedIndex; i++) {
13548
+ if (sections[i]) {
13549
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13550
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13551
+ allowedIds.add(sections[i].numericId);
13552
+ allowedIds.add(String(sections[i].numericId));
13553
+ }
12812
13554
  }
12813
13555
  }
12814
- }
12815
- const rotation = newStack.reduce((acc, step) => {
12816
- return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
12817
- }, 0);
12818
- handleRenderAncestry(parentAncestryObj, allowedIds, focusTargetId, rotation, false);
12819
- if (popped && popped.nodeId) {
12820
- const nodeMesh = stateRef.current.nodeObjects[String(popped.nodeId)];
12821
- if (nodeMesh) {
12822
- tweenToTarget(nodeMesh);
13556
+ const rotation = newStack.reduce((acc, step) => {
13557
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13558
+ }, 0);
13559
+ handleRenderAncestry(
13560
+ parentAncestryObj,
13561
+ allowedIds,
13562
+ focusTargetId,
13563
+ rotation,
13564
+ false
13565
+ );
13566
+ if (popped && popped.nodeId) {
13567
+ const nodeMesh = stateRef.current.nodeObjects[String(popped.nodeId)];
13568
+ if (nodeMesh) {
13569
+ tweenToTarget(nodeMesh);
13570
+ }
12823
13571
  }
13572
+ setReadingMode((prev) => ({
13573
+ ...prev,
13574
+ branchStack: newStack,
13575
+ initialSectionId: focusTargetId
13576
+ }));
12824
13577
  }
12825
- setReadingMode((prev) => ({
12826
- ...prev,
12827
- branchStack: newStack,
12828
- initialSectionId: focusTargetId
12829
- }));
12830
- }
12831
- }, [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]);
13578
+ },
13579
+ [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]
13580
+ );
12832
13581
  const handleReadModeHighlight = useCallback4((nodeId) => {
12833
13582
  if (stateRef.current.highlightedNodeId !== nodeId) {
12834
13583
  stateRef.current.highlightedNodeId = nodeId;
@@ -12836,7 +13585,8 @@ function XViewScene({
12836
13585
  setHighlightedNodeId(nodeId);
12837
13586
  }, []);
12838
13587
  const activeNodeBranches = useMemo12(() => {
12839
- if (!highlightedNodeId || !readingMode.ancestry || !readingMode.ancestry.tree) return null;
13588
+ if (!highlightedNodeId || !readingMode.ancestry || !readingMode.ancestry.tree)
13589
+ return null;
12840
13590
  const fullTree = buildFullAncestryTree(
12841
13591
  readingMode.ancestry.tree,
12842
13592
  Object.values(parentDataRef.current).flatMap((f) => f.nodes),
@@ -12871,7 +13621,13 @@ function XViewScene({
12871
13621
  };
12872
13622
  }
12873
13623
  return null;
12874
- }, [highlightedNodeId, readingMode.ancestry, buildFullAncestryTree, readingMode.branchStack, ancestryDataRef.current]);
13624
+ }, [
13625
+ highlightedNodeId,
13626
+ readingMode.ancestry,
13627
+ buildFullAncestryTree,
13628
+ readingMode.branchStack,
13629
+ ancestryDataRef.current
13630
+ ]);
12875
13631
  const backNavigationInfo = useMemo12(() => {
12876
13632
  const { branchStack } = readingMode;
12877
13633
  if (!branchStack || branchStack.length === 0) return null;
@@ -12907,7 +13663,9 @@ function XViewScene({
12907
13663
  for (const step of branchStack) {
12908
13664
  const found = findNodePath3(currentPtr, step.nodeId);
12909
13665
  if (found && found.node.parallel_branches) {
12910
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
13666
+ const branch = found.node.parallel_branches.find(
13667
+ (b) => b.id === step.branchId
13668
+ );
12911
13669
  if (branch) {
12912
13670
  currentPtr = branch.tree;
12913
13671
  currentMeta = branch;
@@ -12928,17 +13686,26 @@ function XViewScene({
12928
13686
  if (!readingMode.isActive || !readingMode.ancestry || !readingMode.ancestry.abstraction_tree) {
12929
13687
  return null;
12930
13688
  }
12931
- const allNodes = Object.values(parentDataRef.current || {}).flatMap((f) => f.nodes || []);
13689
+ const allNodes = Object.values(parentDataRef.current || {}).flatMap(
13690
+ (f) => f.nodes || []
13691
+ );
12932
13692
  const allAncestries = ancestryDataRef.current || [];
12933
13693
  return buildFullAncestryTree(
12934
13694
  readingMode.ancestry.abstraction_tree,
12935
13695
  allNodes,
12936
13696
  allAncestries
12937
13697
  );
12938
- }, [readingMode.isActive, readingMode.ancestry, buildFullAncestryTree, sceneVersion]);
13698
+ }, [
13699
+ readingMode.isActive,
13700
+ readingMode.ancestry,
13701
+ buildFullAncestryTree,
13702
+ sceneVersion
13703
+ ]);
12939
13704
  const handleStartReadingAncestry = useCallback4(
12940
- async (ancestryObject) => {
12941
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
13705
+ async (ancestryObject, renderMode = "full") => {
13706
+ setContextMenu(
13707
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13708
+ );
12942
13709
  if (!ancestryObject || !ancestryObject.tree) {
12943
13710
  console.warn("Ancestralidade inv\xE1lida para leitura.");
12944
13711
  return;
@@ -12951,14 +13718,20 @@ function XViewScene({
12951
13718
  const hasDescription = ancestryObject.description && ancestryObject.description.trim() !== "";
12952
13719
  const hasMainTreeNodes = ancestryObject.tree.children && ancestryObject.tree.children.length > 0;
12953
13720
  const hasAbstractionNodes = ancestryObject.abstraction_tree && ancestryObject.abstraction_tree.children && ancestryObject.abstraction_tree.children.length > 0;
12954
- const shouldAutoRenderAbstraction = !hasDescription && !hasMainTreeNodes && hasAbstractionNodes;
13721
+ const isFull = renderMode === "full";
13722
+ const isAncestryOnly = renderMode === "ancestry_only";
13723
+ const isAbstractionOnly = renderMode === "abstraction_only";
13724
+ let shouldRenderAbstraction = isAbstractionOnly;
13725
+ if (isFull) {
13726
+ shouldRenderAbstraction = !hasDescription && !hasMainTreeNodes && hasAbstractionNodes;
13727
+ }
12955
13728
  setReadingMode({
12956
- isActive: true,
13729
+ isActive: isFull ? hasDescription : false,
12957
13730
  ancestry: ancestryObject,
12958
13731
  branchStack: [],
12959
- autoAbstraction: shouldAutoRenderAbstraction
13732
+ autoAbstraction: shouldRenderAbstraction
12960
13733
  });
12961
- if (shouldAutoRenderAbstraction) {
13734
+ if (shouldRenderAbstraction) {
12962
13735
  handleRenderAbstractionTree(ancestryObject, null);
12963
13736
  } else {
12964
13737
  const initialSections = /* @__PURE__ */ new Set(["preamble", 0, "0"]);
@@ -12971,148 +13744,190 @@ function XViewScene({
12971
13744
  },
12972
13745
  [handleRenderAncestry, handleRenderAbstractionTree]
12973
13746
  );
12974
- const handleReadModeSectionChange = useCallback4((activeSectionId) => {
12975
- const { ancestry, branchStack } = readingMode;
12976
- if (!ancestry || !readingMode.isActive) return;
12977
- let targetObj = ancestry;
12978
- let targetTree = ancestry.tree;
12979
- if (branchStack.length > 0) {
12980
- const allNodes = Object.values(parentDataRef.current).flatMap((f) => f.nodes);
12981
- const fullTree = buildFullAncestryTree(
12982
- ancestry.tree,
12983
- allNodes,
12984
- ancestryDataRef.current
12985
- );
12986
- let currentPtr = fullTree;
12987
- for (const step of branchStack) {
12988
- const found = findNodePath3(currentPtr, step.nodeId);
12989
- if (found && found.node && found.node.parallel_branches) {
12990
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
12991
- if (branch) {
12992
- targetObj = branch;
12993
- targetTree = branch.tree;
12994
- currentPtr = branch.tree;
13747
+ const handleReadModeSectionChange = useCallback4(
13748
+ (activeSectionId) => {
13749
+ const { ancestry, branchStack } = readingMode;
13750
+ if (!ancestry || !readingMode.isActive) return;
13751
+ let targetObj = ancestry;
13752
+ let targetTree = ancestry.tree;
13753
+ if (branchStack.length > 0) {
13754
+ const allNodes = Object.values(parentDataRef.current).flatMap(
13755
+ (f) => f.nodes
13756
+ );
13757
+ const fullTree = buildFullAncestryTree(
13758
+ ancestry.tree,
13759
+ allNodes,
13760
+ ancestryDataRef.current
13761
+ );
13762
+ let currentPtr = fullTree;
13763
+ for (const step of branchStack) {
13764
+ const found = findNodePath3(currentPtr, step.nodeId);
13765
+ if (found && found.node && found.node.parallel_branches) {
13766
+ const branch = found.node.parallel_branches.find(
13767
+ (b) => b.id === step.branchId
13768
+ );
13769
+ if (branch) {
13770
+ targetObj = branch;
13771
+ targetTree = branch.tree;
13772
+ currentPtr = branch.tree;
13773
+ }
12995
13774
  }
12996
13775
  }
12997
13776
  }
12998
- }
12999
- const descriptionText = targetObj.description || "";
13000
- const savedSections = targetObj.description_sections || [];
13001
- const sections = parseDescriptionSections(descriptionText, savedSections);
13002
- let activeIndex = sections.findIndex((s) => String(s.id) === String(activeSectionId));
13003
- if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13004
- activeIndex = sections.findIndex((s) => s.numericId === parseInt(activeSectionId));
13005
- }
13006
- if (activeIndex === -1) activeIndex = 0;
13007
- stateRef.current.readMode.currentMaxIndex = activeIndex;
13008
- stateRef.current.maxAncestryRenderIndex = activeIndex;
13009
- const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13010
- const allowedIds = /* @__PURE__ */ new Set();
13011
- allowedIds.add("preamble");
13012
- allowedIds.add(0);
13013
- allowedIds.add("0");
13014
- for (let i = 0; i <= renderLimitIndex; i++) {
13015
- if (sections[i]) {
13016
- if (sections[i].id) allowedIds.add(String(sections[i].id));
13017
- if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13018
- allowedIds.add(sections[i].numericId);
13019
- allowedIds.add(String(sections[i].numericId));
13777
+ const descriptionText = targetObj.description || "";
13778
+ const savedSections = targetObj.description_sections || [];
13779
+ const sections = parseDescriptionSections(descriptionText, savedSections);
13780
+ let activeIndex = sections.findIndex(
13781
+ (s) => String(s.id) === String(activeSectionId)
13782
+ );
13783
+ if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13784
+ activeIndex = sections.findIndex(
13785
+ (s) => s.numericId === parseInt(activeSectionId)
13786
+ );
13787
+ }
13788
+ if (activeIndex === -1) activeIndex = 0;
13789
+ stateRef.current.readMode.currentMaxIndex = activeIndex;
13790
+ stateRef.current.maxAncestryRenderIndex = activeIndex;
13791
+ const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13792
+ const allowedIds = /* @__PURE__ */ new Set();
13793
+ allowedIds.add("preamble");
13794
+ allowedIds.add(0);
13795
+ allowedIds.add("0");
13796
+ for (let i = 0; i <= renderLimitIndex; i++) {
13797
+ if (sections[i]) {
13798
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13799
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13800
+ allowedIds.add(sections[i].numericId);
13801
+ allowedIds.add(String(sections[i].numericId));
13802
+ }
13020
13803
  }
13021
13804
  }
13022
- }
13023
- const activeSection = sections[activeIndex];
13024
- let focusTargetId = activeSectionId;
13025
- if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13026
- focusTargetId = activeSection.numericId;
13027
- }
13028
- const currentStackItem = branchStack.length > 0 ? branchStack[branchStack.length - 1] : null;
13029
- const renderPayload = {
13030
- ...targetObj,
13031
- ancestry_id: targetObj.ancestry_id || targetObj.id,
13032
- ancestral_node: targetTree.node ? targetTree.node.id : targetTree.node_id,
13033
- tree: targetTree,
13034
- _originNodeId: currentStackItem ? currentStackItem.nodeId : null,
13035
- _branchDirection: currentStackItem ? currentStackItem.entryDirection : null,
13036
- _forceUpdate: true
13037
- };
13038
- const rotation = branchStack.reduce((acc, step) => {
13039
- return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13040
- }, 0);
13041
- handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13042
- }, [readingMode, handleRenderAncestry, buildFullAncestryTree, ancestryDataRef.current]);
13805
+ const activeSection = sections[activeIndex];
13806
+ let focusTargetId = activeSectionId;
13807
+ if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13808
+ focusTargetId = activeSection.numericId;
13809
+ }
13810
+ const currentStackItem = branchStack.length > 0 ? branchStack[branchStack.length - 1] : null;
13811
+ const renderPayload = {
13812
+ ...targetObj,
13813
+ ancestry_id: targetObj.ancestry_id || targetObj.id,
13814
+ ancestral_node: targetTree.node ? targetTree.node.id : targetTree.node_id,
13815
+ tree: targetTree,
13816
+ _originNodeId: currentStackItem ? currentStackItem.nodeId : null,
13817
+ _branchDirection: currentStackItem ? currentStackItem.entryDirection : null,
13818
+ _forceUpdate: true
13819
+ };
13820
+ const rotation = branchStack.reduce((acc, step) => {
13821
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13822
+ }, 0);
13823
+ handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13824
+ },
13825
+ [
13826
+ readingMode,
13827
+ handleRenderAncestry,
13828
+ buildFullAncestryTree,
13829
+ ancestryDataRef.current
13830
+ ]
13831
+ );
13043
13832
  const handleCloseReadMode = useCallback4(() => {
13044
13833
  setReadingMode({ isActive: false, ancestry: null, branchStack: [] });
13045
13834
  }, []);
13046
- const handleAncestrySectionChange = useCallback4((activeSectionId, ancestryOverride = null, rotation = 0) => {
13047
- var _a2, _b2;
13048
- const currentMode = stateRef.current.ancestry;
13049
- let targetObj = ancestryOverride;
13050
- if (!targetObj) {
13051
- const currentAncestryId = currentMode.currentAncestryId;
13052
- const ancestryObj = (ancestryDataRef.current || []).find((a) => String(a.ancestry_id) === String(currentAncestryId));
13053
- targetObj = ancestryObj || (currentMode.isActive ? {
13054
- ...currentMode,
13055
- ancestry_id: "temp_creating",
13056
- ancestral_node: (_b2 = (_a2 = currentMode.tree) == null ? void 0 : _a2.node) == null ? void 0 : _b2.id
13057
- } : null);
13058
- }
13059
- if (!targetObj) return;
13060
- const targetId = targetObj.ancestry_id || targetObj.id;
13061
- if (stateRef.current.lastRenderedAncestryId !== targetId) {
13062
- stateRef.current.maxAncestryRenderIndex = 0;
13063
- stateRef.current.lastRenderedAncestryId = targetId;
13064
- }
13065
- const descriptionText = (ancestryOverride ? targetObj.description : currentMode.ancestryDescription) || targetObj.description || "";
13066
- const savedSections = (ancestryOverride ? targetObj.description_sections : currentMode.ancestryDescriptionSections) || targetObj.description_sections || [];
13067
- const sections = parseDescriptionSections(descriptionText, savedSections);
13068
- let activeIndex = sections.findIndex((s) => String(s.id) === String(activeSectionId));
13069
- if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13070
- activeIndex = sections.findIndex((s) => s.numericId === parseInt(activeSectionId));
13071
- }
13072
- if (activeIndex === -1) activeIndex = 0;
13073
- if (stateRef.current.maxAncestryRenderIndex === void 0) stateRef.current.maxAncestryRenderIndex = 0;
13074
- stateRef.current.maxAncestryRenderIndex = activeIndex;
13075
- const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13076
- const allowedIds = /* @__PURE__ */ new Set();
13077
- allowedIds.add("preamble");
13078
- allowedIds.add(0);
13079
- allowedIds.add("0");
13080
- for (let i = 0; i <= renderLimitIndex; i++) {
13081
- if (sections[i]) {
13082
- if (sections[i].id) allowedIds.add(String(sections[i].id));
13083
- if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13084
- allowedIds.add(sections[i].numericId);
13085
- allowedIds.add(String(sections[i].numericId));
13835
+ const handleAncestrySectionChange = useCallback4(
13836
+ (activeSectionId, ancestryOverride = null, rotation = 0) => {
13837
+ var _a2, _b2;
13838
+ const currentMode = stateRef.current.ancestry;
13839
+ let targetObj = ancestryOverride;
13840
+ if (!targetObj) {
13841
+ const currentAncestryId = currentMode.currentAncestryId;
13842
+ const ancestryObj = (ancestryDataRef.current || []).find(
13843
+ (a) => String(a.ancestry_id) === String(currentAncestryId)
13844
+ );
13845
+ targetObj = ancestryObj || (currentMode.isActive ? {
13846
+ ...currentMode,
13847
+ ancestry_id: "temp_creating",
13848
+ ancestral_node: (_b2 = (_a2 = currentMode.tree) == null ? void 0 : _a2.node) == null ? void 0 : _b2.id
13849
+ } : null);
13850
+ }
13851
+ if (!targetObj) return;
13852
+ const targetId = targetObj.ancestry_id || targetObj.id;
13853
+ if (stateRef.current.lastRenderedAncestryId !== targetId) {
13854
+ stateRef.current.maxAncestryRenderIndex = 0;
13855
+ stateRef.current.lastRenderedAncestryId = targetId;
13856
+ }
13857
+ const descriptionText = (ancestryOverride ? targetObj.description : currentMode.ancestryDescription) || targetObj.description || "";
13858
+ const savedSections = (ancestryOverride ? targetObj.description_sections : currentMode.ancestryDescriptionSections) || targetObj.description_sections || [];
13859
+ const sections = parseDescriptionSections(descriptionText, savedSections);
13860
+ let activeIndex = sections.findIndex(
13861
+ (s) => String(s.id) === String(activeSectionId)
13862
+ );
13863
+ if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13864
+ activeIndex = sections.findIndex(
13865
+ (s) => s.numericId === parseInt(activeSectionId)
13866
+ );
13867
+ }
13868
+ if (activeIndex === -1) activeIndex = 0;
13869
+ if (stateRef.current.maxAncestryRenderIndex === void 0)
13870
+ stateRef.current.maxAncestryRenderIndex = 0;
13871
+ stateRef.current.maxAncestryRenderIndex = activeIndex;
13872
+ const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13873
+ const allowedIds = /* @__PURE__ */ new Set();
13874
+ allowedIds.add("preamble");
13875
+ allowedIds.add(0);
13876
+ allowedIds.add("0");
13877
+ for (let i = 0; i <= renderLimitIndex; i++) {
13878
+ if (sections[i]) {
13879
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13880
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13881
+ allowedIds.add(sections[i].numericId);
13882
+ allowedIds.add(String(sections[i].numericId));
13883
+ }
13086
13884
  }
13087
13885
  }
13088
- }
13089
- const activeSection = sections[activeIndex];
13090
- let focusTargetId = activeSectionId;
13091
- if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13092
- focusTargetId = activeSection.numericId;
13093
- }
13094
- const treeToRender = ancestryOverride ? ancestryOverride.tree : currentMode.isActive && currentMode.tree ? currentMode.tree : targetObj.tree;
13095
- const renderPayload = { ...targetObj, tree: treeToRender };
13096
- handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13097
- }, [handleRenderAncestry]);
13886
+ const activeSection = sections[activeIndex];
13887
+ let focusTargetId = activeSectionId;
13888
+ if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13889
+ focusTargetId = activeSection.numericId;
13890
+ }
13891
+ const treeToRender = ancestryOverride ? ancestryOverride.tree : currentMode.isActive && currentMode.tree ? currentMode.tree : targetObj.tree;
13892
+ const renderPayload = { ...targetObj, tree: treeToRender };
13893
+ handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13894
+ },
13895
+ [handleRenderAncestry]
13896
+ );
13098
13897
  const handleEditAncestry = useCallback4(
13099
13898
  async (ancestryObject) => {
13100
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
13899
+ setContextMenu(
13900
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13901
+ );
13101
13902
  if (!ancestryObject || !ancestryObject.tree) {
13102
- alert("N\xE3o foi poss\xEDvel carregar os dados desta ancestralidade para edi\xE7\xE3o.");
13903
+ alert(
13904
+ "N\xE3o foi poss\xEDvel carregar os dados desta ancestralidade para edi\xE7\xE3o."
13905
+ );
13103
13906
  return;
13104
13907
  }
13105
13908
  stateRef.current.maxAncestryRenderIndex = 0;
13106
13909
  const initialSections = /* @__PURE__ */ new Set(["preamble", 0]);
13107
13910
  await handleRenderAncestry(ancestryObject, initialSections);
13108
- const allParentNodes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes);
13911
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
13912
+ (fileData) => fileData.nodes
13913
+ );
13109
13914
  const allAncestries = ancestryDataRef.current || [];
13110
- const fullTree = buildFullAncestryTree(ancestryObject.tree, allParentNodes, allAncestries);
13915
+ const fullTree = buildFullAncestryTree(
13916
+ ancestryObject.tree,
13917
+ allParentNodes,
13918
+ allAncestries
13919
+ );
13111
13920
  if (!fullTree) {
13112
- alert("Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando.");
13921
+ alert(
13922
+ "Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando."
13923
+ );
13113
13924
  return;
13114
13925
  }
13115
- const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, allAncestries) : { node: fullTree.node, children: [] };
13926
+ const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(
13927
+ ancestryObject.abstraction_tree,
13928
+ allParentNodes,
13929
+ allAncestries
13930
+ ) : { node: fullTree.node, children: [] };
13116
13931
  setAncestryMode({
13117
13932
  isActive: true,
13118
13933
  ...ancestryObject,
@@ -13164,7 +13979,9 @@ function XViewScene({
13164
13979
  const treeToUse = treeOverride || ancestryMode.tree;
13165
13980
  const { isEditMode, currentAncestryId } = ancestryMode;
13166
13981
  if (!treeToUse || !treeToUse.node) {
13167
- alert("Erro: A estrutura da ancestralidade \xE9 inv\xE1lida (Node raiz ausente).");
13982
+ alert(
13983
+ "Erro: A estrutura da ancestralidade \xE9 inv\xE1lida (Node raiz ausente)."
13984
+ );
13168
13985
  return;
13169
13986
  }
13170
13987
  if (!save_view_data || !stateRef.current.nodeIdToParentFileMap) return;
@@ -13197,14 +14014,18 @@ function XViewScene({
13197
14014
  };
13198
14015
  };
13199
14016
  const treeWithIds = convertTreeToIds(treeToUse);
13200
- const abstractionTreeWithIds = convertTreeToIds(ancestryMode.abstraction_tree);
14017
+ const abstractionTreeWithIds = convertTreeToIds(
14018
+ ancestryMode.abstraction_tree
14019
+ );
13201
14020
  if (!treeWithIds) {
13202
14021
  alert("Erro ao processar a \xE1rvore da ancestralidade.");
13203
14022
  return;
13204
14023
  }
13205
14024
  let originalAncestryObj = null;
13206
14025
  if (isEditMode && currentAncestryId) {
13207
- originalAncestryObj = (ancestryDataRef.current || []).find((anc) => String(anc.ancestry_id) === String(currentAncestryId));
14026
+ originalAncestryObj = (ancestryDataRef.current || []).find(
14027
+ (anc) => String(anc.ancestry_id) === String(currentAncestryId)
14028
+ );
13208
14029
  }
13209
14030
  const ancestryObjectToSave = {
13210
14031
  ancestry_id: isEditMode && currentAncestryId ? currentAncestryId : `${short2.generate()}`,
@@ -13287,14 +14108,20 @@ function XViewScene({
13287
14108
  try {
13288
14109
  if (isExternalSave) {
13289
14110
  try {
13290
- const remoteResponse = await get_ancestry_file(targetFileIdForCache, targetOwnerIdForCache);
14111
+ const remoteResponse = await get_ancestry_file(
14112
+ targetFileIdForCache,
14113
+ targetOwnerIdForCache
14114
+ );
13291
14115
  if (remoteResponse.success && Array.isArray(remoteResponse.data)) {
13292
14116
  masterAncestryList = remoteResponse.data;
13293
14117
  } else {
13294
14118
  masterAncestryList = [];
13295
14119
  }
13296
14120
  } catch (fetchErr) {
13297
- console.warn("Arquivo de destino n\xE3o existe ou erro ao buscar, criando novo:", fetchErr);
14121
+ console.warn(
14122
+ "Arquivo de destino n\xE3o existe ou erro ao buscar, criando novo:",
14123
+ fetchErr
14124
+ );
13298
14125
  masterAncestryList = [];
13299
14126
  }
13300
14127
  } else {
@@ -13314,7 +14141,9 @@ function XViewScene({
13314
14141
  const result = await save_view_data(finalSaveUrl, masterAncestryList);
13315
14142
  if (result.success) {
13316
14143
  const localList = [...ancestryDataRef.current || []];
13317
- const localIndex = localList.findIndex((a) => String(a.ancestry_id) === String(ancestryObjectToSave.ancestry_id));
14144
+ const localIndex = localList.findIndex(
14145
+ (a) => String(a.ancestry_id) === String(ancestryObjectToSave.ancestry_id)
14146
+ );
13318
14147
  if (localIndex !== -1) {
13319
14148
  localList[localIndex] = ancestryObjectToSave;
13320
14149
  } else {
@@ -13342,11 +14171,29 @@ function XViewScene({
13342
14171
  return;
13343
14172
  }
13344
14173
  if (!keepOpen) {
13345
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
14174
+ setAncestryMode({
14175
+ isActive: false,
14176
+ tree: null,
14177
+ selectedParentId: null,
14178
+ isEditMode: false,
14179
+ currentAncestryId: null,
14180
+ ancestryName: "",
14181
+ ancestryDescription: "",
14182
+ ancestryDescriptionSections: [],
14183
+ isAddingNodes: false
14184
+ });
13346
14185
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13347
14186
  }
13348
14187
  },
13349
- [ancestryMode, ancestry_save_url, handleRenderAncestry, save_view_data, sceneConfigId, ownerId, get_ancestry_file]
14188
+ [
14189
+ ancestryMode,
14190
+ ancestry_save_url,
14191
+ handleRenderAncestry,
14192
+ save_view_data,
14193
+ sceneConfigId,
14194
+ ownerId,
14195
+ get_ancestry_file
14196
+ ]
13350
14197
  );
13351
14198
  const handleOpenAncestryRelEditor = (path, currentData) => {
13352
14199
  setEditingAncestryRel({ visible: true, data: currentData, path });
@@ -13376,7 +14223,9 @@ function XViewScene({
13376
14223
  if (ancestryToDelete && delete_file_action) {
13377
14224
  const urls = extractFileUrlsFromProperties(ancestryToDelete);
13378
14225
  if (urls.length > 0) {
13379
- Promise.all(urls.map((url) => delete_file_action(url))).catch((err) => console.error("Erro ao deletar arquivos da ancestralidade:", err));
14226
+ Promise.all(urls.map((url) => delete_file_action(url))).catch(
14227
+ (err) => console.error("Erro ao deletar arquivos da ancestralidade:", err)
14228
+ );
13380
14229
  }
13381
14230
  }
13382
14231
  if (!ancestryToDelete) {
@@ -13386,7 +14235,9 @@ function XViewScene({
13386
14235
  const sourceFileId = ancestryToDelete._source_file_id;
13387
14236
  const sourceOwnerId = ancestryToDelete._source_owner_id;
13388
14237
  if (!sourceFileId || !sourceOwnerId) {
13389
- alert("N\xE3o foi poss\xEDvel identificar o arquivo de origem desta ancestralidade.");
14238
+ alert(
14239
+ "N\xE3o foi poss\xEDvel identificar o arquivo de origem desta ancestralidade."
14240
+ );
13390
14241
  return;
13391
14242
  }
13392
14243
  const finalSaveUrl = `x_view_ancestry/${sourceOwnerId}/${sourceFileId}`;
@@ -13394,13 +14245,18 @@ function XViewScene({
13394
14245
  (anc) => anc._source_file_id === sourceFileId && String(anc.ancestry_id) !== String(ancestryIdToDelete)
13395
14246
  );
13396
14247
  try {
13397
- const result = await save_view_data(finalSaveUrl, updatedAncestriesForFile);
14248
+ const result = await save_view_data(
14249
+ finalSaveUrl,
14250
+ updatedAncestriesForFile
14251
+ );
13398
14252
  if (result.success) {
13399
14253
  ancestryDataRef.current = (ancestryDataRef.current || []).filter(
13400
14254
  (ancestry) => String(ancestry.ancestry_id) !== String(ancestryIdToDelete)
13401
14255
  );
13402
14256
  const { renderedAncestries, ancestryGroup } = stateRef.current;
13403
- const renderIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryIdToDelete));
14257
+ const renderIndex = renderedAncestries.findIndex(
14258
+ (a) => String(a.id) === String(ancestryIdToDelete)
14259
+ );
13404
14260
  if (renderIndex !== -1) {
13405
14261
  const toRemove = renderedAncestries[renderIndex];
13406
14262
  toRemove.lines.forEach((line) => {
@@ -13409,18 +14265,29 @@ function XViewScene({
13409
14265
  if (line.material) line.material.dispose();
13410
14266
  });
13411
14267
  renderedAncestries.splice(renderIndex, 1);
13412
- stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
14268
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
14269
+ (a) => a.lines
14270
+ );
13413
14271
  }
13414
14272
  setSceneVersion((v) => v + 1);
13415
14273
  } else {
13416
- throw new Error(result.error || "Ocorreu um erro desconhecido ao excluir.");
14274
+ throw new Error(
14275
+ result.error || "Ocorreu um erro desconhecido ao excluir."
14276
+ );
13417
14277
  }
13418
14278
  } catch (error) {
13419
14279
  console.error("Falha ao excluir a ancestralidade:", error);
13420
14280
  alert(`Erro ao excluir a ancestralidade: ${error.message}`);
13421
14281
  return;
13422
14282
  }
13423
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "" });
14283
+ setAncestryMode({
14284
+ isActive: false,
14285
+ tree: null,
14286
+ selectedParentId: null,
14287
+ isEditMode: false,
14288
+ currentAncestryId: null,
14289
+ ancestryName: ""
14290
+ });
13424
14291
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13425
14292
  },
13426
14293
  [save_view_data, delete_file_action]
@@ -13428,24 +14295,38 @@ function XViewScene({
13428
14295
  const handleOpenAncestryBoard = useCallback4(() => {
13429
14296
  setIsAncestryBoardOpen(true);
13430
14297
  }, []);
13431
- const handleSelectAncestryFromBoard = useCallback4((ancestry) => {
13432
- setIsAncestryBoardOpen(false);
13433
- setIsSidebarOpen(false);
13434
- handleStartReadingAncestry(ancestry);
13435
- }, [handleStartReadingAncestry]);
13436
- const handleSaveAncestryBoard = useCallback4(async (groups) => {
13437
- if (!sceneConfigId || !viewParams || !session) return;
13438
- const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
13439
- await save_ancestry_board_action(sceneConfigId, sceneType, groups, session, ownerId);
13440
- }, [sceneConfigId, viewParams, session, save_ancestry_board_action, ownerId]);
14298
+ const handleSelectAncestryFromBoard = useCallback4(
14299
+ (ancestry) => {
14300
+ setIsAncestryBoardOpen(false);
14301
+ setIsSidebarOpen(false);
14302
+ handleStartReadingAncestry(ancestry);
14303
+ },
14304
+ [handleStartReadingAncestry]
14305
+ );
14306
+ const handleSaveAncestryBoard = useCallback4(
14307
+ async (groups) => {
14308
+ if (!sceneConfigId || !viewParams || !session) return;
14309
+ const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
14310
+ await save_ancestry_board_action(
14311
+ sceneConfigId,
14312
+ sceneType,
14313
+ groups,
14314
+ session,
14315
+ ownerId
14316
+ );
14317
+ },
14318
+ [sceneConfigId, viewParams, session, save_ancestry_board_action, ownerId]
14319
+ );
13441
14320
  const existingNodeTypes = useMemo12(() => {
13442
14321
  if (!parentDataRef.current) {
13443
14322
  return [];
13444
14323
  }
13445
- const allTypes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes.flatMap((node) => {
13446
- if (Array.isArray(node.type)) return node.type;
13447
- return [node.type];
13448
- })).filter((t) => Boolean(t) && String(t).toLowerCase() !== "quest");
14324
+ const allTypes = Object.values(parentDataRef.current).flatMap(
14325
+ (fileData) => fileData.nodes.flatMap((node) => {
14326
+ if (Array.isArray(node.type)) return node.type;
14327
+ return [node.type];
14328
+ })
14329
+ ).filter((t) => Boolean(t) && String(t).toLowerCase() !== "quest");
13449
14330
  return [...new Set(allTypes)];
13450
14331
  }, [parentDataRef.current, sceneVersion]);
13451
14332
  const searchableDbNodes = useMemo12(() => {
@@ -13459,7 +14340,10 @@ function XViewScene({
13459
14340
  }, [parentDataRef.current, sceneVersion]);
13460
14341
  const handleAddExistingNode = useCallback4(
13461
14342
  (nodeId) => {
13462
- return userActionHandlers.handleAddExistingNodeById(actionHandlerContext, nodeId);
14343
+ return userActionHandlers.handleAddExistingNodeById(
14344
+ actionHandlerContext,
14345
+ nodeId
14346
+ );
13463
14347
  },
13464
14348
  [actionHandlerContext]
13465
14349
  );
@@ -13467,7 +14351,9 @@ function XViewScene({
13467
14351
  var _a2, _b2, _c2;
13468
14352
  const { nodeObjects, allLinks } = stateRef.current;
13469
14353
  if (!nodeObjects || !allLinks || !sceneSaveUrl || !parentDataRef.current) {
13470
- console.warn("N\xE3o \xE9 poss\xEDvel salvar a cena: estado n\xE3o inicializado ou URL de salvamento ausente.");
14354
+ console.warn(
14355
+ "N\xE3o \xE9 poss\xEDvel salvar a cena: estado n\xE3o inicializado ou URL de salvamento ausente."
14356
+ );
13471
14357
  return;
13472
14358
  }
13473
14359
  if (!save_view_data) return;
@@ -13504,48 +14390,68 @@ function XViewScene({
13504
14390
  }, [sceneSaveUrl, save_view_data, sceneConfigId, viewParams == null ? void 0 : viewParams.type]);
13505
14391
  const allAvailableNodes = useMemo12(() => {
13506
14392
  if (!parentDataRef.current) return [];
13507
- return Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes || []);
14393
+ return Object.values(parentDataRef.current).flatMap(
14394
+ (fileData) => fileData.nodes || []
14395
+ );
13508
14396
  }, [sceneVersion, isInitialized]);
13509
14397
  const allAvailableAncestries = useMemo12(() => {
13510
14398
  return ancestryDataRef.current || [];
13511
14399
  }, [sceneVersion, isInitialized]);
13512
- const handleOpenReference = useCallback4((referenceData) => {
13513
- const { type, id } = referenceData;
13514
- if (type === "node") {
13515
- const targetNode = allAvailableNodes.find((n) => String(n.id) === String(id));
13516
- if (targetNode) {
13517
- setAncestryLinkDetails(null);
13518
- setDetailsLink(null);
13519
- setDetailsNode(targetNode);
13520
- const sceneMesh = stateRef.current.nodeObjects[String(id)];
13521
- if (sceneMesh) {
13522
- tweenToTarget(sceneMesh);
14400
+ const handleOpenReference = useCallback4(
14401
+ (referenceData) => {
14402
+ const { type, id } = referenceData;
14403
+ if (type === "node") {
14404
+ const targetNode = allAvailableNodes.find(
14405
+ (n) => String(n.id) === String(id)
14406
+ );
14407
+ if (targetNode) {
14408
+ setAncestryLinkDetails(null);
14409
+ setDetailsLink(null);
14410
+ setDetailsNode(targetNode);
14411
+ const sceneMesh = stateRef.current.nodeObjects[String(id)];
14412
+ if (sceneMesh) {
14413
+ tweenToTarget(sceneMesh);
14414
+ }
14415
+ } else {
14416
+ alert("Node original n\xE3o encontrado neste contexto.");
14417
+ }
14418
+ } else if (type === "ancestry") {
14419
+ const targetAncestry = allAvailableAncestries.find(
14420
+ (a) => String(a.ancestry_id) === String(id)
14421
+ );
14422
+ if (targetAncestry) {
14423
+ setDetailsNode(null);
14424
+ setDetailsLink(null);
14425
+ setAncestryLinkDetails(null);
14426
+ handleEditAncestry(targetAncestry);
14427
+ } else {
14428
+ alert("Ancestralidade original n\xE3o encontrada neste contexto.");
13523
14429
  }
13524
- } else {
13525
- alert("Node original n\xE3o encontrado neste contexto.");
13526
- }
13527
- } else if (type === "ancestry") {
13528
- const targetAncestry = allAvailableAncestries.find((a) => String(a.ancestry_id) === String(id));
13529
- if (targetAncestry) {
13530
- setDetailsNode(null);
13531
- setDetailsLink(null);
13532
- setAncestryLinkDetails(null);
13533
- handleEditAncestry(targetAncestry);
13534
- } else {
13535
- alert("Ancestralidade original n\xE3o encontrada neste contexto.");
13536
14430
  }
13537
- }
13538
- }, [allAvailableNodes, allAvailableAncestries, handleEditAncestry, tweenToTarget]);
14431
+ },
14432
+ [
14433
+ allAvailableNodes,
14434
+ allAvailableAncestries,
14435
+ handleEditAncestry,
14436
+ tweenToTarget
14437
+ ]
14438
+ );
13539
14439
  const handleToggleAncestryAddMode = useCallback4(() => {
13540
- setAncestryMode((prev) => ({ ...prev, isAddingNodes: !prev.isAddingNodes }));
14440
+ setAncestryMode((prev) => ({
14441
+ ...prev,
14442
+ isAddingNodes: !prev.isAddingNodes
14443
+ }));
13541
14444
  }, []);
13542
- const handleFocusNode = useCallback4((nodeData) => {
13543
- if (!nodeData) return;
13544
- const nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
13545
- if (nodeMesh) {
13546
- tweenToTarget(nodeMesh, 1.2);
13547
- }
13548
- }, [tweenToTarget]);
14445
+ const handleFocusNode = useCallback4(
14446
+ (nodeData) => {
14447
+ if (!nodeData) return;
14448
+ const nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
14449
+ if (nodeMesh) {
14450
+ tweenToTarget(nodeMesh, 1.2);
14451
+ }
14452
+ },
14453
+ [tweenToTarget]
14454
+ );
13549
14455
  const availableDatasets = useMemo12(() => {
13550
14456
  if (!sceneDataRef.current || !parentDataRef.current) return [];
13551
14457
  return sceneDataRef.current.parent_dbs.map((db) => {
@@ -13556,7 +14462,9 @@ function XViewScene({
13556
14462
  };
13557
14463
  });
13558
14464
  }, [sceneVersion, isInitialized]);
13559
- const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(String(creationMode.sourceNodeData.id))) == null ? void 0 : _b.parentFileId : null;
14465
+ const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(
14466
+ String(creationMode.sourceNodeData.id)
14467
+ )) == null ? void 0 : _b.parentFileId : null;
13560
14468
  const detailsNodeDatasetInfo = detailsNode ? stateRef.current.nodeIdToParentFileMap.get(String(detailsNode.id)) : null;
13561
14469
  useEffect22(() => {
13562
14470
  if (isInitialized && focusNodeId && !hasFocusedInitial) {
@@ -13569,13 +14477,24 @@ function XViewScene({
13569
14477
  }, 300);
13570
14478
  } else {
13571
14479
  setHasFocusedInitial(true);
14480
+ setInvalidTargetError(
14481
+ "O link aponta para um item que n\xE3o foi encontrado ou foi exclu\xEDdo."
14482
+ );
13572
14483
  }
13573
14484
  }
13574
- }, [isInitialized, sceneVersion, focusNodeId, hasFocusedInitial, tweenToTarget]);
14485
+ }, [
14486
+ isInitialized,
14487
+ sceneVersion,
14488
+ focusNodeId,
14489
+ hasFocusedInitial,
14490
+ tweenToTarget
14491
+ ]);
13575
14492
  useEffect22(() => {
13576
14493
  if (isInitialized && focusAncestryId && !hasOpenedInitialAncestry) {
13577
14494
  const ancestries = ancestryDataRef.current || [];
13578
- const targetAncestry = ancestries.find((a) => String(a.ancestry_id) === String(focusAncestryId));
14495
+ const targetAncestry = ancestries.find(
14496
+ (a) => String(a.ancestry_id) === String(focusAncestryId)
14497
+ );
13579
14498
  if (targetAncestry) {
13580
14499
  setTimeout(() => {
13581
14500
  handleStartReadingAncestry(targetAncestry);
@@ -13583,20 +14502,43 @@ function XViewScene({
13583
14502
  }, 300);
13584
14503
  } else {
13585
14504
  setHasOpenedInitialAncestry(true);
14505
+ setInvalidTargetError(
14506
+ "O link aponta para uma ancestralidade que n\xE3o foi encontrada ou foi exclu\xEDda."
14507
+ );
13586
14508
  }
13587
14509
  }
13588
- }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
14510
+ }, [
14511
+ isInitialized,
14512
+ sceneVersion,
14513
+ focusAncestryId,
14514
+ hasOpenedInitialAncestry,
14515
+ handleStartReadingAncestry
14516
+ ]);
13589
14517
  useEffect22(() => {
13590
14518
  function handleKeyDown(event) {
13591
14519
  var _a2, _b2, _c2;
13592
14520
  const context = actionHandlerContext;
13593
14521
  if (event.key === "Escape") {
13594
- if (stateRef.current.connection.isActive) userActionHandlers.handleCancelConnection(context);
13595
- if (stateRef.current.relink.isActive) userActionHandlers.handleCancelRelink(context);
13596
- if (stateRef.current.creation.isActive) userActionHandlers.handleCancelCreation(context);
13597
- if ((_a2 = stateRef.current.versionMode) == null ? void 0 : _a2.isActive) userActionHandlers.handleCancelVersioning(context);
14522
+ if (stateRef.current.connection.isActive)
14523
+ userActionHandlers.handleCancelConnection(context);
14524
+ if (stateRef.current.relink.isActive)
14525
+ userActionHandlers.handleCancelRelink(context);
14526
+ if (stateRef.current.creation.isActive)
14527
+ userActionHandlers.handleCancelCreation(context);
14528
+ if ((_a2 = stateRef.current.versionMode) == null ? void 0 : _a2.isActive)
14529
+ userActionHandlers.handleCancelVersioning(context);
13598
14530
  if (stateRef.current.ancestry.isActive) {
13599
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
14531
+ setAncestryMode({
14532
+ isActive: false,
14533
+ tree: null,
14534
+ selectedParentId: null,
14535
+ isEditMode: false,
14536
+ currentAncestryId: null,
14537
+ ancestryName: "",
14538
+ ancestryDescription: "",
14539
+ ancestryDescriptionSections: [],
14540
+ isAddingNodes: false
14541
+ });
13600
14542
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13601
14543
  }
13602
14544
  if (questMode.isActive) {
@@ -13605,7 +14547,9 @@ function XViewScene({
13605
14547
  if (stateRef.current.selectedNodes.size > 0) {
13606
14548
  stateRef.current.selectedNodes.clear();
13607
14549
  }
13608
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
14550
+ setContextMenu(
14551
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
14552
+ );
13609
14553
  setMultiContextMenu((prev) => ({ ...prev, visible: false }));
13610
14554
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13611
14555
  }
@@ -13624,7 +14568,9 @@ function XViewScene({
13624
14568
  let attempts = 0;
13625
14569
  const MIN_CLEARANCE = 15;
13626
14570
  while (isOccupied && attempts < 30) {
13627
- isOccupied = existingNodes.some((mesh) => mesh.position.distanceTo(ghostPosition) < MIN_CLEARANCE);
14571
+ isOccupied = existingNodes.some(
14572
+ (mesh) => mesh.position.distanceTo(ghostPosition) < MIN_CLEARANCE
14573
+ );
13628
14574
  if (isOccupied) {
13629
14575
  ghostPosition.x = controls.target.x + Math.cos(angle) * radius;
13630
14576
  ghostPosition.y = controls.target.y + (Math.random() - 0.5) * 8;
@@ -13643,7 +14589,11 @@ function XViewScene({
13643
14589
  intensity: 0,
13644
14590
  type: ["quest"]
13645
14591
  };
13646
- const ghostNode = createNodeMesh(ghostData, ghostPosition, glowTexture);
14592
+ const ghostNode = createNodeMesh(
14593
+ ghostData,
14594
+ ghostPosition,
14595
+ glowTexture
14596
+ );
13647
14597
  ghostNode.traverse((child) => {
13648
14598
  if (child.isMesh) {
13649
14599
  child.material.transparent = true;
@@ -13690,13 +14640,49 @@ function XViewScene({
13690
14640
  return /* @__PURE__ */ React25.createElement(LoadingScreen, null);
13691
14641
  }
13692
14642
  if (permissionStatus === "denied") {
13693
- return /* @__PURE__ */ React25.createElement("div", { className: "flex flex-col items-center justify-center min-h-screen w-full bg-slate-950 text-white" }, /* @__PURE__ */ React25.createElement("div", { className: "bg-slate-900/50 p-8 rounded-2xl border border-slate-800 shadow-2xl text-center max-w-md" }, /* @__PURE__ */ React25.createElement("div", { className: "mb-4 text-red-500" }, /* @__PURE__ */ React25.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "w-16 h-16 mx-auto" }, /* @__PURE__ */ React25.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" }))), /* @__PURE__ */ React25.createElement("h2", { className: "text-2xl font-bold mb-2" }, "Acesso Negado"), /* @__PURE__ */ React25.createElement("p", { className: "text-slate-400 mb-6" }, "Voc\xEA n\xE3o tem permiss\xE3o para acessar este conte\xFAdo. Solicite acesso ao propriet\xE1rio ou verifique se est\xE1 na conta correta."), /* @__PURE__ */ React25.createElement(
14643
+ return /* @__PURE__ */ React25.createElement("div", { className: "flex flex-col items-center justify-center min-h-screen w-full bg-slate-950 text-white" }, /* @__PURE__ */ React25.createElement("div", { className: "bg-slate-900/50 p-8 rounded-2xl border border-slate-800 shadow-2xl text-center max-w-md" }, /* @__PURE__ */ React25.createElement("div", { className: "mb-4 text-red-500" }, /* @__PURE__ */ React25.createElement(
14644
+ "svg",
14645
+ {
14646
+ xmlns: "http://www.w3.org/2000/svg",
14647
+ fill: "none",
14648
+ viewBox: "0 0 24 24",
14649
+ strokeWidth: 1.5,
14650
+ stroke: "currentColor",
14651
+ className: "w-16 h-16 mx-auto"
14652
+ },
14653
+ /* @__PURE__ */ React25.createElement(
14654
+ "path",
14655
+ {
14656
+ strokeLinecap: "round",
14657
+ strokeLinejoin: "round",
14658
+ d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
14659
+ }
14660
+ )
14661
+ )), /* @__PURE__ */ React25.createElement("h2", { className: "text-2xl font-bold mb-2" }, "Acesso Negado"), /* @__PURE__ */ React25.createElement("p", { className: "text-slate-400 mb-6" }, "Voc\xEA n\xE3o tem permiss\xE3o para acessar este conte\xFAdo. Solicite acesso ao propriet\xE1rio ou verifique se est\xE1 na conta correta."), /* @__PURE__ */ React25.createElement(
13694
14662
  "button",
13695
14663
  {
13696
14664
  onClick: () => router.push("/dashboard/scenes"),
13697
14665
  className: "flex items-center justify-center gap-2 w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
13698
14666
  },
13699
- /* @__PURE__ */ React25.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 2, stroke: "currentColor", className: "w-5 h-5" }, /* @__PURE__ */ React25.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" })),
14667
+ /* @__PURE__ */ React25.createElement(
14668
+ "svg",
14669
+ {
14670
+ xmlns: "http://www.w3.org/2000/svg",
14671
+ fill: "none",
14672
+ viewBox: "0 0 24 24",
14673
+ strokeWidth: 2,
14674
+ stroke: "currentColor",
14675
+ className: "w-5 h-5"
14676
+ },
14677
+ /* @__PURE__ */ React25.createElement(
14678
+ "path",
14679
+ {
14680
+ strokeLinecap: "round",
14681
+ strokeLinejoin: "round",
14682
+ d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
14683
+ }
14684
+ )
14685
+ ),
13700
14686
  "Voltar para Scenes"
13701
14687
  )));
13702
14688
  }
@@ -13743,7 +14729,14 @@ function XViewScene({
13743
14729
  onImageChange: handleGhostNodeImageChange,
13744
14730
  onOpenImageViewer: handleOpenImageViewer,
13745
14731
  onMentionClick: handleAddExistingNode,
13746
- style: { position: "absolute", left: `${formPosition.left}px`, top: `${formPosition.top}px`, opacity: formPosition.opacity, zIndex: 20, transition: "opacity 200ms ease-out" },
14732
+ style: {
14733
+ position: "absolute",
14734
+ left: `${formPosition.left}px`,
14735
+ top: `${formPosition.top}px`,
14736
+ opacity: formPosition.opacity,
14737
+ zIndex: 20,
14738
+ transition: "opacity 200ms ease-out"
14739
+ },
13747
14740
  refEl: formRef,
13748
14741
  existingTypes: existingNodeTypes,
13749
14742
  initialColor: (_c = creationMode.sourceNodeData) == null ? void 0 : _c.color,
@@ -13767,7 +14760,14 @@ function XViewScene({
13767
14760
  onImageChange: handleGhostNodeImageChange,
13768
14761
  onOpenImageViewer: handleOpenImageViewer,
13769
14762
  onMentionClick: handleAddExistingNode,
13770
- style: { position: "absolute", left: `${formPosition.left}px`, top: `${formPosition.top}px`, opacity: formPosition.opacity, zIndex: 20, transition: "opacity 200ms ease-out" },
14763
+ style: {
14764
+ position: "absolute",
14765
+ left: `${formPosition.left}px`,
14766
+ top: `${formPosition.top}px`,
14767
+ opacity: formPosition.opacity,
14768
+ zIndex: 20,
14769
+ transition: "opacity 200ms ease-out"
14770
+ },
13771
14771
  refEl: formRef,
13772
14772
  fixedType: (_e = versionMode.sourceNodeData) == null ? void 0 : _e.type,
13773
14773
  fixedColor: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.color,
@@ -13784,7 +14784,13 @@ function XViewScene({
13784
14784
  onNameChange: handleGhostNodeNameChange,
13785
14785
  onColorChange: handleGhostNodeColorChange,
13786
14786
  onSizeChange: handleGhostNodeSizeChange,
13787
- style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
14787
+ style: {
14788
+ position: "absolute",
14789
+ left: `16px`,
14790
+ top: `16px`,
14791
+ zIndex: 20,
14792
+ transition: "opacity 200ms ease-out"
14793
+ },
13788
14794
  refEl: formRef,
13789
14795
  onOpenImageViewer: handleOpenImageViewer,
13790
14796
  onMentionClick: handleAddExistingNode,
@@ -13799,7 +14805,14 @@ function XViewScene({
13799
14805
  "div",
13800
14806
  {
13801
14807
  className: `ui-overlay absolute group rounded-2xl border border-white/10 bg-slate-950/70 backdrop-blur-xl shadow-[0_20px_80px_rgba(0,0,0,0.6)] ring-1 ring-white/10 text-white overflow-hidden flex flex-col ${isReadModeResizing ? "transition-none" : "transition-all duration-300 ease-out"}`,
13802
- style: { top: 16, right: 16, zIndex: 1100, maxHeight: "calc(100vh - 32px)", width: `${readModeWidth}px`, maxWidth: "92vw" }
14808
+ style: {
14809
+ top: 16,
14810
+ right: 16,
14811
+ zIndex: 1100,
14812
+ maxHeight: "calc(100vh - 32px)",
14813
+ width: `${readModeWidth}px`,
14814
+ maxWidth: "92vw"
14815
+ }
13803
14816
  },
13804
14817
  /* @__PURE__ */ React25.createElement(
13805
14818
  "div",
@@ -13857,7 +14870,16 @@ function XViewScene({
13857
14870
  onSave: handleSaveAncestry,
13858
14871
  onEditRelationship: handleOpenAncestryRelEditor,
13859
14872
  onClose: () => {
13860
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", isAddingNodes: false });
14873
+ setAncestryMode({
14874
+ isActive: false,
14875
+ tree: null,
14876
+ selectedParentId: null,
14877
+ isEditMode: false,
14878
+ currentAncestryId: null,
14879
+ ancestryName: "",
14880
+ ancestryDescription: "",
14881
+ isAddingNodes: false
14882
+ });
13861
14883
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13862
14884
  },
13863
14885
  availableNodes: allAvailableNodes,
@@ -13922,7 +14944,12 @@ function XViewScene({
13922
14944
  onOpenImageViewer: handleOpenImageViewer,
13923
14945
  existingTypes: existingNodeTypes,
13924
14946
  onImageChange: (useImage, url, currentColorOverride) => {
13925
- const updatedNode = { ...detailsNode, useImageAsTexture: useImage, textureImageUrl: url, color: currentColorOverride || detailsNode.color };
14947
+ const updatedNode = {
14948
+ ...detailsNode,
14949
+ useImageAsTexture: useImage,
14950
+ textureImageUrl: url,
14951
+ color: currentColorOverride || detailsNode.color
14952
+ };
13926
14953
  updateExistingNodeVisuals(stateRef.current, updatedNode);
13927
14954
  setDetailsNode(updatedNode);
13928
14955
  },
@@ -13996,7 +15023,9 @@ function XViewScene({
13996
15023
  parentData: parentDataRef.current,
13997
15024
  sceneData: sceneDataRef.current,
13998
15025
  ancestryData: ancestryDataRef.current,
13999
- onClose: () => setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev),
15026
+ onClose: () => setContextMenu(
15027
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
15028
+ ),
14000
15029
  onStartCreation: (data) => userActionHandlers.handleStartCreation(actionHandlerContext, data),
14001
15030
  onStartConnection: (data) => userActionHandlers.handleStartConnection(actionHandlerContext, data),
14002
15031
  onStartVersioning: handleStartVersioning,
@@ -14004,7 +15033,11 @@ function XViewScene({
14004
15033
  onDeleteNode: (data) => userActionHandlers.handleDeleteNode(actionHandlerContext, data),
14005
15034
  onDismissNode: (data) => userActionHandlers.handleDismissNode(actionHandlerContext, data),
14006
15035
  onDismissOtherNodes: (data) => userActionHandlers.handleDismissOtherNodes(actionHandlerContext, data),
14007
- onExpandConnections: (sourceNode, links) => userActionHandlers.handleExpandConnections(actionHandlerContext, sourceNode, links),
15036
+ onExpandConnections: (sourceNode, links) => userActionHandlers.handleExpandConnections(
15037
+ actionHandlerContext,
15038
+ sourceNode,
15039
+ links
15040
+ ),
14008
15041
  onRenderAncestry: handleStartReadingAncestry,
14009
15042
  onEditAncestry: handleEditAncestry,
14010
15043
  onDeleteAncestry: (ancestryId) => handleDeleteAncestry(ancestryId),
@@ -14017,9 +15050,18 @@ function XViewScene({
14017
15050
  data: multiContextMenu,
14018
15051
  userRole: userPermissionRole,
14019
15052
  onClose: () => setMultiContextMenu((prev) => ({ ...prev, visible: false })),
14020
- onDismissNodes: (ids) => userActionHandlers.handleDismissMultipleNodes(actionHandlerContext, ids),
14021
- onDismissOtherNodes: (ids) => userActionHandlers.handleDismissOtherMultipleNodes(actionHandlerContext, ids),
14022
- onDeleteNodes: (ids) => userActionHandlers.handleDeleteMultipleNodes(actionHandlerContext, ids)
15053
+ onDismissNodes: (ids) => userActionHandlers.handleDismissMultipleNodes(
15054
+ actionHandlerContext,
15055
+ ids
15056
+ ),
15057
+ onDismissOtherNodes: (ids) => userActionHandlers.handleDismissOtherMultipleNodes(
15058
+ actionHandlerContext,
15059
+ ids
15060
+ ),
15061
+ onDeleteNodes: (ids) => userActionHandlers.handleDeleteMultipleNodes(
15062
+ actionHandlerContext,
15063
+ ids
15064
+ )
14023
15065
  }
14024
15066
  ),
14025
15067
  /* @__PURE__ */ React25.createElement(
@@ -14040,7 +15082,13 @@ function XViewScene({
14040
15082
  onDelete: (data) => userActionHandlers.handleDeleteLink(actionHandlerContext, data)
14041
15083
  }
14042
15084
  ),
14043
- /* @__PURE__ */ React25.createElement(ImageViewer, { data: imageViewer, onClose: () => setImageViewer({ ...imageViewer, visible: false }) }),
15085
+ /* @__PURE__ */ React25.createElement(
15086
+ ImageViewer,
15087
+ {
15088
+ data: imageViewer,
15089
+ onClose: () => setImageViewer({ ...imageViewer, visible: false })
15090
+ }
15091
+ ),
14044
15092
  /* @__PURE__ */ React25.createElement(
14045
15093
  AncestryBoard,
14046
15094
  {
@@ -14066,6 +15114,83 @@ function XViewScene({
14066
15114
  currentViewName: viewParams == null ? void 0 : viewParams.name,
14067
15115
  currentAncestries: ancestryDataRef.current || []
14068
15116
  }
15117
+ ),
15118
+ invalidTargetError && /* @__PURE__ */ React25.createElement(
15119
+ "div",
15120
+ {
15121
+ className: "ui-overlay",
15122
+ style: {
15123
+ position: "fixed",
15124
+ top: "24px",
15125
+ left: "50%",
15126
+ transform: "translateX(-50%)",
15127
+ zIndex: 1e4,
15128
+ padding: "16px 24px",
15129
+ background: "rgba(30, 20, 20, 0.85)",
15130
+ backdropFilter: "blur(12px)",
15131
+ WebkitBackdropFilter: "blur(12px)",
15132
+ border: "1px solid rgba(255, 70, 70, 0.35)",
15133
+ borderRadius: "16px",
15134
+ boxShadow: "0 12px 40px rgba(0,0,0,0.5), 0 0 30px rgba(255, 50, 50, 0.1)",
15135
+ color: "#ffa0a0",
15136
+ display: "flex",
15137
+ alignItems: "center",
15138
+ gap: "16px",
15139
+ fontFamily: "Inter, sans-serif",
15140
+ animation: "fadeInDown 0.5s cubic-bezier(0.16, 1, 0.3, 1)"
15141
+ }
15142
+ },
15143
+ /* @__PURE__ */ React25.createElement("style", null, `
15144
+ @keyframes fadeInDown {
15145
+ from { opacity: 0; transform: translate(-50%, -20px); }
15146
+ to { opacity: 1; transform: translate(-50%, 0); }
15147
+ }
15148
+ `),
15149
+ /* @__PURE__ */ React25.createElement(
15150
+ "svg",
15151
+ {
15152
+ width: "20",
15153
+ height: "20",
15154
+ viewBox: "0 0 24 24",
15155
+ fill: "none",
15156
+ stroke: "currentColor",
15157
+ strokeWidth: "2",
15158
+ strokeLinecap: "round",
15159
+ strokeLinejoin: "round"
15160
+ },
15161
+ /* @__PURE__ */ React25.createElement("circle", { cx: "12", cy: "12", r: "10" }),
15162
+ /* @__PURE__ */ React25.createElement("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
15163
+ /* @__PURE__ */ React25.createElement("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
15164
+ ),
15165
+ /* @__PURE__ */ React25.createElement("span", { style: { fontSize: "14px", fontWeight: 500 } }, invalidTargetError),
15166
+ /* @__PURE__ */ React25.createElement(
15167
+ "button",
15168
+ {
15169
+ onClick: () => setInvalidTargetError(null),
15170
+ style: {
15171
+ background: "rgba(255, 255, 255, 0.1)",
15172
+ border: "none",
15173
+ color: "white",
15174
+ padding: "4px 10px",
15175
+ borderRadius: "8px",
15176
+ cursor: "pointer",
15177
+ fontSize: "12px",
15178
+ fontWeight: 600,
15179
+ transition: "all 0.2s",
15180
+ marginLeft: "8px",
15181
+ border: "1px solid rgba(255,255,255,0.1)"
15182
+ },
15183
+ onMouseOver: (e) => {
15184
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
15185
+ e.currentTarget.style.transform = "translateY(-1px)";
15186
+ },
15187
+ onMouseOut: (e) => {
15188
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
15189
+ e.currentTarget.style.transform = "translateY(0)";
15190
+ }
15191
+ },
15192
+ "Fechar"
15193
+ )
14069
15194
  )
14070
15195
  );
14071
15196
  }