@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.js CHANGED
@@ -149,6 +149,7 @@ function ContextMenu({
149
149
  const [menuView, setMenuView] = (0, import_react.useState)("main");
150
150
  const [selectedAncestry, setSelectedAncestry] = (0, import_react.useState)(null);
151
151
  const [versionSubMenu, setVersionSubMenu] = (0, import_react.useState)(null);
152
+ const [labelSubMenu, setLabelSubMenu] = (0, import_react.useState)(null);
152
153
  const [isLinkCopied, setIsLinkCopied] = (0, import_react.useState)(false);
153
154
  const [selectedQuestStatus, setSelectedQuestStatus] = (0, import_react.useState)(null);
154
155
  const ability = (0, import_react.useMemo)(() => defineAbilityFor(userRole), [userRole]);
@@ -157,6 +158,7 @@ function ContextMenu({
157
158
  setMenuView("main");
158
159
  setSelectedAncestry(null);
159
160
  setVersionSubMenu(null);
161
+ setLabelSubMenu(null);
160
162
  setSelectedQuestStatus(null);
161
163
  }
162
164
  }, [data.visible, (_a = data.nodeData) == null ? void 0 : _a.id]);
@@ -172,7 +174,7 @@ function ContextMenu({
172
174
  if (left + w + 8 > vw) left = Math.max(8, vw - w - 8);
173
175
  if (top + h + 8 > vh) top = Math.max(8, vh - h - 8);
174
176
  setMenuPos({ left, top });
175
- }, [data, menuView, versionSubMenu, selectedQuestStatus]);
177
+ }, [data, menuView, versionSubMenu, labelSubMenu, selectedQuestStatus]);
176
178
  (0, import_react.useEffect)(() => {
177
179
  if (!data.visible) return;
178
180
  const handleClickOutside = (e) => {
@@ -224,7 +226,21 @@ function ContextMenu({
224
226
  var _a2;
225
227
  return (_a2 = c.targetNode) == null ? void 0 : _a2.is_quest;
226
228
  });
227
- const groupedConnections = commonConnections.reduce((acc, conn) => {
229
+ const getLabelForConnection = (conn) => {
230
+ if (conn.direction === "outgoing") return conn.link.source_label || null;
231
+ if (conn.direction === "incoming") return conn.link.target_label || null;
232
+ return null;
233
+ };
234
+ const commonWithLabel = commonConnections.filter((c) => getLabelForConnection(c));
235
+ const commonWithoutLabel = commonConnections.filter((c) => !getLabelForConnection(c));
236
+ const labelGroups = commonWithLabel.reduce((acc, conn) => {
237
+ const label = getLabelForConnection(conn);
238
+ if (!acc[label]) acc[label] = [];
239
+ acc[label].push(conn);
240
+ return acc;
241
+ }, {});
242
+ const labelGroupEntries = Object.entries(labelGroups).sort(([a], [b]) => a.localeCompare(b));
243
+ const groupedConnections = commonWithoutLabel.reduce((acc, conn) => {
228
244
  var _a2;
229
245
  const { targetNode } = conn;
230
246
  const groupingKey = ((_a2 = targetNode.version_node) == null ? void 0 : _a2.is_version) ? targetNode.version_node.parent_node : targetNode.id;
@@ -352,11 +368,32 @@ function ContextMenu({
352
368
  /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
353
369
  ))));
354
370
  };
371
+ const renderLabelSubMenuView = () => {
372
+ const group = labelSubMenu;
373
+ const isScrollable = group.connections.length > 10;
374
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
375
+ setLabelSubMenu(null);
376
+ setMenuView("connections");
377
+ }, className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-1.5 min-w-0" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.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__ */ import_react.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-violet-300/80 truncate", title: group.label }, group.label))), group.connections.length > 0 && /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("div", { className: `flex flex-col gap-1 ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, group.connections.map((conn) => /* @__PURE__ */ import_react.default.createElement(
378
+ "button",
379
+ {
380
+ key: conn.targetNode.id,
381
+ onClick: () => handleExpandAndClose([conn.link]),
382
+ className: baseButtonClass,
383
+ title: `Expandir conex\xE3o com ${conn.targetNode.name}`
384
+ },
385
+ /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }), /* @__PURE__ */ import_react.default.createElement("polyline", { points: "12 5 19 12 12 19" })),
386
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
387
+ ))));
388
+ };
355
389
  const renderConnectionsView = () => {
356
390
  if (versionSubMenu) {
357
391
  return renderVersionSubMenuView();
358
392
  }
359
- const totalItems = availableAncestries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
393
+ if (labelSubMenu) {
394
+ return renderLabelSubMenuView();
395
+ }
396
+ const totalItems = availableAncestries.length + labelGroupEntries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
360
397
  const isScrollable = totalItems > 10;
361
398
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => setMenuView("main"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Conex\xF5es")), commonConnections.length > 0 && /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("div", { className: `flex flex-col ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: `flex flex-col gap-1 ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wider text-indigo-400/90" }, "Ancestralidades Salvas"), availableAncestries.map((anc) => /* @__PURE__ */ import_react.default.createElement(
362
399
  "button",
@@ -368,7 +405,20 @@ function ContextMenu({
368
405
  },
369
406
  /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("path", { d: "M6 3v4a2 2 0 0 0 2 2h4" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M18 3v4a2 2 0 0 1-2 2h-4" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "6", cy: "3", r: "2" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "18", cy: "3", r: "2" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "11", r: "2" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M12 13v6" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "21", r: "2" })),
370
407
  /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, anc.name || `Ancestralidade #${anc.ancestry_id.substring(0, 8)}`)
371
- ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, availableAncestries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
408
+ ))), labelGroupEntries.length > 0 && /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), labelGroupEntries.map(([label, conns]) => /* @__PURE__ */ import_react.default.createElement(
409
+ "button",
410
+ {
411
+ key: label,
412
+ onClick: () => {
413
+ setLabelSubMenu({ label, connections: conns });
414
+ },
415
+ className: baseButtonClass,
416
+ title: `Ver grupo: ${label}`
417
+ },
418
+ /* @__PURE__ */ import_react.default.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__ */ import_react.default.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__ */ import_react.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })),
419
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, label),
420
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "text-xs px-2 py-0.5 bg-violet-500/20 text-violet-300 rounded-full" }, conns.length)
421
+ ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, (availableAncestries.length > 0 || labelGroupEntries.length > 0) && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
372
422
  if (group.isVersionGroup) {
373
423
  return /* @__PURE__ */ import_react.default.createElement(
374
424
  "button",
@@ -446,11 +496,18 @@ function ContextMenu({
446
496
  ))));
447
497
  };
448
498
  const renderAncestryActionsView = () => {
499
+ var _a2, _b2;
449
500
  const ancestryTitle = (selectedAncestry == null ? void 0 : selectedAncestry.name) || `Ancestralidade #${selectedAncestry == null ? void 0 : selectedAncestry.ancestry_id.substring(0, 8)}`;
450
- return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => setMenuView("connections"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400 truncate", title: ancestryTitle }, ancestryTitle))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, ability.can("read", "Ancestry") && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
451
- onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry);
501
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => setMenuView("connections"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400 truncate", title: ancestryTitle }, ancestryTitle))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, ability.can("read", "Ancestry") && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
502
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "full");
503
+ onClose();
504
+ }, className: baseButtonClass, title: "Renderizar Ancestralidade Completa" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "3" })), /* @__PURE__ */ import_react.default.createElement("span", null, "Renderizar Ancestralidade")), /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
505
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "ancestry_only");
506
+ onClose();
507
+ }, className: baseButtonClass, title: "Renderizar apenas a \xC1rvore de Ancestralidade (Radial)" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "3" }), /* @__PURE__ */ import_react.default.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__ */ import_react.default.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__ */ import_react.default.createElement("button", { onClick: () => {
508
+ onRenderAncestry == null ? void 0 : onRenderAncestry(selectedAncestry, "abstraction_only");
452
509
  onClose();
453
- }, className: baseButtonClass, title: "Renderizar Ancestralidade" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "3" })), /* @__PURE__ */ import_react.default.createElement("span", null, "Renderizar Ancestralidade")), ability.can("update", "Ancestry") && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
510
+ }, className: baseButtonClass, title: "Renderizar apenas a \xC1rvore de Abstra\xE7\xE3o (Vertical)" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "14", y: "14", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M10 6.5h4" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M10 17.5h4" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M6.5 10v4" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M17.5 10v4" })), /* @__PURE__ */ import_react.default.createElement("span", null, "\xC1rvore de Abstra\xE7\xE3o"))), ability.can("update", "Ancestry") && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
454
511
  onEditAncestry == null ? void 0 : onEditAncestry(selectedAncestry);
455
512
  onClose();
456
513
  }, className: baseButtonClass, title: "Editar Ancestralidade" }, /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("path", { d: "M12 20h9" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })), /* @__PURE__ */ import_react.default.createElement("span", null, "Editar Ancestralidade")), (ability.can("update", "Ancestry") || ability.can("delete", "Ancestry")) && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), ability.can("delete", "Ancestry") && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
@@ -697,7 +754,7 @@ function XViewSidebar({
697
754
  "div",
698
755
  {
699
756
  ref: containerRef,
700
- className: "ui-overlay fixed left-0 top-0 h-full w-[min(92vw,320px)] z-40",
757
+ className: "ui-overlay fixed left-0 top-0 h-[100dvh] w-[min(92vw,320px)] z-40 overflow-hidden",
701
758
  onPointerDown: swallow,
702
759
  onPointerMove: swallow,
703
760
  onPointerUp: swallow,
@@ -706,7 +763,7 @@ function XViewSidebar({
706
763
  onContextMenu: swallow,
707
764
  onDoubleClick: swallow
708
765
  },
709
- /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ import_react2.default.createElement(
766
+ /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ import_react2.default.createElement(
710
767
  "button",
711
768
  {
712
769
  className: "ml-auto p-2 rounded-md text-slate-400 hover:text-white hover:bg-white/10 transition-colors",
@@ -763,7 +820,7 @@ function XViewSidebar({
763
820
  autoComplete: "off"
764
821
  }
765
822
  )
766
- ), showList && /* @__PURE__ */ import_react2.default.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) => {
823
+ ), showList && /* @__PURE__ */ import_react2.default.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) => {
767
824
  const inView = isNodeInView(n.id);
768
825
  const active = selectedNodeId === n.id;
769
826
  const typeLabel = Array.isArray(n.type) ? n.type.join(", ") : n.type;
@@ -3139,6 +3196,7 @@ function calculateNodePositions(nodes) {
3139
3196
  return positions;
3140
3197
  }
3141
3198
  function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, isSceneBusy, parentData, ancestryData }) {
3199
+ var _a, _b;
3142
3200
  if (!tooltipEl || !camera || !mountEl) return;
3143
3201
  let content = "";
3144
3202
  let positionTarget = null;
@@ -3151,17 +3209,21 @@ function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, i
3151
3209
  content = generateTooltipHtml(hoveredNode.userData, parentData, ancestryData);
3152
3210
  }
3153
3211
  } else if (hoveredLink && !isSceneBusy) {
3154
- currentId = `link_${hoveredLink.userData.id}`;
3155
- if (hoveredLink.userData.isCurved) {
3156
- const positions = hoveredLink.geometry.attributes.position.array;
3157
- const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3158
- positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3159
- } else {
3160
- positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3161
- }
3162
- isLink = true;
3163
- if (tooltipEl.dataset.currentId !== currentId) {
3164
- content = hoveredLink.userData.isAncestryLink ? generateLinkTooltipHtml(hoveredLink.userData.relationship || {}, parentData, ancestryData) : generateLinkTooltipHtml(hoveredLink.userData, parentData, ancestryData);
3212
+ const linkData = hoveredLink.userData.isAncestryLink ? hoveredLink.userData.relationship || {} : hoveredLink.userData;
3213
+ const hasContent = ((_a = linkData.name) == null ? void 0 : _a.trim()) || ((_b = linkData.description) == null ? void 0 : _b.trim());
3214
+ if (hasContent) {
3215
+ currentId = `link_${hoveredLink.userData.id}`;
3216
+ if (hoveredLink.userData.isCurved) {
3217
+ const positions = hoveredLink.geometry.attributes.position.array;
3218
+ const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3219
+ positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3220
+ } else {
3221
+ positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3222
+ }
3223
+ isLink = true;
3224
+ if (tooltipEl.dataset.currentId !== currentId) {
3225
+ content = generateLinkTooltipHtml(linkData, parentData, ancestryData);
3226
+ }
3165
3227
  }
3166
3228
  }
3167
3229
  if (positionTarget) {
@@ -3470,9 +3532,9 @@ function CustomPropertyDisplay({
3470
3532
  };
3471
3533
  const handleRemoveListItem = (j) => setTempProp((p) => ({ ...p, value: p.value.filter((_, k) => k !== j) }));
3472
3534
  const handleListItemChange = (j, f, v) => setTempProp((p) => {
3473
- const newValue = [...p.value];
3474
- newValue[j] = { ...newValue[j], [f]: v };
3475
- return { ...p, value: newValue };
3535
+ const newValue2 = [...p.value];
3536
+ newValue2[j] = { ...newValue2[j], [f]: v };
3537
+ return { ...p, value: newValue2 };
3476
3538
  });
3477
3539
  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";
3478
3540
  const renderEditView = () => {
@@ -3526,14 +3588,14 @@ function CustomPropertyDisplay({
3526
3588
  const inputClass = `${baseInput} ${noSpinnerClass}`;
3527
3589
  const handleDateTypeChange = (newDateType) => {
3528
3590
  var _a3, _b2, _c2;
3529
- let newValue = { type: newDateType };
3591
+ let newValue2 = { type: newDateType };
3530
3592
  if (newDateType === "Date Interval") {
3531
- newValue.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3532
- newValue.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3593
+ newValue2.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3594
+ newValue2.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3533
3595
  } else {
3534
- newValue.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3596
+ newValue2.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3535
3597
  }
3536
- handlePropChange("value", newValue);
3598
+ handlePropChange("value", newValue2);
3537
3599
  };
3538
3600
  const handleDateValueChange = (dateField, dateValue) => {
3539
3601
  handlePropChange("value", { ...tempProp.value, [dateField]: dateValue });
@@ -4381,7 +4443,7 @@ ${space}${bullet} `);
4381
4443
  }
4382
4444
  ),
4383
4445
  /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0 shrink-0" }),
4384
- /* @__PURE__ */ import_react6.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-center justify-between gap-4 shrink-0" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80" }), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-sm font-medium text-slate-200" }, title || "Editar Descri\xE7\xE3o")), /* @__PURE__ */ import_react6.default.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")),
4446
+ /* @__PURE__ */ import_react6.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-center justify-between gap-4 shrink-0" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "inline-flex h-2.5 w-2.5 rounded-full bg-indigo-400/80 shrink-0" }), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-sm font-medium text-slate-200 truncate" }, title || "Editar Descri\xE7\xE3o")), /* @__PURE__ */ import_react6.default.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")),
4385
4447
  /* @__PURE__ */ import_react6.default.createElement("div", { className: "px-6 py-3 flex flex-col gap-3 border-b border-white/5 bg-white/5 shrink-0" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center gap-2 flex-wrap" }, /* @__PURE__ */ import_react6.default.createElement(
4386
4448
  "button",
4387
4449
  {
@@ -5496,7 +5558,7 @@ function AncestryRelationshipPanel({
5496
5558
  onImageClick: handleImageClickFromText,
5497
5559
  onSaveDescription: handleSaveDescriptionInline
5498
5560
  }
5499
- ) : /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-cyan-400/0 via-cyan-400/70 to-cyan-400/0" }), /* @__PURE__ */ import_react9.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react9.default.createElement("div", null, /* @__PURE__ */ import_react9.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o de Ancestralidade")), /* @__PURE__ */ import_react9.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, "Editar Rela\xE7\xE3o")), /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement("div", { className: "px-6 pb-4 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react9.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o da Rela\xE7\xE3o"), /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement(
5561
+ ) : /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-cyan-400/0 via-cyan-400/70 to-cyan-400/0" }), /* @__PURE__ */ import_react9.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o de Ancestralidade")), /* @__PURE__ */ import_react9.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, "Editar Rela\xE7\xE3o")), /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement("div", { className: "px-6 pb-4 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react9.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o da Rela\xE7\xE3o"), /* @__PURE__ */ import_react9.default.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__ */ import_react9.default.createElement(
5500
5562
  DescriptionDisplay,
5501
5563
  {
5502
5564
  description,
@@ -5801,6 +5863,7 @@ function CreateAncestryPanel({
5801
5863
  } = ancestryMode;
5802
5864
  const [isSaving, setIsSaving] = (0, import_react11.useState)(false);
5803
5865
  const [isLinkCopied, setIsLinkCopied] = (0, import_react11.useState)(false);
5866
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react11.useState)(false);
5804
5867
  const [showDeleteBranchConfirm, setShowDeleteBranchConfirm] = (0, import_react11.useState)(false);
5805
5868
  const handleCopyLink = (e) => {
5806
5869
  e.stopPropagation();
@@ -5868,12 +5931,14 @@ function CreateAncestryPanel({
5868
5931
  };
5869
5932
  const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5870
5933
  setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5934
+ setHasUnsavedChanges(true);
5871
5935
  };
5872
5936
  const handleToggleAddMode = (isAbstraction = false) => {
5873
5937
  if (isAbstraction && !ancestryMode.isAddingAbstractionNodes) {
5874
5938
  setTargetRenderNodeId(null);
5875
5939
  }
5876
5940
  setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5941
+ setHasUnsavedChanges(true);
5877
5942
  };
5878
5943
  const handleRemoveNode = (0, import_react11.useCallback)((pathToRemove, isAbstraction = false) => {
5879
5944
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
@@ -5891,6 +5956,7 @@ function CreateAncestryPanel({
5891
5956
  const indexToRemove = pathToRemove[pathToRemove.length - 1];
5892
5957
  if (currentParent.children && currentParent.children.length > indexToRemove) {
5893
5958
  currentParent.children.splice(indexToRemove, 1);
5959
+ setHasUnsavedChanges(true);
5894
5960
  }
5895
5961
  return { ...prev, [treeKey]: newTree };
5896
5962
  });
@@ -5949,6 +6015,7 @@ function CreateAncestryPanel({
5949
6015
  updateGlobalTree(rootTreeClone);
5950
6016
  }
5951
6017
  setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
6018
+ setHasUnsavedChanges(true);
5952
6019
  } else {
5953
6020
  alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5954
6021
  }
@@ -6021,6 +6088,7 @@ function CreateAncestryPanel({
6021
6088
  const handleAddProp = () => {
6022
6089
  const newProp = createNewCustomProperty(customProps);
6023
6090
  setCustomProps((p) => [...p, newProp]);
6091
+ setHasUnsavedChanges(true);
6024
6092
  setTimeout(() => {
6025
6093
  var _a;
6026
6094
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -6029,11 +6097,13 @@ function CreateAncestryPanel({
6029
6097
  const handleRemoveProp = (i) => {
6030
6098
  const newProps = customProps.filter((_, idx) => idx !== i);
6031
6099
  setCustomProps(newProps);
6100
+ setHasUnsavedChanges(true);
6032
6101
  };
6033
6102
  const handleUpdateProp = (index, updatedProp) => {
6034
6103
  const newProps = [...customProps];
6035
6104
  newProps[index] = updatedProp;
6036
6105
  setCustomProps(newProps);
6106
+ setHasUnsavedChanges(true);
6037
6107
  };
6038
6108
  const currentUsedTypes = customProps.map((p) => p.type).filter((t) => UNIQUE_PROP_TYPES.includes(t));
6039
6109
  (0, import_react11.useEffect)(() => {
@@ -6143,6 +6213,7 @@ function CreateAncestryPanel({
6143
6213
  updateGlobalTree(rootTreeClone);
6144
6214
  setBranchStack([...branchStack]);
6145
6215
  setIsPickerOpen(false);
6216
+ setHasUnsavedChanges(true);
6146
6217
  try {
6147
6218
  setIsSaving(true);
6148
6219
  const rootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6156,6 +6227,7 @@ function CreateAncestryPanel({
6156
6227
  rootExtras
6157
6228
  );
6158
6229
  setLastSavedSnapshot(takeSnapshot(rootTreeClone, ancestryName, description, processedSections, [], isPrivate, ancestryMode.abstraction_tree));
6230
+ setHasUnsavedChanges(false);
6159
6231
  if (onRenderFullAncestry) {
6160
6232
  const fullTreePayload = {
6161
6233
  ancestry_id: ancestryMode.currentAncestryId || "temp_root",
@@ -6198,6 +6270,7 @@ function CreateAncestryPanel({
6198
6270
  if (branchIndex !== -1) {
6199
6271
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6200
6272
  updateGlobalTree(rootTreeClone);
6273
+ setHasUnsavedChanges(true);
6201
6274
  try {
6202
6275
  setIsSaving(true);
6203
6276
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6219,6 +6292,7 @@ function CreateAncestryPanel({
6219
6292
  isPrivate,
6220
6293
  ancestryMode.abstraction_tree
6221
6294
  ));
6295
+ setHasUnsavedChanges(false);
6222
6296
  if (onClearAncestryVisuals) {
6223
6297
  onClearAncestryVisuals(currentStep.branchId);
6224
6298
  }
@@ -6251,6 +6325,7 @@ function CreateAncestryPanel({
6251
6325
  if (branchIndex !== -1) {
6252
6326
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6253
6327
  updateGlobalTree(rootTreeClone);
6328
+ setHasUnsavedChanges(true);
6254
6329
  try {
6255
6330
  setIsSaving(true);
6256
6331
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6272,6 +6347,7 @@ function CreateAncestryPanel({
6272
6347
  isPrivate,
6273
6348
  ancestryMode.abstraction_tree
6274
6349
  ));
6350
+ setHasUnsavedChanges(false);
6275
6351
  if (onClearAncestryVisuals) {
6276
6352
  onClearAncestryVisuals(currentStep.branchId);
6277
6353
  }
@@ -6533,6 +6609,7 @@ function CreateAncestryPanel({
6533
6609
  }
6534
6610
  setBranchStack(parentStack);
6535
6611
  setTargetScrollSectionId(targetFocusId);
6612
+ setHasUnsavedChanges(true);
6536
6613
  if (onRenderFullAncestry) {
6537
6614
  const parentStack2 = currentStack;
6538
6615
  const rotation = parentStack2.reduce((acc, step) => {
@@ -6594,7 +6671,6 @@ function CreateAncestryPanel({
6594
6671
  direction,
6595
6672
  tree: {
6596
6673
  node: nodeData,
6597
- node_id: nodeId,
6598
6674
  children: [],
6599
6675
  relationship: {}
6600
6676
  }
@@ -6616,6 +6692,7 @@ function CreateAncestryPanel({
6616
6692
  savedMaxIndex: parentIndexToSave,
6617
6693
  entryDirection: direction
6618
6694
  }]);
6695
+ setHasUnsavedChanges(true);
6619
6696
  if (branch && branch.tree && onRenderFullAncestry) {
6620
6697
  const branchAncestryObj = {
6621
6698
  ancestry_id: branch.id,
@@ -6666,6 +6743,10 @@ function CreateAncestryPanel({
6666
6743
  const currentInputName = overrides.ancestryName !== void 0 ? overrides.ancestryName : ancestryName;
6667
6744
  const currentInputDesc = overrides.description !== void 0 ? overrides.description : description;
6668
6745
  const currentInputSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
6746
+ if (!keepOpen && !hasUnsavedChanges) {
6747
+ onClose();
6748
+ return;
6749
+ }
6669
6750
  if (!currentInputName.trim()) {
6670
6751
  alert("O nome n\xE3o pode estar vazio.");
6671
6752
  return;
@@ -6673,11 +6754,7 @@ function CreateAncestryPanel({
6673
6754
  setIsSaving(true);
6674
6755
  const processedSections = processDescriptionForSave(currentInputDesc, currentInputSections);
6675
6756
  setExistingSections(processedSections);
6676
- const updatedRootTree = applyDescriptionToTree(
6677
- ancestryMode.tree,
6678
- currentInputDesc,
6679
- processedSections
6680
- );
6757
+ const updatedRootTree = JSON.parse(JSON.stringify(ancestryMode.tree));
6681
6758
  const extrasObj = {
6682
6759
  ...toObjectFromCustomProps(customProps.filter((p) => !p.isEditing)),
6683
6760
  is_private: isPrivate
@@ -6719,6 +6796,7 @@ function CreateAncestryPanel({
6719
6796
  isPrivate,
6720
6797
  ancestryMode.abstraction_tree
6721
6798
  ));
6799
+ setHasUnsavedChanges(false);
6722
6800
  if (onRenderFullAncestry) {
6723
6801
  const rotation = branchStack.reduce((acc, step) => {
6724
6802
  return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
@@ -6770,6 +6848,7 @@ function CreateAncestryPanel({
6770
6848
  updatedRootTree,
6771
6849
  extrasObj
6772
6850
  );
6851
+ setHasUnsavedChanges(false);
6773
6852
  setLastSavedSnapshot(takeSnapshot(
6774
6853
  updatedRootTree,
6775
6854
  currentInputName,
@@ -6792,6 +6871,7 @@ function CreateAncestryPanel({
6792
6871
  const newTreeString = JSON.stringify(newRootTree);
6793
6872
  if (currentTreeString !== newTreeString) {
6794
6873
  updateGlobalTree(newRootTree);
6874
+ setHasUnsavedChanges(true);
6795
6875
  }
6796
6876
  }, [description, existingSections]);
6797
6877
  const handleTriggerFullRender = () => {
@@ -6814,6 +6894,7 @@ function CreateAncestryPanel({
6814
6894
  };
6815
6895
  const handleSaveDescriptionInline = (newDesc) => {
6816
6896
  setDescription(newDesc);
6897
+ setHasUnsavedChanges(true);
6817
6898
  handleLocalSave(true, { description: newDesc });
6818
6899
  };
6819
6900
  const swallow = (e) => e.stopPropagation();
@@ -6943,7 +7024,11 @@ function CreateAncestryPanel({
6943
7024
  {
6944
7025
  type: "text",
6945
7026
  value: ancestryName,
6946
- onChange: (e) => setAncestryName(e.target.value),
7027
+ onChange: (e) => {
7028
+ setAncestryName(e.target.value);
7029
+ setHasUnsavedChanges(true);
7030
+ },
7031
+ readOnly: isContextLinked,
6947
7032
  placeholder: "Nome da Ancestralidade",
6948
7033
  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"
6949
7034
  }
@@ -7592,9 +7677,9 @@ function InSceneCreationForm({
7592
7677
  };
7593
7678
  const handleToggleImageMode = () => {
7594
7679
  var _a2, _b;
7595
- const newValue = !useImageAsTexture;
7596
- setUseImageAsTexture(newValue);
7597
- if (newValue) {
7680
+ const newValue2 = !useImageAsTexture;
7681
+ setUseImageAsTexture(newValue2);
7682
+ if (newValue2) {
7598
7683
  const firstImageProp = customProps.find((p) => p.type === "images");
7599
7684
  if (firstImageProp && ((_b = (_a2 = firstImageProp.value) == null ? void 0 : _a2[0]) == null ? void 0 : _b.value)) {
7600
7685
  const url = firstImageProp.value[0].value;
@@ -7892,9 +7977,9 @@ function InSceneVersionForm({
7892
7977
  };
7893
7978
  const handleToggleImageMode = () => {
7894
7979
  var _a, _b;
7895
- const newValue = !useImageAsTexture;
7896
- setUseImageAsTexture(newValue);
7897
- if (newValue) {
7980
+ const newValue2 = !useImageAsTexture;
7981
+ setUseImageAsTexture(newValue2);
7982
+ if (newValue2) {
7898
7983
  const firstImageProp = customProps.find((p) => p.type === "images");
7899
7984
  if (firstImageProp && ((_b = (_a = firstImageProp.value) == null ? void 0 : _a[0]) == null ? void 0 : _b.value)) {
7900
7985
  const url = firstImageProp.value[0].value;
@@ -8314,6 +8399,7 @@ function NodeDetailsPanel({
8314
8399
  return !!(node == null ? void 0 : node.useImageAsTexture);
8315
8400
  });
8316
8401
  const [selectedImageUrl, setSelectedImageUrl] = (0, import_react17.useState)((node == null ? void 0 : node.textureImageUrl) ?? null);
8402
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react17.useState)(false);
8317
8403
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8318
8404
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8319
8405
  initialWidth: isReadMode ? 700 : 440,
@@ -8351,6 +8437,7 @@ function NodeDetailsPanel({
8351
8437
  else if ((node == null ? void 0 : node.useImageAsTexture) === "false") setUseImageAsTexture(false);
8352
8438
  else setUseImageAsTexture(!!(node == null ? void 0 : node.useImageAsTexture));
8353
8439
  setSelectedImageUrl((node == null ? void 0 : node.textureImageUrl) ?? null);
8440
+ setHasUnsavedChanges(false);
8354
8441
  }
8355
8442
  }, [node]);
8356
8443
  const hasImages = customProps.some((p) => p.type === "images" && Array.isArray(p.value) && p.value.length > 0 && p.value.some((img) => img.value));
@@ -8377,6 +8464,7 @@ function NodeDetailsPanel({
8377
8464
  setIntensity(val);
8378
8465
  onIntensityChange == null ? void 0 : onIntensityChange(node.id, val);
8379
8466
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, intensity: val });
8467
+ setHasUnsavedChanges(true);
8380
8468
  };
8381
8469
  const handleCopyLink = () => {
8382
8470
  if (!(node == null ? void 0 : node.id)) return;
@@ -8394,14 +8482,17 @@ function NodeDetailsPanel({
8394
8482
  const v = e.target.value;
8395
8483
  setName(v);
8396
8484
  onNameChange == null ? void 0 : onNameChange(node.id, v);
8485
+ setHasUnsavedChanges(true);
8397
8486
  };
8398
8487
  const handleColorChange = (val) => {
8399
8488
  setColor(val);
8400
8489
  onColorChange == null ? void 0 : onColorChange(node.id, val);
8490
+ setHasUnsavedChanges(true);
8401
8491
  };
8402
8492
  const handleSizeChange = (newSize) => {
8403
8493
  setSize(newSize);
8404
8494
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8495
+ setHasUnsavedChanges(true);
8405
8496
  };
8406
8497
  const handleAddType = (newType) => {
8407
8498
  const trimmed = newType.trim();
@@ -8409,10 +8500,12 @@ function NodeDetailsPanel({
8409
8500
  setTypes([...types, trimmed]);
8410
8501
  setTypeInput("");
8411
8502
  setShowTypeSuggestions(false);
8503
+ setHasUnsavedChanges(true);
8412
8504
  }
8413
8505
  };
8414
8506
  const handleRemoveType = (indexToRemove) => {
8415
8507
  setTypes(types.filter((_, index) => index !== indexToRemove));
8508
+ setHasUnsavedChanges(true);
8416
8509
  };
8417
8510
  const handleTypeInputKeyDown = (e) => {
8418
8511
  if (e.key === "Enter") {
@@ -8425,6 +8518,7 @@ function NodeDetailsPanel({
8425
8518
  const handleAddProp = () => {
8426
8519
  const newProp = createNewCustomProperty(customProps);
8427
8520
  setCustomProps((p) => [...p, newProp]);
8521
+ setHasUnsavedChanges(true);
8428
8522
  setTimeout(() => {
8429
8523
  var _a;
8430
8524
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8433,19 +8527,21 @@ function NodeDetailsPanel({
8433
8527
  const handleRemoveProp = (i) => {
8434
8528
  const newProps = customProps.filter((_, idx) => idx !== i);
8435
8529
  setCustomProps(newProps);
8530
+ setHasUnsavedChanges(true);
8436
8531
  triggerAutoSave({ customProps: newProps });
8437
8532
  };
8438
8533
  const handleUpdateProp = (index, updatedProp) => {
8439
8534
  const newProps = [...customProps];
8440
8535
  newProps[index] = updatedProp;
8441
8536
  setCustomProps(newProps);
8537
+ setHasUnsavedChanges(true);
8442
8538
  if (!updatedProp.isEditing) {
8443
8539
  triggerAutoSave({ customProps: newProps });
8444
8540
  }
8445
8541
  };
8446
8542
  const handleToggleImageMode = () => {
8447
- const newValue = !useImageAsTexture;
8448
8543
  setUseImageAsTexture(newValue);
8544
+ setHasUnsavedChanges(true);
8449
8545
  let activeUrl = null;
8450
8546
  if (newValue) {
8451
8547
  const firstImageProp = customProps.find((p) => p.type === "images");
@@ -8467,6 +8563,7 @@ function NodeDetailsPanel({
8467
8563
  };
8468
8564
  const handleSelectTexture = (url) => {
8469
8565
  setSelectedImageUrl(url);
8566
+ setHasUnsavedChanges(true);
8470
8567
  onImageChange == null ? void 0 : onImageChange(true, url, color);
8471
8568
  onDataUpdate == null ? void 0 : onDataUpdate({
8472
8569
  ...node,
@@ -8477,6 +8574,7 @@ function NodeDetailsPanel({
8477
8574
  };
8478
8575
  const handleSaveDescriptionInline = (newDescription) => {
8479
8576
  setDescription(newDescription);
8577
+ setHasUnsavedChanges(true);
8480
8578
  onDataUpdate({ ...node, description: newDescription });
8481
8579
  triggerAutoSave({ description: newDescription });
8482
8580
  };
@@ -8487,6 +8585,10 @@ function NodeDetailsPanel({
8487
8585
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8488
8586
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8489
8587
  const currentIntensity = overrides.intensity !== void 0 ? overrides.intensity : intensity;
8588
+ if (!keepOpen && !hasUnsavedChanges) {
8589
+ onClose();
8590
+ return;
8591
+ }
8490
8592
  if (!currentName.trim() || currentTypes.length === 0) {
8491
8593
  alert("O campo 'Nome' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8492
8594
  return;
@@ -8511,6 +8613,7 @@ function NodeDetailsPanel({
8511
8613
  };
8512
8614
  await onSave(dataToSave, keepOpen);
8513
8615
  onDataUpdate(dataToSave);
8616
+ setHasUnsavedChanges(false);
8514
8617
  if (!keepOpen) {
8515
8618
  onClose();
8516
8619
  }
@@ -8577,7 +8680,7 @@ function NodeDetailsPanel({
8577
8680
  onImageClick: handleImageClickFromText,
8578
8681
  onSaveDescription: handleSaveDescriptionInline
8579
8682
  }
8580
- ) : /* @__PURE__ */ import_react17.default.createElement(import_react17.default.Fragment, null, /* @__PURE__ */ import_react17.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0" }), /* @__PURE__ */ import_react17.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react17.default.createElement("div", null, /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes do Node"), /* @__PURE__ */ import_react17.default.createElement(
8683
+ ) : /* @__PURE__ */ import_react17.default.createElement(import_react17.default.Fragment, null, /* @__PURE__ */ import_react17.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-indigo-400/0 via-indigo-400/70 to-indigo-400/0" }), /* @__PURE__ */ import_react17.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes do Node"), /* @__PURE__ */ import_react17.default.createElement(
8581
8684
  "button",
8582
8685
  {
8583
8686
  onClick: handleCopyLink,
@@ -8585,7 +8688,7 @@ function NodeDetailsPanel({
8585
8688
  title: isLinkCopied ? "Link Copiado!" : "Copiar link para este Node"
8586
8689
  },
8587
8690
  isLinkCopied ? /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiCheck, { size: 12 }) : /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiLink, { size: 12 })
8588
- )), /* @__PURE__ */ import_react17.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || (node == null ? void 0 : node.name))), /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-xs text-slate-300" }, "Tipos"), /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.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__ */ import_react17.default.createElement(
8691
+ )), /* @__PURE__ */ import_react17.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || (node == null ? void 0 : node.name))), /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-xs text-slate-300" }, "Tipos"), /* @__PURE__ */ import_react17.default.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__ */ import_react17.default.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__ */ import_react17.default.createElement(
8589
8692
  "button",
8590
8693
  {
8591
8694
  type: "button",
@@ -8747,6 +8850,7 @@ function NodeDetailsPanel({
8747
8850
  initialValue: description,
8748
8851
  onSave: (newDescription) => {
8749
8852
  setDescription(newDescription);
8853
+ setHasUnsavedChanges(true);
8750
8854
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
8751
8855
  triggerAutoSave({ description: newDescription });
8752
8856
  },
@@ -8806,6 +8910,7 @@ function QuestDetailsPanel({
8806
8910
  const [existingSections, setExistingSections] = (0, import_react18.useState)((node == null ? void 0 : node.description_sections) || []);
8807
8911
  const [isSaving, setIsSaving] = (0, import_react18.useState)(false);
8808
8912
  const [isLinkCopied, setIsLinkCopied] = (0, import_react18.useState)(false);
8913
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react18.useState)(false);
8809
8914
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8810
8915
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8811
8916
  initialWidth: isReadMode ? 700 : 440,
@@ -8837,6 +8942,7 @@ function QuestDetailsPanel({
8837
8942
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8838
8943
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8839
8944
  setCustomProps(extractCustomPropsFromNode(node || {}));
8945
+ setHasUnsavedChanges(false);
8840
8946
  }
8841
8947
  }, [node]);
8842
8948
  (0, import_react18.useEffect)(() => {
@@ -8868,16 +8974,19 @@ function QuestDetailsPanel({
8868
8974
  setRawTitle(val);
8869
8975
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8870
8976
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8977
+ setHasUnsavedChanges(true);
8871
8978
  };
8872
8979
  const handleSizeChange = (newSize) => {
8873
8980
  setSize(newSize);
8874
8981
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8982
+ setHasUnsavedChanges(true);
8875
8983
  };
8876
8984
  const handleStatusChange = (newStatus) => {
8877
8985
  setStatus(newStatus);
8878
8986
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8879
8987
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8880
8988
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8989
+ setHasUnsavedChanges(true);
8881
8990
  };
8882
8991
  const handleAddType = (newType) => {
8883
8992
  const trimmed = newType.trim();
@@ -8885,11 +8994,13 @@ function QuestDetailsPanel({
8885
8994
  setTypes([...types, trimmed]);
8886
8995
  setTypeInput("");
8887
8996
  setShowTypeSuggestions(false);
8997
+ setHasUnsavedChanges(true);
8888
8998
  }
8889
8999
  };
8890
9000
  const handleRemoveType = (indexToRemove) => {
8891
9001
  if (types[indexToRemove] === "quest") return;
8892
9002
  setTypes(types.filter((_, index) => index !== indexToRemove));
9003
+ setHasUnsavedChanges(true);
8893
9004
  };
8894
9005
  const handleTypeInputKeyDown = (e) => {
8895
9006
  if (e.key === "Enter") {
@@ -8902,6 +9013,7 @@ function QuestDetailsPanel({
8902
9013
  const handleAddProp = () => {
8903
9014
  const newProp = createNewCustomProperty(customProps);
8904
9015
  setCustomProps((p) => [...p, newProp]);
9016
+ setHasUnsavedChanges(true);
8905
9017
  setTimeout(() => {
8906
9018
  var _a2;
8907
9019
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8910,18 +9022,21 @@ function QuestDetailsPanel({
8910
9022
  const handleRemoveProp = (i) => {
8911
9023
  const newProps = customProps.filter((_, idx) => idx !== i);
8912
9024
  setCustomProps(newProps);
9025
+ setHasUnsavedChanges(true);
8913
9026
  triggerAutoSave({ customProps: newProps });
8914
9027
  };
8915
9028
  const handleUpdateProp = (index, updatedProp) => {
8916
9029
  const newProps = [...customProps];
8917
9030
  newProps[index] = updatedProp;
8918
9031
  setCustomProps(newProps);
9032
+ setHasUnsavedChanges(true);
8919
9033
  if (!updatedProp.isEditing) {
8920
9034
  triggerAutoSave({ customProps: newProps });
8921
9035
  }
8922
9036
  };
8923
9037
  const handleSaveDescriptionInline = (newDescription) => {
8924
9038
  setDescription(newDescription);
9039
+ setHasUnsavedChanges(true);
8925
9040
  onDataUpdate({ ...node, description: newDescription });
8926
9041
  triggerAutoSave({ description: newDescription });
8927
9042
  };
@@ -8933,6 +9048,10 @@ function QuestDetailsPanel({
8933
9048
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8934
9049
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8935
9050
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9051
+ if (!keepOpen && !hasUnsavedChanges) {
9052
+ onClose();
9053
+ return;
9054
+ }
8936
9055
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8937
9056
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8938
9057
  return;
@@ -8962,6 +9081,7 @@ function QuestDetailsPanel({
8962
9081
  };
8963
9082
  await onSave(dataToSave, keepOpen);
8964
9083
  onDataUpdate(dataToSave);
9084
+ setHasUnsavedChanges(false);
8965
9085
  if (!keepOpen) {
8966
9086
  onClose();
8967
9087
  }
@@ -9019,7 +9139,7 @@ function QuestDetailsPanel({
9019
9139
  onImageClick: handleImageClickFromText,
9020
9140
  onSaveDescription: handleSaveDescriptionInline
9021
9141
  }
9022
- ) : /* @__PURE__ */ import_react18.default.createElement(import_react18.default.Fragment, null, /* @__PURE__ */ import_react18.default.createElement("div", { className: "h-[2px]", style: { background: `linear-gradient(to right, transparent, ${QUEST_STATUS_COLORS3[status]}, transparent)` } }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react18.default.createElement("div", null, /* @__PURE__ */ import_react18.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react18.default.createElement(import_fi16.FiTarget, { className: "text-sky-400", size: 14 }), /* @__PURE__ */ import_react18.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Quest"), /* @__PURE__ */ import_react18.default.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__ */ import_react18.default.createElement(import_fi16.FiCheck, { size: 12 }) : /* @__PURE__ */ import_react18.default.createElement(import_fi16.FiLink, { size: 12 }))), /* @__PURE__ */ import_react18.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, standardizedName || (node == null ? void 0 : node.name))), /* @__PURE__ */ import_react18.default.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__ */ import_react18.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react18.default.createElement("label", { className: "text-xs text-slate-300" }, "T\xEDtulo da Quest"), /* @__PURE__ */ import_react18.default.createElement(
9142
+ ) : /* @__PURE__ */ import_react18.default.createElement(import_react18.default.Fragment, null, /* @__PURE__ */ import_react18.default.createElement("div", { className: "h-[2px]", style: { background: `linear-gradient(to right, transparent, ${QUEST_STATUS_COLORS3[status]}, transparent)` } }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react18.default.createElement(import_fi16.FiTarget, { className: "text-sky-400", size: 14 }), /* @__PURE__ */ import_react18.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Quest"), /* @__PURE__ */ import_react18.default.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__ */ import_react18.default.createElement(import_fi16.FiCheck, { size: 12 }) : /* @__PURE__ */ import_react18.default.createElement(import_fi16.FiLink, { size: 12 }))), /* @__PURE__ */ import_react18.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, standardizedName || (node == null ? void 0 : node.name))), /* @__PURE__ */ import_react18.default.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__ */ import_react18.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react18.default.createElement("label", { className: "text-xs text-slate-300" }, "T\xEDtulo da Quest"), /* @__PURE__ */ import_react18.default.createElement(
9023
9143
  "input",
9024
9144
  {
9025
9145
  type: "text",
@@ -9193,9 +9313,12 @@ function RelationshipDetailsPanel({
9193
9313
  const [description, setDescription] = (0, import_react20.useState)((link == null ? void 0 : link.description) ?? "");
9194
9314
  const [customProps, setCustomProps] = (0, import_react20.useState)(() => extractCustomPropsFromNode(link || {}));
9195
9315
  const [existingSections, setExistingSections] = (0, import_react20.useState)((link == null ? void 0 : link.description_sections) || []);
9316
+ const [sourceLabel, setSourceLabel] = (0, import_react20.useState)((link == null ? void 0 : link.source_label) ?? "");
9317
+ const [targetLabel, setTargetLabel] = (0, import_react20.useState)((link == null ? void 0 : link.target_label) ?? "");
9196
9318
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react20.useState)(false);
9197
9319
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9198
9320
  const [isReadMode, setIsReadMode] = (0, import_react20.useState)(false);
9321
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react20.useState)(false);
9199
9322
  const propsEndRef = (0, import_react20.useRef)(null);
9200
9323
  const canEdit = (0, import_react20.useMemo)(() => {
9201
9324
  const ability = defineAbilityFor(userRole);
@@ -9206,12 +9329,16 @@ function RelationshipDetailsPanel({
9206
9329
  setDescription((link == null ? void 0 : link.description) ?? "");
9207
9330
  setExistingSections((link == null ? void 0 : link.description_sections) || []);
9208
9331
  setCustomProps(extractCustomPropsFromNode(link || {}));
9332
+ setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9333
+ setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9334
+ setHasUnsavedChanges(false);
9209
9335
  }, [link]);
9210
9336
  const swallow = (e) => e.stopPropagation();
9211
9337
  const handleAddProp = () => {
9212
9338
  if (!canEdit) return;
9213
9339
  const newProp = createNewCustomProperty(customProps);
9214
9340
  setCustomProps((p) => [...p, newProp]);
9341
+ setHasUnsavedChanges(true);
9215
9342
  setTimeout(() => {
9216
9343
  var _a;
9217
9344
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9223,6 +9350,12 @@ function RelationshipDetailsPanel({
9223
9350
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
9224
9351
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
9225
9352
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9353
+ const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9354
+ const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9355
+ if (!keepOpen && !hasUnsavedChanges) {
9356
+ onClose();
9357
+ return;
9358
+ }
9226
9359
  setIsSaving(true);
9227
9360
  try {
9228
9361
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9238,8 +9371,11 @@ function RelationshipDetailsPanel({
9238
9371
  isCurved: link.isCurved,
9239
9372
  curveOffset: link.curveOffset
9240
9373
  };
9374
+ if (currentSourceLabel.trim()) dataToSave.source_label = currentSourceLabel.trim();
9375
+ if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9241
9376
  await onSave(dataToSave, keepOpen);
9242
9377
  onDataUpdate(dataToSave);
9378
+ setHasUnsavedChanges(false);
9243
9379
  if (!keepOpen) {
9244
9380
  onClose();
9245
9381
  }
@@ -9253,18 +9389,21 @@ function RelationshipDetailsPanel({
9253
9389
  const handleSaveDescriptionInline = (newDescription) => {
9254
9390
  if (!canEdit) return;
9255
9391
  setDescription(newDescription);
9392
+ setHasUnsavedChanges(true);
9256
9393
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9257
9394
  triggerAutoSave({ description: newDescription });
9258
9395
  };
9259
9396
  const handleRemoveProp = (i) => {
9260
9397
  const newProps = customProps.filter((_, idx) => idx !== i);
9261
9398
  setCustomProps(newProps);
9399
+ setHasUnsavedChanges(true);
9262
9400
  triggerAutoSave({ customProps: newProps });
9263
9401
  };
9264
9402
  const handleUpdateProp = (index, updatedProp) => {
9265
9403
  const newProps = [...customProps];
9266
9404
  newProps[index] = updatedProp;
9267
9405
  setCustomProps(newProps);
9406
+ setHasUnsavedChanges(true);
9268
9407
  if (!updatedProp.isEditing) {
9269
9408
  triggerAutoSave({ customProps: newProps });
9270
9409
  }
@@ -9312,19 +9451,52 @@ function RelationshipDetailsPanel({
9312
9451
  onImageClick: handleImageClickFromText,
9313
9452
  onSaveDescription: handleSaveDescriptionInline
9314
9453
  }
9315
- ) : /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, /* @__PURE__ */ import_react20.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-teal-400/0 via-teal-400/70 to-teal-400/0" }), /* @__PURE__ */ import_react20.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react20.default.createElement("div", null, /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o")), /* @__PURE__ */ import_react20.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || "Rela\xE7\xE3o")), /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Rela\xE7\xE3o (Opcional)"), /* @__PURE__ */ import_react20.default.createElement(
9454
+ ) : /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, /* @__PURE__ */ import_react20.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-teal-400/0 via-teal-400/70 to-teal-400/0" }), /* @__PURE__ */ import_react20.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Rela\xE7\xE3o")), /* @__PURE__ */ import_react20.default.createElement("h2", { className: "text-xl sm:text-2xl font-semibold tracking-tight" }, name || "Rela\xE7\xE3o")), /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement("div", { className: "px-6 pb-28 overflow-y-auto overscroll-contain space-y-4 max-h-[68vh] custom-scrollbar" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Rela\xE7\xE3o (Opcional)"), /* @__PURE__ */ import_react20.default.createElement(
9316
9455
  "input",
9317
9456
  {
9318
9457
  type: "text",
9319
9458
  value: name,
9320
- onChange: (e) => setName(e.target.value),
9459
+ onChange: (e) => {
9460
+ setName(e.target.value);
9461
+ setHasUnsavedChanges(true);
9462
+ },
9321
9463
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9322
9464
  disabled: !canEdit,
9323
9465
  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
9324
9466
  ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9325
9467
  `
9326
9468
  }
9327
- )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement(
9469
+ )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rounded-xl border border-white/8 bg-slate-800/30 p-3 space-y-3" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.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__ */ import_react20.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ import_react20.default.createElement("p", { className: "text-xs font-medium text-violet-300/80 uppercase tracking-wider" }, "Labels de Agrupamento")), /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Source"), /* @__PURE__ */ import_react20.default.createElement(
9470
+ "input",
9471
+ {
9472
+ type: "text",
9473
+ value: sourceLabel,
9474
+ onChange: (e) => {
9475
+ setSourceLabel(e.target.value);
9476
+ setHasUnsavedChanges(true);
9477
+ },
9478
+ placeholder: "Ex: Conceitos",
9479
+ disabled: !canEdit,
9480
+ 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
9481
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9482
+ `
9483
+ }
9484
+ )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Target"), /* @__PURE__ */ import_react20.default.createElement(
9485
+ "input",
9486
+ {
9487
+ type: "text",
9488
+ value: targetLabel,
9489
+ onChange: (e) => {
9490
+ setTargetLabel(e.target.value);
9491
+ setHasUnsavedChanges(true);
9492
+ },
9493
+ placeholder: "Ex: Refer\xEAncias",
9494
+ disabled: !canEdit,
9495
+ 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
9496
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9497
+ `
9498
+ }
9499
+ )))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react20.default.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__ */ import_react20.default.createElement(
9328
9500
  DescriptionDisplay,
9329
9501
  {
9330
9502
  description,
@@ -9394,6 +9566,7 @@ function RelationshipDetailsPanel({
9394
9566
  onSave: (newDescription) => {
9395
9567
  if (!canEdit) return;
9396
9568
  setDescription(newDescription);
9569
+ setHasUnsavedChanges(true);
9397
9570
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9398
9571
  triggerAutoSave({ description: newDescription });
9399
9572
  },
@@ -9841,7 +10014,7 @@ function AncestryLinkDetailsPanel({ data, onClose, onOpenImageViewer, onOpenRefe
9841
10014
  onMentionClick,
9842
10015
  onImageClick: handleImageClickFromText
9843
10016
  }
9844
- ) : /* @__PURE__ */ import_react24.default.createElement(import_react24.default.Fragment, null, /* @__PURE__ */ import_react24.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-blue-500/0 via-blue-500/70 to-blue-500/0" }), /* @__PURE__ */ import_react24.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react24.default.createElement("div", null, /* @__PURE__ */ import_react24.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react24.default.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__ */ import_react24.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Ancestralidade")), /* @__PURE__ */ import_react24.default.createElement("h2", { className: "text-lg font-semibold tracking-tight flex items-center gap-2" }, /* @__PURE__ */ import_react24.default.createElement("span", { className: "truncate max-w-[150px]" }, sourceName), /* @__PURE__ */ import_react24.default.createElement("span", { className: "text-slate-500 text-sm" }, "\u2794"), /* @__PURE__ */ import_react24.default.createElement("span", { className: "truncate max-w-[150px]" }, targetName))), /* @__PURE__ */ import_react24.default.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__ */ import_react24.default.createElement("div", { className: "px-6 pb-6 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, description && /* @__PURE__ */ import_react24.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react24.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react24.default.createElement("label", { className: "text-xs text-slate-300 font-medium" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react24.default.createElement(
10017
+ ) : /* @__PURE__ */ import_react24.default.createElement(import_react24.default.Fragment, null, /* @__PURE__ */ import_react24.default.createElement("div", { className: "h-[2px] bg-gradient-to-r from-blue-500/0 via-blue-500/70 to-blue-500/0" }), /* @__PURE__ */ import_react24.default.createElement("div", { className: "px-6 pt-5 pb-3 flex items-start justify-between gap-4" }, /* @__PURE__ */ import_react24.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ import_react24.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ import_react24.default.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__ */ import_react24.default.createElement("p", { className: "text-xs/relaxed text-slate-300" }, "Detalhes da Ancestralidade")), /* @__PURE__ */ import_react24.default.createElement("h2", { className: "text-lg font-semibold tracking-tight flex items-center gap-2" }, /* @__PURE__ */ import_react24.default.createElement("span", { className: "truncate max-w-[150px]" }, sourceName), /* @__PURE__ */ import_react24.default.createElement("span", { className: "text-slate-500 text-sm" }, "\u2794"), /* @__PURE__ */ import_react24.default.createElement("span", { className: "truncate max-w-[150px]" }, targetName))), /* @__PURE__ */ import_react24.default.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__ */ import_react24.default.createElement("div", { className: "px-6 pb-6 overflow-y-auto overscroll-contain space-y-4 custom-scrollbar" }, description && /* @__PURE__ */ import_react24.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react24.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react24.default.createElement("label", { className: "text-xs text-slate-300 font-medium" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react24.default.createElement(
9845
10018
  "button",
9846
10019
  {
9847
10020
  onClick: () => setIsReadMode(true),
@@ -9890,7 +10063,6 @@ var GroupItem = ({
9890
10063
  onPlayAncestry,
9891
10064
  availableIds,
9892
10065
  canEdit
9893
- // [NOVO] Recebe permissão de edição
9894
10066
  }) => {
9895
10067
  const canIndent = index > 0;
9896
10068
  const isPickingForThisGroup = pickingGroupId === group.id;
@@ -9905,107 +10077,126 @@ var GroupItem = ({
9905
10077
  (0, import_react25.useEffect)(() => {
9906
10078
  adjustHeight();
9907
10079
  }, [group.text]);
9908
- return /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ import_react25.default.createElement("div", { className: `
10080
+ return /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ import_react25.default.createElement(
10081
+ "div",
10082
+ {
10083
+ className: `
9909
10084
  flex flex-col gap-2 py-2 px-3 transition-all duration-200
9910
10085
  ${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__ */ import_react25.default.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__ */ import_react25.default.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__ */ import_react25.default.createElement(
9931
- "div",
10086
+ `
10087
+ },
10088
+ /* @__PURE__ */ import_react25.default.createElement(
10089
+ "textarea",
9932
10090
  {
9933
- key: anc.ancestry_id,
9934
- className: `
10091
+ ref: textareaRef,
10092
+ 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" : ""}`,
10093
+ rows: 1,
10094
+ style: {
10095
+ minHeight: "1.5rem",
10096
+ maxHeight: "250px"
10097
+ },
10098
+ placeholder: canEdit ? "Escreva sobre este grupo..." : "",
10099
+ value: group.text,
10100
+ readOnly: !canEdit,
10101
+ onChange: (e) => {
10102
+ if (canEdit) onUpdate(group.id, { ...group, text: e.target.value });
10103
+ }
10104
+ }
10105
+ ),
10106
+ group.ancestries && group.ancestries.length > 0 && /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex flex-wrap gap-2 mt-1" }, group.ancestries.map((anc) => {
10107
+ const isValid = availableIds.has(String(anc.ancestry_id));
10108
+ return /* @__PURE__ */ import_react25.default.createElement(
10109
+ "div",
10110
+ {
10111
+ key: anc.ancestry_id,
10112
+ className: `
9935
10113
  flex items-center gap-2 rounded-md px-3 py-1.5 text-xs transition-all group/card border
9936
10114
  ${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
10115
  `,
9938
- title: isValid ? "" : "Esta ancestralidade foi removida ou n\xE3o existe mais."
9939
- },
9940
- isValid ? (
9941
- // [MANTIDO] Botão Play visível para todos
10116
+ title: isValid ? "" : "Esta ancestralidade foi removida ou n\xE3o existe mais."
10117
+ },
10118
+ isValid ? (
10119
+ // [MANTIDO] Botão Play visível para todos
10120
+ /* @__PURE__ */ import_react25.default.createElement(
10121
+ "button",
10122
+ {
10123
+ onClick: () => onPlayAncestry(anc.ancestry_id),
10124
+ className: "text-indigo-400 hover:text-white hover:bg-indigo-500 p-1 rounded-full transition-colors",
10125
+ title: "Renderizar no cen\xE1rio"
10126
+ },
10127
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlay, { size: 10, className: "ml-0.5 fill-current" })
10128
+ )
10129
+ ) : /* @__PURE__ */ import_react25.default.createElement("div", { className: "p-1 text-red-500 cursor-not-allowed" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiAlertTriangle, { size: 10 })),
9942
10130
  /* @__PURE__ */ import_react25.default.createElement(
10131
+ "span",
10132
+ {
10133
+ className: `font-medium truncate max-w-[150px] ${!isValid && "line-through decoration-red-500/50"}`
10134
+ },
10135
+ anc.name
10136
+ ),
10137
+ canEdit && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(
10138
+ "div",
10139
+ {
10140
+ className: `w-px h-3 mx-0.5 ${isValid ? "bg-white/10" : "bg-red-500/20"}`
10141
+ }
10142
+ ), /* @__PURE__ */ import_react25.default.createElement(
9943
10143
  "button",
9944
10144
  {
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"
10145
+ onClick: () => onRemoveAncestry(group.id, anc.ancestry_id),
10146
+ className: `${isValid ? "text-slate-500 hover:text-red-400" : "text-red-400 hover:text-red-200"} p-0.5 rounded transition-colors`,
10147
+ title: "Remover men\xE7\xE3o"
9948
10148
  },
9949
- /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlay, { size: 10, className: "ml-0.5 fill-current" })
9950
- )
9951
- ) : /* @__PURE__ */ import_react25.default.createElement("div", { className: "p-1 text-red-500 cursor-not-allowed" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiAlertTriangle, { size: 10 })),
9952
- /* @__PURE__ */ import_react25.default.createElement("span", { className: `font-medium truncate max-w-[150px] ${!isValid && "line-through decoration-red-500/50"}` }, anc.name),
9953
- canEdit && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement("div", { className: `w-px h-3 mx-0.5 ${isValid ? "bg-white/10" : "bg-red-500/20"}` }), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiX, { size: 12 })
9961
- ))
9962
- );
9963
- })), canEdit && /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react25.default.createElement(
9964
- "button",
9965
- {
9966
- onClick: () => onRequestPickAncestry(group.id),
9967
- className: `
10149
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiX, { size: 12 })
10150
+ ))
10151
+ );
10152
+ })),
10153
+ canEdit && /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react25.default.createElement(
10154
+ "button",
10155
+ {
10156
+ onClick: () => onRequestPickAncestry(group.id),
10157
+ className: `
9968
10158
  flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors
9969
10159
  ${isPickingForThisGroup ? "text-indigo-300 animate-pulse" : "text-slate-500 hover:text-indigo-300 hover:bg-indigo-500/10"}
9970
10160
  `,
9971
- title: "Adicionar Ancestralidade a este grupo"
9972
- },
9973
- isPickingForThisGroup ? /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiCheckCircle, { size: 12 }) : /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiSearch, { size: 12 }),
9974
- isPickingForThisGroup ? "Selecionando..." : "Adicionar"
9975
- ), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiPlus, { size: 14 })
9983
- )), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiArrowRight, { size: 14 })
9992
- ), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiArrowLeft, { size: 14 })
10000
- ), /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiTrash2, { size: 14 })
10008
- )))), group.children && group.children.length > 0 && /* @__PURE__ */ import_react25.default.createElement("div", { className: "ml-2" }, group.children.map((childGroup, idx) => /* @__PURE__ */ import_react25.default.createElement(
10161
+ title: "Adicionar Ancestralidade a este grupo"
10162
+ },
10163
+ isPickingForThisGroup ? /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiCheckCircle, { size: 12 }) : /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiSearch, { size: 12 }),
10164
+ isPickingForThisGroup ? "Selecionando..." : "Adicionar"
10165
+ ), /* @__PURE__ */ import_react25.default.createElement(
10166
+ "button",
10167
+ {
10168
+ onClick: () => onAddSubgroup(group.id),
10169
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
10170
+ title: "Criar Subgrupo"
10171
+ },
10172
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlus, { size: 14 })
10173
+ )), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react25.default.createElement(
10174
+ "button",
10175
+ {
10176
+ onClick: () => onIndent(group.id),
10177
+ disabled: !canIndent,
10178
+ className: `p-1.5 rounded transition-colors ${!canIndent ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`,
10179
+ title: "Aninhar no grupo acima"
10180
+ },
10181
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiArrowRight, { size: 14 })
10182
+ ), /* @__PURE__ */ import_react25.default.createElement(
10183
+ "button",
10184
+ {
10185
+ onClick: () => onOutdent(group.id),
10186
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
10187
+ title: "Desaninhar"
10188
+ },
10189
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiArrowLeft, { size: 14 })
10190
+ ), /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ import_react25.default.createElement(
10191
+ "button",
10192
+ {
10193
+ onClick: () => onDelete(group.id),
10194
+ className: "p-1.5 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors",
10195
+ title: "Remover Grupo"
10196
+ },
10197
+ /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiTrash2, { size: 14 })
10198
+ )))
10199
+ ), group.children && group.children.length > 0 && /* @__PURE__ */ import_react25.default.createElement("div", { className: "ml-2" }, group.children.map((childGroup, idx) => /* @__PURE__ */ import_react25.default.createElement(
10009
10200
  GroupItem,
10010
10201
  {
10011
10202
  key: childGroup.id,
@@ -10214,7 +10405,9 @@ function AncestryBoard({
10214
10405
  const addRecursive = (list) => {
10215
10406
  return list.map((g) => {
10216
10407
  if (g.id === pickingGroupId) {
10217
- const exists = g.ancestries.some((a) => a.ancestry_id === ancestry.ancestry_id);
10408
+ const exists = g.ancestries.some(
10409
+ (a) => a.ancestry_id === ancestry.ancestry_id
10410
+ );
10218
10411
  if (exists) return g;
10219
10412
  return { ...g, ancestries: [...g.ancestries, ancestry] };
10220
10413
  }
@@ -10225,12 +10418,16 @@ function AncestryBoard({
10225
10418
  });
10226
10419
  setPickingGroupId(null);
10227
10420
  } else {
10228
- const fullAncestry = availableAncestries.find((a) => a.ancestry_id === ancestry.ancestry_id) || ancestry;
10421
+ const fullAncestry = availableAncestries.find(
10422
+ (a) => a.ancestry_id === ancestry.ancestry_id
10423
+ ) || ancestry;
10229
10424
  onSelect(fullAncestry);
10230
10425
  }
10231
10426
  };
10232
10427
  const handlePlayFromGroup = (ancestryId) => {
10233
- const fullAncestry = availableAncestries.find((a) => a.ancestry_id === ancestryId);
10428
+ const fullAncestry = availableAncestries.find(
10429
+ (a) => a.ancestry_id === ancestryId
10430
+ );
10234
10431
  if (fullAncestry && onSelect) {
10235
10432
  onSelect(fullAncestry);
10236
10433
  }
@@ -10240,7 +10437,12 @@ function AncestryBoard({
10240
10437
  const removeRecursive = (list) => {
10241
10438
  return list.map((g) => {
10242
10439
  if (g.id === groupId) {
10243
- return { ...g, ancestries: g.ancestries.filter((a) => a.ancestry_id !== ancestryId) };
10440
+ return {
10441
+ ...g,
10442
+ ancestries: g.ancestries.filter(
10443
+ (a) => a.ancestry_id !== ancestryId
10444
+ )
10445
+ };
10244
10446
  }
10245
10447
  return { ...g, children: removeRecursive(g.children) };
10246
10448
  });
@@ -10261,7 +10463,13 @@ function AncestryBoard({
10261
10463
  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
10464
  onClick: (e) => e.stopPropagation()
10263
10465
  },
10264
- /* @__PURE__ */ import_react25.default.createElement("div", { className: "h-14 px-4 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ import_react25.default.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiLayers, { className: "text-indigo-400" }), "Ancestry Board"), saveStatus !== "idle" && /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiLoader, { className: "animate-spin text-indigo-400", size: 12 }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-3" }, pickingGroupId && /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-xs text-indigo-300 font-medium animate-pulse hidden sm:inline-block mr-2" }, "Selecione na lateral..."), canEdit && /* @__PURE__ */ import_react25.default.createElement(
10466
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "h-14 px-4 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ import_react25.default.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiLayers, { className: "text-indigo-400" }), "Ancestry Board"), saveStatus !== "idle" && /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(
10467
+ import_fi19.FiLoader,
10468
+ {
10469
+ className: "animate-spin text-indigo-400",
10470
+ size: 12
10471
+ }
10472
+ ), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center gap-3" }, pickingGroupId && /* @__PURE__ */ import_react25.default.createElement("span", { className: "text-xs text-indigo-300 font-medium animate-pulse hidden sm:inline-block mr-2" }, "Selecione na lateral..."), canEdit && /* @__PURE__ */ import_react25.default.createElement(
10265
10473
  "button",
10266
10474
  {
10267
10475
  onClick: handleAddRootGroup,
@@ -10277,57 +10485,75 @@ function AncestryBoard({
10277
10485
  },
10278
10486
  "\xD7"
10279
10487
  ))),
10280
- /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: `
10488
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react25.default.createElement(
10489
+ "div",
10490
+ {
10491
+ className: `
10281
10492
  flex flex-col border-r border-white/10 transition-all duration-300 flex-none
10282
10493
  ${pickingGroupId ? "w-[25%] border-indigo-500/30" : "w-[20%]"}
10283
10494
  min-w-[280px] max-w-[500px] bg-slate-900
10284
- ` }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "p-3 border-b border-white/5 bg-slate-900/50" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "relative group" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiSearch, { 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__ */ import_react25.default.createElement(
10285
- "input",
10286
- {
10287
- type: "text",
10288
- placeholder: pickingGroupId ? "Pesquise para adicionar..." : "Pesquisar ancestralidade...",
10289
- className: `
10495
+ `
10496
+ },
10497
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "p-3 border-b border-white/5 bg-slate-900/50" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "relative group" }, /* @__PURE__ */ import_react25.default.createElement(
10498
+ import_fi19.FiSearch,
10499
+ {
10500
+ 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"}`
10501
+ }
10502
+ ), /* @__PURE__ */ import_react25.default.createElement(
10503
+ "input",
10504
+ {
10505
+ type: "text",
10506
+ placeholder: pickingGroupId ? "Pesquise para adicionar..." : "Pesquisar ancestralidade...",
10507
+ className: `
10290
10508
  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
10509
  ${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
10510
  `,
10293
- value: searchTerm,
10294
- onChange: (e) => setSearchTerm(e.target.value),
10295
- autoFocus: !pickingGroupId
10296
- }
10297
- ))), /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(
10301
- "div",
10302
- {
10303
- key: anc.ancestry_id,
10304
- onClick: () => {
10305
- if (isPicking) handleSelectAncestry(anc);
10306
- },
10307
- className: `
10511
+ value: searchTerm,
10512
+ onChange: (e) => setSearchTerm(e.target.value),
10513
+ autoFocus: !pickingGroupId
10514
+ }
10515
+ ))),
10516
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2" }, filtered.map((anc) => {
10517
+ const parentNodeName = nodeNamesMap.get(String(anc.ancestral_node)) || "Node Desconhecido";
10518
+ const isPicking = !!pickingGroupId;
10519
+ return /* @__PURE__ */ import_react25.default.createElement(
10520
+ "div",
10521
+ {
10522
+ key: anc.ancestry_id,
10523
+ onClick: () => {
10524
+ if (isPicking) handleSelectAncestry(anc);
10525
+ },
10526
+ className: `
10308
10527
  group relative flex items-start gap-3 p-3 text-left rounded-lg border transition-all duration-200
10309
10528
  ${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
10529
  `
10311
- },
10312
- /* @__PURE__ */ import_react25.default.createElement("div", { className: `
10530
+ },
10531
+ /* @__PURE__ */ import_react25.default.createElement(
10532
+ "div",
10533
+ {
10534
+ className: `
10313
10535
  mt-0.5 w-8 h-8 rounded-md grid place-content-center shrink-0 border transition-all shadow-lg
10314
10536
  ${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__ */ import_react25.default.createElement(import_fi19.FiPlus, { size: 16 }) : /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiLayers, { size: 14 })),
10316
- /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex-1 min-w-0 pb-2" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center justify-between gap-2" }, /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.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__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiCornerUpRight, { size: 10 }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "truncate max-w-[120px]" }, parentNodeName)), anc.description && /* @__PURE__ */ import_react25.default.createElement("p", { className: "mt-1.5 text-[11px] text-slate-400 line-clamp-2 leading-relaxed opacity-80" }, anc.description)),
10317
- !isPicking && /* @__PURE__ */ import_react25.default.createElement(
10318
- "button",
10319
- {
10320
- onClick: (e) => {
10321
- e.stopPropagation();
10322
- handleSelectAncestry(anc);
10537
+ `
10323
10538
  },
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__ */ import_react25.default.createElement("div", { className: "bg-indigo-500 text-white p-2 rounded-full shadow-lg hover:bg-indigo-400 hover:scale-110 transition-all" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlay, { size: 14, className: "ml-0.5" }))
10328
- )
10329
- );
10330
- }))), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex flex-col flex-1 bg-slate-950/30" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4" }, groups.length === 0 ? /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiLayers, { size: 24, className: "opacity-20" }), /* @__PURE__ */ import_react25.default.createElement("p", { className: "text-xs text-center px-4" }, canEdit ? /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, "Nenhum grupo criado.", /* @__PURE__ */ import_react25.default.createElement("br", null), 'Use o bot\xE3o "Novo Grupo" acima.') : /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, "Nenhum grupo dispon\xEDvel para visualiza\xE7\xE3o."))) : groups.map((group, index) => /* @__PURE__ */ import_react25.default.createElement(
10539
+ isPicking ? /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlus, { size: 16 }) : /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiLayers, { size: 14 })
10540
+ ),
10541
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex-1 min-w-0 pb-2" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center justify-between gap-2" }, /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.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__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiCornerUpRight, { size: 10 }), /* @__PURE__ */ import_react25.default.createElement("span", { className: "truncate max-w-[120px]" }, parentNodeName)), anc.description && /* @__PURE__ */ import_react25.default.createElement("p", { className: "mt-1.5 text-[11px] text-slate-400 line-clamp-2 leading-relaxed opacity-80" }, anc.description)),
10542
+ !isPicking && /* @__PURE__ */ import_react25.default.createElement(
10543
+ "button",
10544
+ {
10545
+ onClick: (e) => {
10546
+ e.stopPropagation();
10547
+ handleSelectAncestry(anc);
10548
+ },
10549
+ 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",
10550
+ title: "Renderizar Ancestralidade"
10551
+ },
10552
+ /* @__PURE__ */ import_react25.default.createElement("div", { className: "bg-indigo-500 text-white p-2 rounded-full shadow-lg hover:bg-indigo-400 hover:scale-110 transition-all" }, /* @__PURE__ */ import_react25.default.createElement(import_fi19.FiPlay, { size: 14, className: "ml-0.5" }))
10553
+ )
10554
+ );
10555
+ }))
10556
+ ), /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex flex-col flex-1 bg-slate-950/30" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4" }, groups.length === 0 ? /* @__PURE__ */ import_react25.default.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__ */ import_react25.default.createElement(import_fi19.FiLayers, { size: 24, className: "opacity-20" }), /* @__PURE__ */ import_react25.default.createElement("p", { className: "text-xs text-center px-4" }, canEdit ? /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, "Nenhum grupo criado.", /* @__PURE__ */ import_react25.default.createElement("br", null), 'Use o bot\xE3o "Novo Grupo" acima.') : /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, "Nenhum grupo dispon\xEDvel para visualiza\xE7\xE3o."))) : groups.map((group, index) => /* @__PURE__ */ import_react25.default.createElement(
10331
10557
  GroupItem,
10332
10558
  {
10333
10559
  key: group.id,
@@ -10406,7 +10632,10 @@ var findNodePath3 = (tree, targetNodeId, currentPath = []) => {
10406
10632
  }
10407
10633
  if (tree.children) {
10408
10634
  for (let i = 0; i < tree.children.length; i++) {
10409
- const res = findNodePath3(tree.children[i], targetNodeId, [...currentPath, i]);
10635
+ const res = findNodePath3(tree.children[i], targetNodeId, [
10636
+ ...currentPath,
10637
+ i
10638
+ ]);
10410
10639
  if (res) return res;
10411
10640
  }
10412
10641
  }
@@ -10499,29 +10728,73 @@ function XViewScene({
10499
10728
  const [userPermissionRole, setUserPermissionRole] = (0, import_react26.useState)(null);
10500
10729
  const [isInitialized, setIsInitialized] = (0, import_react26.useState)(false);
10501
10730
  const [sceneVersion, setSceneVersion] = (0, import_react26.useState)(0);
10502
- const [contextMenu, setContextMenu] = (0, import_react26.useState)({ visible: false, x: 0, y: 0, nodeData: null });
10503
- const [multiContextMenu, setMultiContextMenu] = (0, import_react26.useState)({ visible: false, x: 0, y: 0, nodeIds: null });
10504
- const [relationshipMenu, setRelationshipMenu] = (0, import_react26.useState)({ visible: false, x: 0, y: 0, linkObject: null });
10505
- const [creationMode, setCreationMode] = (0, import_react26.useState)({ isActive: false, sourceNodeData: null });
10506
- const [versionMode, setVersionMode] = (0, import_react26.useState)({ isActive: false, sourceNodeData: null });
10731
+ const [contextMenu, setContextMenu] = (0, import_react26.useState)({
10732
+ visible: false,
10733
+ x: 0,
10734
+ y: 0,
10735
+ nodeData: null
10736
+ });
10737
+ const [multiContextMenu, setMultiContextMenu] = (0, import_react26.useState)({
10738
+ visible: false,
10739
+ x: 0,
10740
+ y: 0,
10741
+ nodeIds: null
10742
+ });
10743
+ const [relationshipMenu, setRelationshipMenu] = (0, import_react26.useState)({
10744
+ visible: false,
10745
+ x: 0,
10746
+ y: 0,
10747
+ linkObject: null
10748
+ });
10749
+ const [creationMode, setCreationMode] = (0, import_react26.useState)({
10750
+ isActive: false,
10751
+ sourceNodeData: null
10752
+ });
10753
+ const [versionMode, setVersionMode] = (0, import_react26.useState)({
10754
+ isActive: false,
10755
+ sourceNodeData: null
10756
+ });
10507
10757
  const [questMode, setQuestMode] = (0, import_react26.useState)({ isActive: false });
10508
10758
  const [hasFocusedInitial, setHasFocusedInitial] = (0, import_react26.useState)(false);
10509
10759
  const [hasOpenedInitialAncestry, setHasOpenedInitialAncestry] = (0, import_react26.useState)(false);
10510
- const [ancestryMode, setAncestryMode] = (0, import_react26.useState)({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
10760
+ const [ancestryMode, setAncestryMode] = (0, import_react26.useState)({
10761
+ isActive: false,
10762
+ tree: null,
10763
+ selectedParentId: null,
10764
+ isEditMode: false,
10765
+ currentAncestryId: null,
10766
+ ancestryName: "",
10767
+ ancestryDescription: "",
10768
+ ancestryDescriptionSections: [],
10769
+ isAddingNodes: false
10770
+ });
10511
10771
  const [readingMode, setReadingMode] = (0, import_react26.useState)({
10512
10772
  isActive: false,
10513
10773
  ancestry: null,
10514
10774
  branchStack: [],
10515
10775
  autoAbstraction: false
10516
10776
  });
10517
- const [formPosition, setFormPosition] = (0, import_react26.useState)({ left: 16, top: 16, opacity: 0 });
10777
+ const [formPosition, setFormPosition] = (0, import_react26.useState)({
10778
+ left: 16,
10779
+ top: 16,
10780
+ opacity: 0
10781
+ });
10518
10782
  const [detailsNode, setDetailsNode] = (0, import_react26.useState)(null);
10519
10783
  const [detailsLink, setDetailsLink] = (0, import_react26.useState)(null);
10520
10784
  const [ancestryLinkDetails, setAncestryLinkDetails] = (0, import_react26.useState)(null);
10521
- const [imageViewer, setImageViewer] = (0, import_react26.useState)({ visible: false, images: [], startIndex: 0 });
10522
- const [editingAncestryRel, setEditingAncestryRel] = (0, import_react26.useState)({ visible: false, data: null, path: null });
10785
+ const [imageViewer, setImageViewer] = (0, import_react26.useState)({
10786
+ visible: false,
10787
+ images: [],
10788
+ startIndex: 0
10789
+ });
10790
+ const [editingAncestryRel, setEditingAncestryRel] = (0, import_react26.useState)({
10791
+ visible: false,
10792
+ data: null,
10793
+ path: null
10794
+ });
10523
10795
  const [isImportModalOpen, setIsImportModalOpen] = (0, import_react26.useState)(false);
10524
10796
  const [importSuccessMessage, setImportSuccessMessage] = (0, import_react26.useState)("");
10797
+ const [invalidTargetError, setInvalidTargetError] = (0, import_react26.useState)(null);
10525
10798
  const [highlightedNodeId, setHighlightedNodeId] = (0, import_react26.useState)(null);
10526
10799
  const [isAncestryBoardOpen, setIsAncestryBoardOpen] = (0, import_react26.useState)(false);
10527
10800
  const [ancestryBoardData, setAncestryBoardData] = (0, import_react26.useState)([]);
@@ -10559,8 +10832,23 @@ function XViewScene({
10559
10832
  ghostElements: { node: null, line: null, aura: null },
10560
10833
  creation: { isActive: false, sourceNodeData: null },
10561
10834
  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 },
10835
+ relink: {
10836
+ isActive: false,
10837
+ end: null,
10838
+ fixedNodeId: null,
10839
+ originalLine: null,
10840
+ line: null
10841
+ },
10842
+ ancestry: {
10843
+ isActive: false,
10844
+ tree: null,
10845
+ selectedParentId: null,
10846
+ isEditMode: false,
10847
+ currentAncestryId: null,
10848
+ ancestryName: "",
10849
+ ancestryDescription: "",
10850
+ isAddingNodes: false
10851
+ },
10564
10852
  glowTexture: null,
10565
10853
  nodeIdToParentFileMap: null,
10566
10854
  maxAncestryRenderIndex: 0,
@@ -10569,7 +10857,11 @@ function XViewScene({
10569
10857
  highlightedNodeId: null
10570
10858
  });
10571
10859
  const maxReadPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
10572
- const { width: readModeWidth, isResizing: isReadModeResizing, handlePointerDown: handleReadModeResize } = useResizablePanel({
10860
+ const {
10861
+ width: readModeWidth,
10862
+ isResizing: isReadModeResizing,
10863
+ handlePointerDown: handleReadModeResize
10864
+ } = useResizablePanel({
10573
10865
  initialWidth: 700,
10574
10866
  minWidth: 320,
10575
10867
  maxWidth: maxReadPanelW
@@ -10586,7 +10878,9 @@ function XViewScene({
10586
10878
  for (const parentFileId in allParentData) {
10587
10879
  if (allParentData.hasOwnProperty(parentFileId)) {
10588
10880
  const parentFile = allParentData[parentFileId];
10589
- const parentDbInfo = parentDbsArray.find((db) => String(db.db_id) === String(parentFileId));
10881
+ const parentDbInfo = parentDbsArray.find(
10882
+ (db) => String(db.db_id) === String(parentFileId)
10883
+ );
10590
10884
  const ownerId2 = (parentDbInfo == null ? void 0 : parentDbInfo.owner_id) || null;
10591
10885
  const datasetName = parentFile.dataset_name || `Dataset #${parentFileId.substring(0, 6)}`;
10592
10886
  if (parentFile.nodes && ownerId2) {
@@ -10622,7 +10916,10 @@ function XViewScene({
10622
10916
  if (files.length > 0 && get_single_parent_file && save_view_data) {
10623
10917
  for (const file of files) {
10624
10918
  try {
10625
- const parentFileData = await get_single_parent_file(file.id, session);
10919
+ const parentFileData = await get_single_parent_file(
10920
+ file.id,
10921
+ session
10922
+ );
10626
10923
  if (parentFileData.success && parentFileData.data) {
10627
10924
  parentDataRef.current = {
10628
10925
  ...parentDataRef.current,
@@ -10633,7 +10930,11 @@ function XViewScene({
10633
10930
  owner_id: session.user.id
10634
10931
  };
10635
10932
  updatedParentDbs.push(newParentDbObject);
10636
- await add_new_parent_file_to_scene_at_firebase_action(sceneConfigId, session, file.id);
10933
+ await add_new_parent_file_to_scene_at_firebase_action(
10934
+ sceneConfigId,
10935
+ session,
10936
+ file.id
10937
+ );
10637
10938
  importedIds.push(file.id);
10638
10939
  successCount++;
10639
10940
  }
@@ -10645,7 +10946,10 @@ function XViewScene({
10645
10946
  if (viewToImport && get_ancestry_file) {
10646
10947
  try {
10647
10948
  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);
10949
+ const ancestryResponse = await get_ancestry_file(
10950
+ viewToImport.id,
10951
+ targetViewOwnerId
10952
+ );
10649
10953
  if (ancestryResponse.success && Array.isArray(ancestryResponse.data)) {
10650
10954
  const viewSpecificAncestries = ancestryResponse.data.filter(
10651
10955
  (anc) => anc._source_file_id === viewToImport.id && !anc.is_private
@@ -10656,14 +10960,21 @@ function XViewScene({
10656
10960
  _imported_from_view_owner_id: targetViewOwnerId,
10657
10961
  _source_file_id: viewToImport.id,
10658
10962
  _source_owner_id: targetViewOwnerId,
10659
- _origin_db_ids: (viewToImport.selected_databases || []).map((db) => db.db_id)
10963
+ _origin_db_ids: (viewToImport.selected_databases || []).map(
10964
+ (db) => db.db_id
10965
+ )
10660
10966
  }));
10661
10967
  const currentAncestries = ancestryDataRef.current || [];
10662
10968
  const newAncestries = processedAncestries.filter(
10663
- (newAnc) => !currentAncestries.some((curr) => String(curr.ancestry_id) === String(newAnc.ancestry_id))
10969
+ (newAnc) => !currentAncestries.some(
10970
+ (curr) => String(curr.ancestry_id) === String(newAnc.ancestry_id)
10971
+ )
10664
10972
  );
10665
10973
  if (newAncestries.length > 0) {
10666
- ancestryDataRef.current = [...currentAncestries, ...newAncestries];
10974
+ ancestryDataRef.current = [
10975
+ ...currentAncestries,
10976
+ ...newAncestries
10977
+ ];
10667
10978
  ancestriesWereImported = true;
10668
10979
  }
10669
10980
  }
@@ -10683,7 +10994,9 @@ function XViewScene({
10683
10994
  if (ancestry_save_url) {
10684
10995
  await save_view_data(ancestry_save_url, ancestryDataRef.current);
10685
10996
  } else {
10686
- console.error("Erro: URL de salvamento de ancestralidade n\xE3o definida.");
10997
+ console.error(
10998
+ "Erro: URL de salvamento de ancestralidade n\xE3o definida."
10999
+ );
10687
11000
  }
10688
11001
  }
10689
11002
  setSceneVersion((v) => v + 1);
@@ -10691,114 +11004,137 @@ function XViewScene({
10691
11004
  setImportSuccessMessage("Importa\xE7\xE3o conclu\xEDda com sucesso.");
10692
11005
  setTimeout(() => setImportSuccessMessage(""), 5e3);
10693
11006
  } else if (viewToImport && !ancestriesWereImported) {
10694
- console.warn("Importa\xE7\xE3o finalizada, mas nenhum dado novo foi adicionado.");
11007
+ console.warn(
11008
+ "Importa\xE7\xE3o finalizada, mas nenhum dado novo foi adicionado."
11009
+ );
10695
11010
  }
10696
11011
  },
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]
11012
+ [
11013
+ get_single_parent_file,
11014
+ get_ancestry_file,
11015
+ save_view_data,
11016
+ session,
11017
+ sceneSaveUrl,
11018
+ ancestry_save_url,
11019
+ sceneConfigId,
11020
+ add_new_parent_file_to_scene_at_firebase_action
11021
+ ]
10698
11022
  );
10699
11023
  const handleOpenImageViewer = (images, startIndex) => {
10700
11024
  setImageViewer({ visible: true, images, startIndex });
10701
11025
  };
10702
- const tweenToTarget = (0, import_react26.useCallback)((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 import_tween2.Tween(controls.target).to(targetPos, 1500).easing(import_tween2.Easing.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 import_tween2.Tween(camera.position).to(targetCameraPos, 1500).easing(import_tween2.Easing.Cubic.Out);
10717
- tweenGroup.add(cameraTween);
10718
- cameraTween.start();
10719
- }, []);
11026
+ const tweenToTarget = (0, import_react26.useCallback)(
11027
+ (target, zoomFactor = 1, forcedDirection = null) => {
11028
+ const { camera, controls, tweenGroup } = stateRef.current;
11029
+ if (!camera || !controls || !tweenGroup) return;
11030
+ const targetPos = target instanceof THREE3.Mesh ? target.getWorldPosition(new THREE3.Vector3()) : target;
11031
+ const controlsTween = new import_tween2.Tween(controls.target).to(targetPos, 1500).easing(import_tween2.Easing.Cubic.Out);
11032
+ tweenGroup.add(controlsTween);
11033
+ controlsTween.start();
11034
+ let offset;
11035
+ if (forcedDirection) {
11036
+ offset = forcedDirection.clone().normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
11037
+ } else {
11038
+ offset = camera.position.clone().sub(controls.target).normalize().multiplyScalar(x_view_config.CAMERA_ZOOM_DISTANCE / zoomFactor);
11039
+ }
11040
+ const targetCameraPos = targetPos.clone().add(offset);
11041
+ const cameraTween = new import_tween2.Tween(camera.position).to(targetCameraPos, 1500).easing(import_tween2.Easing.Cubic.Out);
11042
+ tweenGroup.add(cameraTween);
11043
+ cameraTween.start();
11044
+ },
11045
+ []
11046
+ );
10720
11047
  const isFromUiOverlay = (event) => {
10721
11048
  const t = event == null ? void 0 : event.target;
10722
11049
  if (!t || typeof t.closest !== "function") return false;
10723
11050
  return !!t.closest(".ui-overlay");
10724
11051
  };
10725
- const buildFullAncestryTree = (0, import_react26.useCallback)((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
- }
11052
+ const buildFullAncestryTree = (0, import_react26.useCallback)(
11053
+ (idTree, nodes, ancestries = []) => {
11054
+ if (!idTree) return null;
11055
+ const nodeMap = new Map(nodes.map((n) => [String(n.id), n]));
11056
+ const ancestryMap = new Map(
11057
+ ancestries.map((a) => [String(a.ancestry_id), a])
11058
+ );
11059
+ const recursiveBuild = (treeItem) => {
11060
+ if (!treeItem) return null;
11061
+ if (treeItem.is_section) {
10758
11062
  return {
10759
- ...branch,
10760
- tree: recursiveBuild(branch.tree)
11063
+ ...treeItem,
11064
+ children: (treeItem.children || []).map(recursiveBuild).filter(Boolean)
10761
11065
  };
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
- };
11066
+ }
11067
+ let nodeId = treeItem.node_id;
11068
+ if (!nodeId && treeItem.node) nodeId = treeItem.node.id;
11069
+ const fullNode = nodeMap.get(String(nodeId));
11070
+ const effectiveNode = fullNode || treeItem.node;
11071
+ let processedBranches = [];
11072
+ if (treeItem.parallel_branches && Array.isArray(treeItem.parallel_branches)) {
11073
+ processedBranches = treeItem.parallel_branches.map((branch) => {
11074
+ if (branch.linked_ancestry_id) {
11075
+ const linkedAncestry = ancestryMap.get(
11076
+ String(branch.linked_ancestry_id)
11077
+ );
11078
+ if (linkedAncestry && linkedAncestry.tree) {
11079
+ const graftedTree = recursiveBuild(linkedAncestry.tree);
11080
+ return {
11081
+ ...branch,
11082
+ name: linkedAncestry.name,
11083
+ description: linkedAncestry.description,
11084
+ description_sections: linkedAncestry.description_sections,
11085
+ tree: graftedTree,
11086
+ isLinked: true
11087
+ };
11088
+ }
10793
11089
  }
10794
- }
10795
- return { ...branch, tree: recursiveBuild(branch.tree) };
10796
- })
11090
+ return {
11091
+ ...branch,
11092
+ tree: recursiveBuild(branch.tree)
11093
+ };
11094
+ });
11095
+ }
11096
+ return {
11097
+ ...effectiveNode ? { node: effectiveNode } : { node: { id: nodeId, name: "Unknown" } },
11098
+ relationship: treeItem.relationship || {},
11099
+ children: (treeItem.children || []).map(recursiveBuild).filter(Boolean),
11100
+ parallel_branches: processedBranches
11101
+ };
10797
11102
  };
10798
- }
10799
- return recursiveBuild(idTree);
10800
- }, []);
10801
- const handleActivateTimeline = (0, import_react26.useCallback)(() => {
11103
+ let rootId = idTree.node_id;
11104
+ if (!rootId && idTree.node) rootId = idTree.node.id;
11105
+ if (rootId) {
11106
+ const rootNode = nodeMap.get(String(rootId));
11107
+ const effectiveRoot = rootNode || idTree.node;
11108
+ if (!effectiveRoot) return null;
11109
+ return {
11110
+ node: effectiveRoot,
11111
+ relationship: idTree.relationship || {},
11112
+ children: (idTree.children || []).map(recursiveBuild).filter(Boolean),
11113
+ parallel_branches: (idTree.parallel_branches || []).map((branch) => {
11114
+ if (branch.linked_ancestry_id) {
11115
+ const linkedAncestry = ancestryMap.get(
11116
+ String(branch.linked_ancestry_id)
11117
+ );
11118
+ if (linkedAncestry && linkedAncestry.tree) {
11119
+ return {
11120
+ ...branch,
11121
+ name: linkedAncestry.name,
11122
+ description: linkedAncestry.description,
11123
+ description_sections: linkedAncestry.description_sections,
11124
+ tree: recursiveBuild(linkedAncestry.tree),
11125
+ isLinked: true
11126
+ };
11127
+ }
11128
+ }
11129
+ return { ...branch, tree: recursiveBuild(branch.tree) };
11130
+ })
11131
+ };
11132
+ }
11133
+ return recursiveBuild(idTree);
11134
+ },
11135
+ []
11136
+ );
11137
+ const handleActivateTimeline = (0, import_react26.useCallback)(() => {
10802
11138
  const { nodeObjects, tweenGroup, timelineIntervalsGroup } = stateRef.current;
10803
11139
  if (!nodeObjects || !tweenGroup || !timelineIntervalsGroup) return;
10804
11140
  while (timelineIntervalsGroup.children.length > 0) {
@@ -10878,10 +11214,12 @@ function XViewScene({
10878
11214
  if (timelineNodes.length === 0) return;
10879
11215
  const sortedTimePoints = Array.from(allTimePoints).sort((a, b) => a - b);
10880
11216
  const maxTimeIndex = sortedTimePoints.length - 1;
10881
- const timeToYMap = new Map(sortedTimePoints.map((time, index) => [
10882
- time,
10883
- (index - maxTimeIndex) * TIMELINE_LAYER_SPACING_Y
10884
- ]));
11217
+ const timeToYMap = new Map(
11218
+ sortedTimePoints.map((time, index) => [
11219
+ time,
11220
+ (index - maxTimeIndex) * TIMELINE_LAYER_SPACING_Y
11221
+ ])
11222
+ );
10885
11223
  timelineNodes.sort((a, b) => {
10886
11224
  if (a.isUndated && b.isUndated) return 0;
10887
11225
  if (a.isUndated) return 1;
@@ -10924,7 +11262,12 @@ function XViewScene({
10924
11262
  if (type === "interval") {
10925
11263
  const endY = timeToYMap.get(endDate.getTime());
10926
11264
  if (endY > y) {
10927
- const barGeometry = new THREE3.CylinderGeometry(TIMELINE_INTERVAL_BAR_RADIUS, TIMELINE_INTERVAL_BAR_RADIUS, 1, 16);
11265
+ const barGeometry = new THREE3.CylinderGeometry(
11266
+ TIMELINE_INTERVAL_BAR_RADIUS,
11267
+ TIMELINE_INTERVAL_BAR_RADIUS,
11268
+ 1,
11269
+ 16
11270
+ );
10928
11271
  const barMaterial = new THREE3.MeshStandardMaterial({
10929
11272
  color: TIMELINE_GOLD_COLOR,
10930
11273
  emissive: 12092939,
@@ -10953,7 +11296,8 @@ function XViewScene({
10953
11296
  }, []);
10954
11297
  const handleVersionTimeline = (0, import_react26.useCallback)((sourceMesh, versionMeshes) => {
10955
11298
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
10956
- if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0) return;
11299
+ if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
11300
+ return;
10957
11301
  versionMeshes.forEach((mesh) => {
10958
11302
  const oldLabel = mesh.getObjectByName("timelineLabel");
10959
11303
  if (oldLabel) {
@@ -10969,7 +11313,8 @@ function XViewScene({
10969
11313
  }
10970
11314
  if (mesh.userData.timelineEndLabel) {
10971
11315
  timelineIntervalsGroup.remove(mesh.userData.timelineEndLabel);
10972
- if (mesh.userData.timelineEndLabel.material.map) mesh.userData.timelineEndLabel.material.map.dispose();
11316
+ if (mesh.userData.timelineEndLabel.material.map)
11317
+ mesh.userData.timelineEndLabel.material.map.dispose();
10973
11318
  mesh.userData.timelineEndLabel.material.dispose();
10974
11319
  delete mesh.userData.timelineEndLabel;
10975
11320
  }
@@ -11029,8 +11374,15 @@ function XViewScene({
11029
11374
  });
11030
11375
  if (timelineNodes.length === 0) return;
11031
11376
  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);
11377
+ const timeToYMap = new Map(
11378
+ sortedTimePoints.map((time, index) => [
11379
+ time,
11380
+ index * TIMELINE_LAYER_SPACING_Y
11381
+ ])
11382
+ );
11383
+ timelineNodes.sort(
11384
+ (a, b) => a.startDate - b.startDate || a.endDate - b.endDate
11385
+ );
11034
11386
  const baseX = sourceMesh.position.x + TIMELINE_NODE_SPACING_X;
11035
11387
  const baseY = sourceMesh.position.y;
11036
11388
  const maxNodeIndex = timelineNodes.length - 1;
@@ -11048,7 +11400,12 @@ function XViewScene({
11048
11400
  if (type === "interval") {
11049
11401
  const relativeEndY = timeToYMap.get(endDate.getTime()) - timeToYMap.get(startDate.getTime());
11050
11402
  if (relativeEndY > 0) {
11051
- const barGeometry = new THREE3.CylinderGeometry(TIMELINE_INTERVAL_BAR_RADIUS, TIMELINE_INTERVAL_BAR_RADIUS, 1, 16);
11403
+ const barGeometry = new THREE3.CylinderGeometry(
11404
+ TIMELINE_INTERVAL_BAR_RADIUS,
11405
+ TIMELINE_INTERVAL_BAR_RADIUS,
11406
+ 1,
11407
+ 16
11408
+ );
11052
11409
  const barMaterial = new THREE3.MeshStandardMaterial({
11053
11410
  color: TIMELINE_GOLD_COLOR,
11054
11411
  emissive: 12092939,
@@ -11085,15 +11442,31 @@ function XViewScene({
11085
11442
  try {
11086
11443
  const typeStr = (viewParams == null ? void 0 : viewParams.type) || "";
11087
11444
  const sceneType = typeStr.toLowerCase().includes("database") ? "database" : "view";
11088
- const scenePromise = get_scene_view_data(configPath, ownerId2, typeStr, session, focusNodeId, focusAncestryId);
11445
+ const scenePromise = get_scene_view_data(
11446
+ configPath,
11447
+ ownerId2,
11448
+ typeStr,
11449
+ session,
11450
+ focusNodeId,
11451
+ focusAncestryId
11452
+ );
11089
11453
  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]);
11454
+ const [sceneResponse, boardResponse] = await Promise.all([
11455
+ scenePromise,
11456
+ boardPromise
11457
+ ]);
11091
11458
  if ((sceneResponse == null ? void 0 : sceneResponse.success) && ((_a2 = sceneResponse.data) == null ? void 0 : _a2.scene) && ((_b2 = sceneResponse.data) == null ? void 0 : _b2.parent)) {
11092
11459
  if (focusNodeId) {
11093
- let targetNode = sceneResponse.data.scene.nodes.find((n) => String(n.id) === String(focusNodeId));
11460
+ let targetNode = sceneResponse.data.scene.nodes.find(
11461
+ (n) => String(n.id) === String(focusNodeId)
11462
+ );
11094
11463
  if (!targetNode) {
11095
- const allParentNodes = Object.values(sceneResponse.data.parent).flatMap((f) => f.nodes || []);
11096
- targetNode = allParentNodes.find((n) => String(n.id) === String(focusNodeId));
11464
+ const allParentNodes = Object.values(
11465
+ sceneResponse.data.parent
11466
+ ).flatMap((f) => f.nodes || []);
11467
+ targetNode = allParentNodes.find(
11468
+ (n) => String(n.id) === String(focusNodeId)
11469
+ );
11097
11470
  }
11098
11471
  if (targetNode) {
11099
11472
  sceneResponse.data.scene.nodes = [targetNode];
@@ -11106,12 +11479,24 @@ function XViewScene({
11106
11479
  sceneDataRef.current = sceneResponse.data.scene;
11107
11480
  parentDataRef.current = sceneResponse.data.parent;
11108
11481
  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);
11482
+ console.log(
11483
+ "Console de sceneResponse.data.scene:",
11484
+ sceneResponse.data.scene
11485
+ );
11486
+ console.log(
11487
+ "Console de sceneResponse.data.parent:",
11488
+ sceneResponse.data.parent
11489
+ );
11490
+ console.log(
11491
+ "Console de sceneResponse.data.ancestry:",
11492
+ sceneResponse.data.ancestry
11493
+ );
11112
11494
  setIsInitialized(true);
11113
11495
  } else {
11114
- console.error("Falha ao buscar dados da cena:", (sceneResponse == null ? void 0 : sceneResponse.error) || "Resposta inv\xE1lida.");
11496
+ console.error(
11497
+ "Falha ao buscar dados da cena:",
11498
+ (sceneResponse == null ? void 0 : sceneResponse.error) || "Resposta inv\xE1lida."
11499
+ );
11115
11500
  }
11116
11501
  if (boardResponse == null ? void 0 : boardResponse.success) {
11117
11502
  setAncestryBoardData(boardResponse.data);
@@ -11130,7 +11515,9 @@ function XViewScene({
11130
11515
  console.error("Usu\xE1rio n\xE3o autenticado. Acesso negado.");
11131
11516
  setIsLoading(false);
11132
11517
  } else if (!sceneConfigId && status !== "loading") {
11133
- console.warn("Nenhum par\xE2metro de cena encontrado na URL ou falha na decripta\xE7\xE3o.");
11518
+ console.warn(
11519
+ "Nenhum par\xE2metro de cena encontrado na URL ou falha na decripta\xE7\xE3o."
11520
+ );
11134
11521
  setIsLoading(false);
11135
11522
  }
11136
11523
  }, [
@@ -11151,33 +11538,46 @@ function XViewScene({
11151
11538
  const objs = stateRef.current.nodeObjects || {};
11152
11539
  return !!objs[key];
11153
11540
  }, []);
11154
- const addOrUpdateNodeMesh = (0, import_react26.useCallback)((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 import_tween2.Tween(existingMesh.position).to(position, 800).easing(import_tween2.Easing.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);
11541
+ const addOrUpdateNodeMesh = (0, import_react26.useCallback)(
11542
+ (nodeData, position, suppressVersionUpdate = false) => {
11543
+ const {
11544
+ graphGroup,
11545
+ nodeObjects,
11546
+ clickableNodes,
11547
+ glowTexture,
11548
+ tweenGroup
11549
+ } = stateRef.current;
11550
+ const nodeId = String(nodeData.id);
11551
+ if (nodeObjects[nodeId]) {
11552
+ const existingMesh = nodeObjects[nodeId];
11553
+ if (position) {
11554
+ const updateTween = new import_tween2.Tween(existingMesh.position).to(position, 800).easing(import_tween2.Easing.Cubic.Out);
11555
+ tweenGroup.add(updateTween);
11556
+ updateTween.start();
11557
+ }
11558
+ return existingMesh;
11559
+ }
11560
+ const mesh = createNodeMesh(
11561
+ nodeData,
11562
+ position || new THREE3.Vector3(),
11563
+ glowTexture
11564
+ );
11565
+ graphGroup.add(mesh);
11566
+ if (mesh.userData.labelObject) {
11567
+ if (mesh.userData.labelOffset) {
11568
+ mesh.userData.labelObject.position.copy(mesh.position).add(mesh.userData.labelOffset);
11569
+ }
11570
+ graphGroup.add(mesh.userData.labelObject);
11171
11571
  }
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
- }, []);
11572
+ nodeObjects[nodeId] = mesh;
11573
+ clickableNodes.push(mesh);
11574
+ if (!suppressVersionUpdate) {
11575
+ setSceneVersion((v) => v + 1);
11576
+ }
11577
+ return mesh;
11578
+ },
11579
+ []
11580
+ );
11181
11581
  (0, import_react26.useEffect)(() => {
11182
11582
  if (!isInitialized || !sceneDataRef.current) return;
11183
11583
  const currentMount = mountRef.current;
@@ -11192,7 +11592,12 @@ function XViewScene({
11192
11592
  const scene = new THREE3.Scene();
11193
11593
  scene.background = new THREE3.Color(0);
11194
11594
  stateRef.current.scene = scene;
11195
- const camera = new THREE3.PerspectiveCamera(75, currentMount.clientWidth / currentMount.clientHeight, 0.1, 2e3);
11595
+ const camera = new THREE3.PerspectiveCamera(
11596
+ 75,
11597
+ currentMount.clientWidth / currentMount.clientHeight,
11598
+ 0.1,
11599
+ 2e3
11600
+ );
11196
11601
  camera.position.set(0, 60, 120);
11197
11602
  stateRef.current.camera = camera;
11198
11603
  const renderer = new THREE3.WebGLRenderer({ antialias: true });
@@ -11213,7 +11618,12 @@ function XViewScene({
11213
11618
  directionalLight.position.set(50, 50, 50);
11214
11619
  scene.add(directionalLight);
11215
11620
  const renderScene = new import_RenderPass.RenderPass(scene, camera);
11216
- const bloomPass = new import_UnrealBloomPass.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);
11621
+ const bloomPass = new import_UnrealBloomPass.UnrealBloomPass(
11622
+ new THREE3.Vector2(currentMount.clientWidth, currentMount.clientHeight),
11623
+ x_view_config.BLOOM_EFFECT.strength,
11624
+ x_view_config.BLOOM_EFFECT.radius,
11625
+ x_view_config.BLOOM_EFFECT.threshold
11626
+ );
11217
11627
  const composer = new import_EffectComposer.EffectComposer(renderer);
11218
11628
  composer.addPass(renderScene);
11219
11629
  composer.addPass(bloomPass);
@@ -11256,8 +11666,16 @@ function XViewScene({
11256
11666
  const sourceNode = nodeObjects[String(linksArray[0].source)];
11257
11667
  const targetNode = nodeObjects[String(linksArray[0].target)];
11258
11668
  if (sourceNode && targetNode) {
11259
- const resolution = new THREE3.Vector2(currentMount.clientWidth, currentMount.clientHeight);
11260
- const newLinks = createMultipleLinkLines(linksArray, sourceNode, targetNode, resolution);
11669
+ const resolution = new THREE3.Vector2(
11670
+ currentMount.clientWidth,
11671
+ currentMount.clientHeight
11672
+ );
11673
+ const newLinks = createMultipleLinkLines(
11674
+ linksArray,
11675
+ sourceNode,
11676
+ targetNode,
11677
+ resolution
11678
+ );
11261
11679
  newLinks.forEach((line, idx) => {
11262
11680
  const meta = linksArray[idx];
11263
11681
  if (meta) {
@@ -11298,7 +11716,10 @@ function XViewScene({
11298
11716
  function tryPickNode() {
11299
11717
  raycaster.setFromCamera(mouse, camera);
11300
11718
  raycaster.layers.enable(GHOST_BLOOM_LAYER);
11301
- const intersects = raycaster.intersectObjects(stateRef.current.clickableNodes, false);
11719
+ const intersects = raycaster.intersectObjects(
11720
+ stateRef.current.clickableNodes,
11721
+ false
11722
+ );
11302
11723
  return intersects.length > 0 ? intersects[0].object : null;
11303
11724
  }
11304
11725
  function findClosestLinkToRay() {
@@ -11310,7 +11731,10 @@ function XViewScene({
11310
11731
  x: (mouse.x * 0.5 + 0.5) * clientWidth,
11311
11732
  y: (-mouse.y * 0.5 + 0.5) * clientHeight
11312
11733
  };
11313
- const allVisibleLinks = [...stateRef.current.allLinks, ...stateRef.current.ancestryLinks];
11734
+ const allVisibleLinks = [
11735
+ ...stateRef.current.allLinks,
11736
+ ...stateRef.current.ancestryLinks
11737
+ ];
11314
11738
  const THRESH = x_view_config.LINE_HOVER_THRESHOLD_PX + 4;
11315
11739
  const tmpP1 = new THREE3.Vector3();
11316
11740
  const tmpP2 = new THREE3.Vector3();
@@ -11325,19 +11749,35 @@ function XViewScene({
11325
11749
  const up = new THREE3.Vector3(0, 1, 0);
11326
11750
  const normal = new THREE3.Vector3().crossVectors(dir, up).normalize();
11327
11751
  const controlPoint = mid.add(normal.multiplyScalar(curveOffset || 0));
11328
- const curve = new THREE3.QuadraticBezierCurve3(start, controlPoint, end);
11752
+ const curve = new THREE3.QuadraticBezierCurve3(
11753
+ start,
11754
+ controlPoint,
11755
+ end
11756
+ );
11329
11757
  const points = curve.getPoints(x_view_config.CURVE_SEGMENTS || 32);
11330
11758
  for (let i = 0; i < points.length - 1; i++) {
11331
11759
  tmpP1.copy(points[i]).project(camera);
11332
11760
  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 };
11761
+ const p1_px = {
11762
+ x: (tmpP1.x * 0.5 + 0.5) * clientWidth,
11763
+ y: (-tmpP1.y * 0.5 + 0.5) * clientHeight
11764
+ };
11765
+ const p2_px = {
11766
+ x: (tmpP2.x * 0.5 + 0.5) * clientWidth,
11767
+ y: (-tmpP2.y * 0.5 + 0.5) * clientHeight
11768
+ };
11335
11769
  const L2 = (p2_px.x - p1_px.x) ** 2 + (p2_px.y - p1_px.y) ** 2;
11336
11770
  if (L2 === 0) continue;
11337
11771
  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
11772
  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);
11773
+ const closestPoint = {
11774
+ x: p1_px.x + t * (p2_px.x - p1_px.x),
11775
+ y: p1_px.y + t * (p2_px.y - p1_px.y)
11776
+ };
11777
+ const dist = Math.hypot(
11778
+ mousePixels.x - closestPoint.x,
11779
+ mousePixels.y - closestPoint.y
11780
+ );
11341
11781
  if (dist < THRESH && dist < minDistance) {
11342
11782
  minDistance = dist;
11343
11783
  closestLink = link;
@@ -11346,14 +11786,26 @@ function XViewScene({
11346
11786
  } else {
11347
11787
  const p1 = new THREE3.Vector3().copy(sourceNode.position).project(camera);
11348
11788
  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 };
11789
+ const p1_px = {
11790
+ x: (p1.x * 0.5 + 0.5) * clientWidth,
11791
+ y: (-p1.y * 0.5 + 0.5) * clientHeight
11792
+ };
11793
+ const p2_px = {
11794
+ x: (p2.x * 0.5 + 0.5) * clientWidth,
11795
+ y: (-p2.y * 0.5 + 0.5) * clientHeight
11796
+ };
11351
11797
  const L2 = (p2_px.x - p1_px.x) ** 2 + (p2_px.y - p1_px.y) ** 2;
11352
11798
  if (L2 === 0) return;
11353
11799
  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
11800
  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);
11801
+ const closestPoint = {
11802
+ x: p1_px.x + t * (p2_px.x - p1_px.x),
11803
+ y: p1_px.y + t * (p2_px.y - p1_px.y)
11804
+ };
11805
+ const dist = Math.hypot(
11806
+ mousePixels.x - closestPoint.x,
11807
+ mousePixels.y - closestPoint.y
11808
+ );
11357
11809
  if (dist < THRESH && dist < minDistance) {
11358
11810
  minDistance = dist;
11359
11811
  closestLink = link;
@@ -11388,7 +11840,11 @@ function XViewScene({
11388
11840
  if (picked && !stateRef.current.ancestry.isActive) {
11389
11841
  stateRef.current.controls.enabled = false;
11390
11842
  }
11391
- stateRef.current.pointerDown = { isDown: true, x: event.clientX, y: event.clientY };
11843
+ stateRef.current.pointerDown = {
11844
+ isDown: true,
11845
+ x: event.clientX,
11846
+ y: event.clientY
11847
+ };
11392
11848
  stateRef.current.dragCandidate = picked;
11393
11849
  }
11394
11850
  function onPointerMove(event) {
@@ -11399,7 +11855,10 @@ function XViewScene({
11399
11855
  const raycaster2 = new THREE3.Raycaster();
11400
11856
  raycaster2.setFromCamera(mouse, camera);
11401
11857
  camera.getWorldDirection(plane.normal);
11402
- plane.setFromNormalAndCoplanarPoint(plane.normal, stateRef.current.draggedNode.position);
11858
+ plane.setFromNormalAndCoplanarPoint(
11859
+ plane.normal,
11860
+ stateRef.current.draggedNode.position
11861
+ );
11403
11862
  if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) {
11404
11863
  const draggedNode = stateRef.current.draggedNode;
11405
11864
  draggedNode.position.copy(intersectionPoint);
@@ -11408,7 +11867,8 @@ function XViewScene({
11408
11867
  }
11409
11868
  if (stateRef.current.connection.isActive || stateRef.current.relink.isActive || stateRef.current.ancestry.isActive) {
11410
11869
  const newHoveredNode2 = tryPickNode();
11411
- if (stateRef.current.hoveredNode !== newHoveredNode2) stateRef.current.hoveredNode = newHoveredNode2;
11870
+ if (stateRef.current.hoveredNode !== newHoveredNode2)
11871
+ stateRef.current.hoveredNode = newHoveredNode2;
11412
11872
  if (currentMount) {
11413
11873
  let fixedId = null;
11414
11874
  if (stateRef.current.connection.isActive) {
@@ -11439,14 +11899,20 @@ function XViewScene({
11439
11899
  stateRef.current.controls.enabled = false;
11440
11900
  if (currentMount) currentMount.style.cursor = "grabbing";
11441
11901
  camera.getWorldDirection(plane.normal);
11442
- plane.setFromNormalAndCoplanarPoint(plane.normal, stateRef.current.draggedNode.position);
11902
+ plane.setFromNormalAndCoplanarPoint(
11903
+ plane.normal,
11904
+ stateRef.current.draggedNode.position
11905
+ );
11443
11906
  }
11444
11907
  }
11445
11908
  const newHoveredNode = tryPickNode();
11446
11909
  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";
11910
+ if (stateRef.current.hoveredNode !== newHoveredNode)
11911
+ stateRef.current.hoveredNode = newHoveredNode;
11912
+ if (stateRef.current.hoveredLink !== newHoveredLink)
11913
+ stateRef.current.hoveredLink = newHoveredLink;
11914
+ if (currentMount)
11915
+ currentMount.style.cursor = newHoveredNode || newHoveredLink ? "pointer" : "grab";
11450
11916
  }
11451
11917
  const isNodeInTree = (tree, nodeId) => {
11452
11918
  if (!tree) return false;
@@ -11461,7 +11927,10 @@ function XViewScene({
11461
11927
  const context = actionHandlerContext;
11462
11928
  if (connection.isActive) {
11463
11929
  if (hoveredNode && String(hoveredNode.userData.id) !== String(connection.sourceNodeData.id)) {
11464
- await userActionHandlers.handleCompleteConnection(context, hoveredNode.userData);
11930
+ await userActionHandlers.handleCompleteConnection(
11931
+ context,
11932
+ hoveredNode.userData
11933
+ );
11465
11934
  } else {
11466
11935
  userActionHandlers.handleCancelConnection(context);
11467
11936
  }
@@ -11469,7 +11938,10 @@ function XViewScene({
11469
11938
  }
11470
11939
  if (relink.isActive) {
11471
11940
  if (hoveredNode && String(hoveredNode.userData.id) !== String(relink.fixedNodeId)) {
11472
- await userActionHandlers.handleCompleteRelink(context, hoveredNode.userData);
11941
+ await userActionHandlers.handleCompleteRelink(
11942
+ context,
11943
+ hoveredNode.userData
11944
+ );
11473
11945
  } else {
11474
11946
  userActionHandlers.handleCancelRelink(context);
11475
11947
  }
@@ -11487,7 +11959,9 @@ function XViewScene({
11487
11959
  const clickedNodeId = String(clickedNode.userData.id);
11488
11960
  const parentId = String(currentSelectedParent);
11489
11961
  if (clickedNodeId === parentId) {
11490
- alert("Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo.");
11962
+ alert(
11963
+ "Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo."
11964
+ );
11491
11965
  return;
11492
11966
  }
11493
11967
  const parentInfo = stateRef.current.nodeIdToParentFileMap.get(clickedNodeId);
@@ -11497,16 +11971,33 @@ function XViewScene({
11497
11971
  const addChildToNode = (current, targetParentId, childNode) => {
11498
11972
  const currentId = current.is_section ? current.id || current.section_id : String(current.node.id);
11499
11973
  if (String(currentId) === String(targetParentId)) {
11500
- const alreadyExists = current.children.some((child) => !child.is_section && String(child.node.id) === String(childNode.id));
11974
+ const alreadyExists = current.children.some(
11975
+ (child) => !child.is_section && String(child.node.id) === String(childNode.id)
11976
+ );
11501
11977
  if (alreadyExists) return current;
11502
- return { ...current, children: [...current.children, { node: childNode, children: [], relationship: {} }] };
11978
+ return {
11979
+ ...current,
11980
+ children: [
11981
+ ...current.children,
11982
+ { node: childNode, children: [], relationship: {} }
11983
+ ]
11984
+ };
11503
11985
  }
11504
- return { ...current, children: current.children.map((c) => addChildToNode(c, targetParentId, childNode)) };
11986
+ return {
11987
+ ...current,
11988
+ children: current.children.map(
11989
+ (c) => addChildToNode(c, targetParentId, childNode)
11990
+ )
11991
+ };
11505
11992
  };
11506
11993
  setAncestryMode((prev) => {
11507
11994
  const treeKey = isAbstraction ? "abstraction_tree" : "tree";
11508
11995
  if (!prev[treeKey]) return prev;
11509
- const newTree = addChildToNode(prev[treeKey], parentId, fullNodeData);
11996
+ const newTree = addChildToNode(
11997
+ prev[treeKey],
11998
+ parentId,
11999
+ fullNodeData
12000
+ );
11510
12001
  return { ...prev, [treeKey]: newTree };
11511
12002
  });
11512
12003
  }
@@ -11520,7 +12011,8 @@ function XViewScene({
11520
12011
  stateRef.current.dragCandidate = null;
11521
12012
  stateRef.current.pointerDown.isDown = false;
11522
12013
  stateRef.current.controls.enabled = true;
11523
- if (currentMount) currentMount.style.cursor = stateRef.current.hoveredNode || stateRef.current.hoveredLink ? "pointer" : "grab";
12014
+ if (currentMount)
12015
+ currentMount.style.cursor = stateRef.current.hoveredNode || stateRef.current.hoveredLink ? "pointer" : "grab";
11524
12016
  return;
11525
12017
  }
11526
12018
  const dragDistance = Math.hypot(
@@ -11568,16 +12060,21 @@ function XViewScene({
11568
12060
  }
11569
12061
  function handleDoubleClick(event) {
11570
12062
  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);
12063
+ if (isFromUiOverlay(event) || stateRef.current.isDragging || stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive)
12064
+ return;
12065
+ if (stateRef.current.hoveredNode)
12066
+ tweenToTarget(stateRef.current.hoveredNode);
11573
12067
  }
11574
12068
  function handleContextMenu(event) {
11575
12069
  if (stateRef.current.camera) stateRef.current.camera.layers.enableAll();
11576
12070
  if (isFromUiOverlay(event)) return;
11577
12071
  event.preventDefault();
11578
- if (stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive) return;
12072
+ if (stateRef.current.creation.isActive || stateRef.current.connection.isActive || stateRef.current.relink.isActive)
12073
+ return;
11579
12074
  setMouseFromEvent(event);
11580
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12075
+ setContextMenu(
12076
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12077
+ );
11581
12078
  setMultiContextMenu((prev) => ({ ...prev, visible: false }));
11582
12079
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
11583
12080
  const pickedNode = tryPickNode();
@@ -11608,7 +12105,12 @@ function XViewScene({
11608
12105
  return;
11609
12106
  }
11610
12107
  stateRef.current.selectedNodes.clear();
11611
- setRelationshipMenu({ visible: true, x: event.clientX, y: event.clientY, linkObject: pickedLink });
12108
+ setRelationshipMenu({
12109
+ visible: true,
12110
+ x: event.clientX,
12111
+ y: event.clientY,
12112
+ linkObject: pickedLink
12113
+ });
11612
12114
  return;
11613
12115
  }
11614
12116
  stateRef.current.selectedNodes.clear();
@@ -11640,7 +12142,10 @@ function XViewScene({
11640
12142
  }
11641
12143
  });
11642
12144
  }
11643
- const allRenderedLinks = [...stateRef.current.allLinks, ...stateRef.current.ancestryLinks];
12145
+ const allRenderedLinks = [
12146
+ ...stateRef.current.allLinks,
12147
+ ...stateRef.current.ancestryLinks
12148
+ ];
11644
12149
  allRenderedLinks.forEach((line) => {
11645
12150
  const { sourceNode, targetNode, isCurved, curveOffset } = line.userData;
11646
12151
  if (sourceNode && targetNode) {
@@ -11652,13 +12157,20 @@ function XViewScene({
11652
12157
  const up = new THREE3.Vector3(0, 1, 0);
11653
12158
  const normal = new THREE3.Vector3().crossVectors(dir, up).normalize();
11654
12159
  const controlPoint = mid.add(normal.multiplyScalar(curveOffset));
11655
- const curve = new THREE3.QuadraticBezierCurve3(start, controlPoint, end);
12160
+ const curve = new THREE3.QuadraticBezierCurve3(
12161
+ start,
12162
+ controlPoint,
12163
+ end
12164
+ );
11656
12165
  const points = curve.getPoints(x_view_config.CURVE_SEGMENTS);
11657
12166
  const positions = [];
11658
12167
  points.forEach((p) => positions.push(p.x, p.y, p.z));
11659
12168
  line.geometry.setPositions(positions);
11660
12169
  } else {
11661
- line.geometry.setPositions([...sourceNode.position.toArray(), ...targetNode.position.toArray()]);
12170
+ line.geometry.setPositions([
12171
+ ...sourceNode.position.toArray(),
12172
+ ...targetNode.position.toArray()
12173
+ ]);
11662
12174
  }
11663
12175
  }
11664
12176
  });
@@ -11671,7 +12183,11 @@ function XViewScene({
11671
12183
  const startPos = node.position;
11672
12184
  const distance = startPos.distanceTo(endPos);
11673
12185
  bar.scale.y = distance;
11674
- const midpoint = new THREE3.Vector3().lerpVectors(startPos, endPos, 0.5);
12186
+ const midpoint = new THREE3.Vector3().lerpVectors(
12187
+ startPos,
12188
+ endPos,
12189
+ 0.5
12190
+ );
11675
12191
  bar.position.copy(midpoint);
11676
12192
  const direction = new THREE3.Vector3().subVectors(endPos, startPos).normalize();
11677
12193
  const upVector = new THREE3.Vector3(0, 1, 0);
@@ -11682,7 +12198,11 @@ function XViewScene({
11682
12198
  const { ghostElements, creation, connection, relink } = stateRef.current;
11683
12199
  if (creation.isActive && ghostElements.node && ghostElements.line) {
11684
12200
  const srcMesh = stateRef.current.nodeObjects[String(creation.sourceNodeData.id)];
11685
- if (srcMesh) ghostElements.line.geometry.setPositions([...srcMesh.position.toArray(), ...ghostElements.node.position.toArray()]);
12201
+ if (srcMesh)
12202
+ ghostElements.line.geometry.setPositions([
12203
+ ...srcMesh.position.toArray(),
12204
+ ...ghostElements.node.position.toArray()
12205
+ ]);
11686
12206
  }
11687
12207
  if (connection.isActive && connection.line) {
11688
12208
  const srcMesh = stateRef.current.nodeObjects[String(connection.sourceNodeData.id)];
@@ -11691,7 +12211,11 @@ function XViewScene({
11691
12211
  raycaster2.setFromCamera(mouse, camera);
11692
12212
  camera.getWorldDirection(plane.normal);
11693
12213
  plane.setFromNormalAndCoplanarPoint(plane.normal, srcMesh.position);
11694
- if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) connection.line.geometry.setPositions([...srcMesh.position.toArray(), ...intersectionPoint.toArray()]);
12214
+ if (raycaster2.ray.intersectPlane(plane, intersectionPoint))
12215
+ connection.line.geometry.setPositions([
12216
+ ...srcMesh.position.toArray(),
12217
+ ...intersectionPoint.toArray()
12218
+ ]);
11695
12219
  }
11696
12220
  }
11697
12221
  if (relink.isActive && relink.line) {
@@ -11701,7 +12225,11 @@ function XViewScene({
11701
12225
  raycaster2.setFromCamera(mouse, camera);
11702
12226
  camera.getWorldDirection(plane.normal);
11703
12227
  plane.setFromNormalAndCoplanarPoint(plane.normal, fixedMesh.position);
11704
- if (raycaster2.ray.intersectPlane(plane, intersectionPoint)) relink.line.geometry.setPositions([...fixedMesh.position.toArray(), ...intersectionPoint.toArray()]);
12228
+ if (raycaster2.ray.intersectPlane(plane, intersectionPoint))
12229
+ relink.line.geometry.setPositions([
12230
+ ...fixedMesh.position.toArray(),
12231
+ ...intersectionPoint.toArray()
12232
+ ]);
11705
12233
  }
11706
12234
  }
11707
12235
  Object.values(stateRef.current.nodeObjects).forEach((node) => {
@@ -11745,11 +12273,18 @@ function XViewScene({
11745
12273
  renderer.setSize(clientWidth, clientHeight);
11746
12274
  composer.setSize(clientWidth, clientHeight);
11747
12275
  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);
12276
+ stateRef.current.allLinks.forEach(
12277
+ (line) => line.material.resolution.copy(resVec)
12278
+ );
12279
+ stateRef.current.ancestryLinks.forEach(
12280
+ (line) => line.material.resolution.copy(resVec)
12281
+ );
12282
+ if (stateRef.current.ghostElements.line)
12283
+ stateRef.current.ghostElements.line.material.resolution.copy(resVec);
12284
+ if (stateRef.current.connection.line)
12285
+ stateRef.current.connection.line.material.resolution.copy(resVec);
12286
+ if (stateRef.current.relink.line)
12287
+ stateRef.current.relink.line.material.resolution.copy(resVec);
11753
12288
  }
11754
12289
  window.addEventListener("resize", handleResize);
11755
12290
  return () => {
@@ -11769,14 +12304,16 @@ function XViewScene({
11769
12304
  ancestryGroup.traverse((obj) => {
11770
12305
  if (obj.geometry) obj.geometry.dispose();
11771
12306
  if (obj.material) {
11772
- if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
12307
+ if (Array.isArray(obj.material))
12308
+ obj.material.forEach((m) => m.dispose());
11773
12309
  else obj.material.dispose();
11774
12310
  }
11775
12311
  });
11776
12312
  graphGroup.traverse((obj) => {
11777
12313
  if (obj.geometry) obj.geometry.dispose();
11778
12314
  if (obj.material) {
11779
- if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
12315
+ if (Array.isArray(obj.material))
12316
+ obj.material.forEach((m) => m.dispose());
11780
12317
  else obj.material.dispose();
11781
12318
  }
11782
12319
  });
@@ -11787,9 +12324,22 @@ function XViewScene({
11787
12324
  currentMount.removeChild(renderer.domElement);
11788
12325
  }
11789
12326
  };
11790
- }, [isInitialized, tweenToTarget, dbSaveUrl, isNodeInView, addOrUpdateNodeMesh, handleActivateTimeline, get_scene_view_data, save_view_data]);
12327
+ }, [
12328
+ isInitialized,
12329
+ tweenToTarget,
12330
+ dbSaveUrl,
12331
+ isNodeInView,
12332
+ addOrUpdateNodeMesh,
12333
+ handleActivateTimeline,
12334
+ get_scene_view_data,
12335
+ save_view_data
12336
+ ]);
11791
12337
  const handleGhostNodeImageChange = (0, import_react26.useCallback)((useImage, imageUrl) => {
11792
- const { node: ghostNode, line: ghostLine, aura: ghostAura } = stateRef.current.ghostElements;
12338
+ const {
12339
+ node: ghostNode,
12340
+ line: ghostLine,
12341
+ aura: ghostAura
12342
+ } = stateRef.current.ghostElements;
11793
12343
  const { graphGroup, glowTexture } = stateRef.current;
11794
12344
  if (!ghostNode || !graphGroup) return;
11795
12345
  const currentData = {
@@ -11800,13 +12350,15 @@ function XViewScene({
11800
12350
  const position = ghostNode.position.clone();
11801
12351
  if (ghostNode.userData.labelObject) {
11802
12352
  graphGroup.remove(ghostNode.userData.labelObject);
11803
- if (ghostNode.userData.labelObject.material.map) ghostNode.userData.labelObject.material.map.dispose();
12353
+ if (ghostNode.userData.labelObject.material.map)
12354
+ ghostNode.userData.labelObject.material.map.dispose();
11804
12355
  ghostNode.userData.labelObject.material.dispose();
11805
12356
  }
11806
12357
  graphGroup.remove(ghostNode);
11807
12358
  if (ghostNode.geometry) ghostNode.geometry.dispose();
11808
12359
  if (ghostNode.material) {
11809
- if (Array.isArray(ghostNode.material)) ghostNode.material.forEach((m) => m.dispose());
12360
+ if (Array.isArray(ghostNode.material))
12361
+ ghostNode.material.forEach((m) => m.dispose());
11810
12362
  else ghostNode.material.dispose();
11811
12363
  }
11812
12364
  const newGhostNode = createNodeMesh(currentData, position, glowTexture);
@@ -11851,28 +12403,31 @@ function XViewScene({
11851
12403
  ghostAura.material.opacity = Math.min(0.8, newIntensity * 0.15);
11852
12404
  }
11853
12405
  }, []);
11854
- const handleDetailNodeIntensityChange = (0, import_react26.useCallback)((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;
12406
+ const handleDetailNodeIntensityChange = (0, import_react26.useCallback)(
12407
+ (nodeId, newIntensity) => {
12408
+ const mesh = stateRef.current.nodeObjects[String(nodeId)];
12409
+ if (!mesh) return;
12410
+ const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
12411
+ mesh.userData.intensity = newIntensity;
12412
+ mesh.userData._baseEmissiveIntensity = adjustedIntensity;
12413
+ const isImageNode = mesh.userData.useImageAsTexture === true;
12414
+ if (isImageNode) {
12415
+ const borderMesh = mesh.getObjectByName("borderRing");
12416
+ if (borderMesh && borderMesh.material) {
12417
+ borderMesh.material.emissiveIntensity = adjustedIntensity;
12418
+ }
12419
+ } else {
12420
+ if (mesh.material) {
12421
+ mesh.material.emissiveIntensity = adjustedIntensity;
12422
+ }
11865
12423
  }
11866
- } else {
11867
- if (mesh.material) {
11868
- mesh.material.emissiveIntensity = adjustedIntensity;
12424
+ const aura = mesh.getObjectByName("aura");
12425
+ if (aura && aura.material) {
12426
+ aura.material.opacity = Math.min(0.8, newIntensity * 0.15);
11869
12427
  }
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
- }, []);
12428
+ },
12429
+ []
12430
+ );
11876
12431
  const handleGhostNodeColorChange = (newColor) => {
11877
12432
  const { node: ghostNode, aura: ghostAura } = stateRef.current.ghostElements;
11878
12433
  if (!ghostNode) return;
@@ -11983,7 +12538,9 @@ function XViewScene({
11983
12538
  mesh.userData.size = newSize;
11984
12539
  };
11985
12540
  const handleStartAncestryCreation = (nodeData) => {
11986
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12541
+ setContextMenu(
12542
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12543
+ );
11987
12544
  stateRef.current.maxAncestryRenderIndex = 0;
11988
12545
  setAncestryMode({
11989
12546
  isActive: true,
@@ -12008,9 +12565,12 @@ function XViewScene({
12008
12565
  const newTreeStr = JSON.stringify(newTree);
12009
12566
  let metaChanged = false;
12010
12567
  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;
12568
+ if (extraData.ancestryName !== void 0 && extraData.ancestryName !== prev.ancestryName)
12569
+ metaChanged = true;
12570
+ if (extraData.ancestryDescription !== void 0 && extraData.ancestryDescription !== prev.ancestryDescription)
12571
+ metaChanged = true;
12572
+ if (extraData.ancestryDescriptionSections !== void 0 && JSON.stringify(extraData.ancestryDescriptionSections) !== JSON.stringify(prev.ancestryDescriptionSections))
12573
+ metaChanged = true;
12014
12574
  }
12015
12575
  if (prevTreeStr === newTreeStr && !metaChanged) {
12016
12576
  return prev;
@@ -12088,13 +12648,15 @@ function XViewScene({
12088
12648
  if (ghostElements.node && graphGroup) {
12089
12649
  if (ghostElements.node.userData.labelObject) {
12090
12650
  graphGroup.remove(ghostElements.node.userData.labelObject);
12091
- if (ghostElements.node.userData.labelObject.material.map) ghostElements.node.userData.labelObject.material.map.dispose();
12651
+ if (ghostElements.node.userData.labelObject.material.map)
12652
+ ghostElements.node.userData.labelObject.material.map.dispose();
12092
12653
  ghostElements.node.userData.labelObject.material.dispose();
12093
12654
  }
12094
12655
  graphGroup.remove(ghostElements.node);
12095
12656
  ghostElements.node.traverse((child) => {
12096
12657
  if (child.material) {
12097
- if (Array.isArray(child.material)) child.material.forEach((m) => m.dispose());
12658
+ if (Array.isArray(child.material))
12659
+ child.material.forEach((m) => m.dispose());
12098
12660
  else child.material.dispose();
12099
12661
  }
12100
12662
  if (child.geometry) child.geometry.dispose();
@@ -12104,7 +12666,17 @@ function XViewScene({
12104
12666
  setQuestMode({ isActive: false });
12105
12667
  }, []);
12106
12668
  const handleSaveQuestNode = async (context, newQuestData) => {
12107
- const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
12669
+ const {
12670
+ graphDataRef,
12671
+ sceneDataRef: sceneDataRef2,
12672
+ stateRef: stateRef2,
12673
+ setters,
12674
+ actions,
12675
+ sceneSaveUrl: sceneSaveUrl2,
12676
+ viewType,
12677
+ sceneConfigId: sceneConfigId2,
12678
+ ownerId: ownerId2
12679
+ } = context;
12108
12680
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
12109
12681
  const currentCounter = sceneDataRef2.current.quest_counter || 1;
12110
12682
  const newNode = {
@@ -12137,7 +12709,8 @@ function XViewScene({
12137
12709
  const finalPosition = stateRef2.current.ghostElements.node ? stateRef2.current.ghostElements.node.position.clone() : stateRef2.current.controls.target.clone();
12138
12710
  const { graphGroup, ghostElements } = stateRef2.current;
12139
12711
  if (ghostElements.node && graphGroup) {
12140
- if (ghostElements.node.userData.labelObject) graphGroup.remove(ghostElements.node.userData.labelObject);
12712
+ if (ghostElements.node.userData.labelObject)
12713
+ graphGroup.remove(ghostElements.node.userData.labelObject);
12141
12714
  graphGroup.remove(ghostElements.node);
12142
12715
  }
12143
12716
  stateRef2.current.ghostElements = { node: null, line: null, aura: null };
@@ -12151,14 +12724,33 @@ function XViewScene({
12151
12724
  }
12152
12725
  };
12153
12726
  userActionHandlers.handleCompleteConnection = async (context, targetNodeData) => {
12154
- const { stateRef: stateRef2, graphDataRef, sceneDataRef: sceneDataRef2, sceneConfigId: sceneConfigId2, sceneSaveUrl: sceneSaveUrl2, ownerId: ownerId2 } = context;
12727
+ const {
12728
+ stateRef: stateRef2,
12729
+ graphDataRef,
12730
+ sceneDataRef: sceneDataRef2,
12731
+ sceneConfigId: sceneConfigId2,
12732
+ sceneSaveUrl: sceneSaveUrl2,
12733
+ ownerId: ownerId2
12734
+ } = context;
12155
12735
  const { sourceNodeData } = stateRef2.current.connection;
12156
12736
  if (!graphDataRef.current || !sceneDataRef2.current || !sourceNodeData || !targetNodeData) {
12157
12737
  userActionHandlers.handleCancelConnection(context);
12158
12738
  return;
12159
12739
  }
12160
- const sourceParentInfo = getParentFileInfoForNode(graphDataRef.current, sceneDataRef2.current, sourceNodeData.id, sceneConfigId2, ownerId2);
12161
- const targetParentInfo = getParentFileInfoForNode(graphDataRef.current, sceneDataRef2.current, targetNodeData.id, sceneConfigId2, ownerId2);
12740
+ const sourceParentInfo = getParentFileInfoForNode(
12741
+ graphDataRef.current,
12742
+ sceneDataRef2.current,
12743
+ sourceNodeData.id,
12744
+ sceneConfigId2,
12745
+ ownerId2
12746
+ );
12747
+ const targetParentInfo = getParentFileInfoForNode(
12748
+ graphDataRef.current,
12749
+ sceneDataRef2.current,
12750
+ targetNodeData.id,
12751
+ sceneConfigId2,
12752
+ ownerId2
12753
+ );
12162
12754
  let parentInfoToSave = sourceParentInfo;
12163
12755
  const isSourceQuest = sourceParentInfo.parentFileId === sceneConfigId2;
12164
12756
  const isTargetQuest = targetParentInfo.parentFileId === sceneConfigId2;
@@ -12184,10 +12776,15 @@ function XViewScene({
12184
12776
  };
12185
12777
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
12186
12778
  } else {
12187
- const specificParentData = JSON.parse(JSON.stringify(graphDataRef.current[parentFileIdToSave]));
12779
+ const specificParentData = JSON.parse(
12780
+ JSON.stringify(graphDataRef.current[parentFileIdToSave])
12781
+ );
12188
12782
  specificParentData.links.push(newLink);
12189
12783
  const filenameForSpecificParent = `x_view_dbs/${ownerIdToSave}/${parentFileIdToSave}`;
12190
- await context.actions.save_view_data(filenameForSpecificParent, specificParentData);
12784
+ await context.actions.save_view_data(
12785
+ filenameForSpecificParent,
12786
+ specificParentData
12787
+ );
12191
12788
  graphDataRef.current[parentFileIdToSave] = specificParentData;
12192
12789
  }
12193
12790
  addNewLinkToScene(stateRef2.current, newLink);
@@ -12199,7 +12796,9 @@ function XViewScene({
12199
12796
  };
12200
12797
  const handleClearAncestryVisuals = (0, import_react26.useCallback)((ancestryId) => {
12201
12798
  const { renderedAncestries, ancestryGroup } = stateRef.current;
12202
- const renderIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryId));
12799
+ const renderIndex = renderedAncestries.findIndex(
12800
+ (a) => String(a.id) === String(ancestryId)
12801
+ );
12203
12802
  if (renderIndex !== -1) {
12204
12803
  const toRemove = renderedAncestries[renderIndex];
12205
12804
  toRemove.lines.forEach((line) => {
@@ -12208,12 +12807,16 @@ function XViewScene({
12208
12807
  if (line.material) line.material.dispose();
12209
12808
  });
12210
12809
  renderedAncestries.splice(renderIndex, 1);
12211
- stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
12810
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
12811
+ (a) => a.lines
12812
+ );
12212
12813
  }
12213
12814
  }, []);
12214
12815
  const handleRenderAncestry = (0, import_react26.useCallback)(
12215
12816
  async (ancestryObject, allowedSectionIds = null, activeSectionIdForFocus = null, baseRotation = 0, forceReprocess = true) => {
12216
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
12817
+ setContextMenu(
12818
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
12819
+ );
12217
12820
  if (!ancestryObject || !ancestryObject.tree) {
12218
12821
  return;
12219
12822
  }
@@ -12240,14 +12843,16 @@ function XViewScene({
12240
12843
  if (numId !== void 0 && normalizedAllowedIds.has(numId)) return true;
12241
12844
  if (strId && normalizedAllowedIds.has(strId)) return true;
12242
12845
  if (strSecId && normalizedAllowedIds.has(strSecId)) return true;
12243
- if (numId !== void 0 && normalizedAllowedIds.has(String(numId))) return true;
12846
+ if (numId !== void 0 && normalizedAllowedIds.has(String(numId)))
12847
+ return true;
12244
12848
  return false;
12245
12849
  };
12246
12850
  const checkIsActive = (section, targetId) => {
12247
12851
  if (targetId === null || targetId === void 0) return false;
12248
12852
  const sTarget = String(targetId);
12249
12853
  if (section.id && String(section.id) === sTarget) return true;
12250
- if (section.section_id && String(section.section_id) === sTarget) return true;
12854
+ if (section.section_id && String(section.section_id) === sTarget)
12855
+ return true;
12251
12856
  if (section.section_numeric_id !== void 0) {
12252
12857
  if (String(section.section_numeric_id) === sTarget) return true;
12253
12858
  }
@@ -12263,7 +12868,9 @@ function XViewScene({
12263
12868
  traverse(sectionTree);
12264
12869
  return ids;
12265
12870
  };
12266
- const existingIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryObject.ancestry_id));
12871
+ const existingIndex = renderedAncestries.findIndex(
12872
+ (a) => String(a.id) === String(ancestryObject.ancestry_id)
12873
+ );
12267
12874
  let skipGeneration = false;
12268
12875
  let ancestryEntry = null;
12269
12876
  if (existingIndex !== -1) {
@@ -12318,9 +12925,15 @@ function XViewScene({
12318
12925
  if (line.material) line.material.dispose();
12319
12926
  });
12320
12927
  }
12321
- const allParentNodes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes);
12928
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
12929
+ (fileData) => fileData.nodes
12930
+ );
12322
12931
  const allAncestries = ancestryDataRef.current || [];
12323
- const fullTree = buildFullAncestryTree(ancestryObject.tree, allParentNodes, allAncestries);
12932
+ const fullTree = buildFullAncestryTree(
12933
+ ancestryObject.tree,
12934
+ allParentNodes,
12935
+ allAncestries
12936
+ );
12324
12937
  if (!fullTree) return;
12325
12938
  const rootNodeId = String(ancestryObject.ancestral_node);
12326
12939
  let rootNodeMesh = nodeObjects[rootNodeId];
@@ -12363,7 +12976,10 @@ function XViewScene({
12363
12976
  const colorIndex = stateRef.current.ancestryRenderCounter % 3;
12364
12977
  colorHex = ANCESTRY_COLORS[colorIndex];
12365
12978
  }
12366
- const resolution = new THREE3.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
12979
+ const resolution = new THREE3.Vector2(
12980
+ renderer.domElement.clientWidth,
12981
+ renderer.domElement.clientHeight
12982
+ );
12367
12983
  const cleanupLinesForNode = (nodeId) => {
12368
12984
  if (!ancestryEntry || !ancestryEntry.lines) return;
12369
12985
  const linesKeep = [];
@@ -12403,7 +13019,13 @@ function XViewScene({
12403
13019
  }
12404
13020
  if (originMesh && rootNodeMesh) {
12405
13021
  cleanupLinesForNode(rootNodeId);
12406
- const branchLine = createAncestryLinkLine(originMesh, rootNodeMesh, resolution, {}, colorHex);
13022
+ const branchLine = createAncestryLinkLine(
13023
+ originMesh,
13024
+ rootNodeMesh,
13025
+ resolution,
13026
+ {},
13027
+ colorHex
13028
+ );
12407
13029
  ancestryGroup.add(branchLine);
12408
13030
  ancestryEntry.lines.push(branchLine);
12409
13031
  }
@@ -12414,8 +13036,14 @@ function XViewScene({
12414
13036
  if (!children || children.length === 0) return null;
12415
13037
  let lastRenderedMesh = null;
12416
13038
  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);
13039
+ const forwardVec = new THREE3.Vector3(0, 0, 1).applyAxisAngle(
13040
+ new THREE3.Vector3(0, 1, 0),
13041
+ currentAngle
13042
+ );
13043
+ const rightVec = new THREE3.Vector3(1, 0, 0).applyAxisAngle(
13044
+ new THREE3.Vector3(0, 1, 0),
13045
+ currentAngle
13046
+ );
12419
13047
  const angleRange = numChildren > 3 ? Math.PI : Math.PI / 1.5;
12420
13048
  children.forEach((childItem, index) => {
12421
13049
  if (childItem.is_section) return;
@@ -12436,14 +13064,30 @@ function XViewScene({
12436
13064
  targetPositionsCache.set(sNodeId, childPosition.clone());
12437
13065
  cleanupLinesForNode(sNodeId);
12438
13066
  }
12439
- const childMesh = addOrUpdateNodeMesh(childItem.node, childPosition, true);
13067
+ const childMesh = addOrUpdateNodeMesh(
13068
+ childItem.node,
13069
+ childPosition,
13070
+ true
13071
+ );
12440
13072
  allRenderedNodePositions.push(childPosition);
12441
- const line = createAncestryLinkLine(parentMesh, childMesh, resolution, childItem.relationship, colorHex);
13073
+ const line = createAncestryLinkLine(
13074
+ parentMesh,
13075
+ childMesh,
13076
+ resolution,
13077
+ childItem.relationship,
13078
+ colorHex
13079
+ );
12442
13080
  ancestryGroup.add(line);
12443
13081
  ancestryEntry.lines.push(line);
12444
13082
  lastRenderedMesh = childMesh;
12445
13083
  if (childItem.children && childItem.children.length > 0) {
12446
- const lastDescendant = renderCluster(childItem.children, childMesh, childPosition, level + 1, currentAngle);
13084
+ const lastDescendant = renderCluster(
13085
+ childItem.children,
13086
+ childMesh,
13087
+ childPosition,
13088
+ level + 1,
13089
+ currentAngle
13090
+ );
12447
13091
  if (lastDescendant) lastRenderedMesh = lastDescendant;
12448
13092
  }
12449
13093
  });
@@ -12457,9 +13101,13 @@ function XViewScene({
12457
13101
  else looseNodes.push(child);
12458
13102
  });
12459
13103
  }
12460
- sections.sort((a, b) => (a.section_numeric_id || 0) - (b.section_numeric_id || 0));
13104
+ sections.sort(
13105
+ (a, b) => (a.section_numeric_id || 0) - (b.section_numeric_id || 0)
13106
+ );
12461
13107
  let combinedStartNodes = [...looseNodes];
12462
- let session0Index = sections.findIndex((s) => s.section_numeric_id === 0 || s.name === "Sess\xE3o 0");
13108
+ let session0Index = sections.findIndex(
13109
+ (s) => s.section_numeric_id === 0 || s.name === "Sess\xE3o 0"
13110
+ );
12463
13111
  if (session0Index !== -1) {
12464
13112
  const session0 = sections[session0Index];
12465
13113
  if (checkShouldRender(session0) && session0.children) {
@@ -12468,7 +13116,13 @@ function XViewScene({
12468
13116
  sections.splice(session0Index, 1);
12469
13117
  }
12470
13118
  const rootTargetPos2 = targetPositionsCache.get(rootNodeId) || rootNodeMesh.position;
12471
- const lastStartMesh = renderCluster(combinedStartNodes, rootNodeMesh, rootTargetPos2, 1, baseRotation);
13119
+ const lastStartMesh = renderCluster(
13120
+ combinedStartNodes,
13121
+ rootNodeMesh,
13122
+ rootTargetPos2,
13123
+ 1,
13124
+ baseRotation
13125
+ );
12472
13126
  if (lastStartMesh) {
12473
13127
  currentAnchorMesh = lastStartMesh;
12474
13128
  }
@@ -12479,10 +13133,18 @@ function XViewScene({
12479
13133
  const sectionNodes = section.children || [];
12480
13134
  if (sectionNodes.length > 0) {
12481
13135
  const parentAngle = nodeRotationMap.get(String(currentAnchorMesh.userData.id)) ?? baseRotation;
12482
- const lastMeshOfThisSection = renderCluster(sectionNodes, currentAnchorMesh, currentAnchorPosition, 1, parentAngle);
13136
+ const lastMeshOfThisSection = renderCluster(
13137
+ sectionNodes,
13138
+ currentAnchorMesh,
13139
+ currentAnchorPosition,
13140
+ 1,
13141
+ parentAngle
13142
+ );
12483
13143
  if (lastMeshOfThisSection) {
12484
13144
  currentAnchorMesh = lastMeshOfThisSection;
12485
- currentAnchorPosition = targetPositionsCache.get(String(lastMeshOfThisSection.userData.id)) || lastMeshOfThisSection.position;
13145
+ currentAnchorPosition = targetPositionsCache.get(
13146
+ String(lastMeshOfThisSection.userData.id)
13147
+ ) || lastMeshOfThisSection.position;
12486
13148
  }
12487
13149
  }
12488
13150
  }
@@ -12511,7 +13173,10 @@ function XViewScene({
12511
13173
  } else {
12512
13174
  const directChildren = fullTree.children ? fullTree.children.filter((c) => !c.is_section) : [];
12513
13175
  if (directChildren.length > 0) {
12514
- targetSectionForZone = { name: "In\xEDcio", children: directChildren };
13176
+ targetSectionForZone = {
13177
+ name: "In\xEDcio",
13178
+ children: directChildren
13179
+ };
12515
13180
  }
12516
13181
  }
12517
13182
  } else {
@@ -12585,7 +13250,10 @@ function XViewScene({
12585
13250
  stateRef.current.ancestryLinks = stateRef.current.renderedAncestries.flatMap((a) => a.lines);
12586
13251
  focusTargetPosition = center;
12587
13252
  focusTargetRotation = baseRotation;
12588
- const desiredDistance = Math.max(x_view_config.CAMERA_ZOOM_DISTANCE, radius * 2.8);
13253
+ const desiredDistance = Math.max(
13254
+ x_view_config.CAMERA_ZOOM_DISTANCE,
13255
+ radius * 2.8
13256
+ );
12589
13257
  focusZoomFactor = x_view_config.CAMERA_ZOOM_DISTANCE / desiredDistance;
12590
13258
  } else {
12591
13259
  const anchorId = String(currentAnchorMesh.userData.id);
@@ -12600,13 +13268,18 @@ function XViewScene({
12600
13268
  }
12601
13269
  if (!focusTargetPosition && !allowedSectionIds) {
12602
13270
  if (allRenderedNodePositions.length > 0) {
12603
- const boundingBox = new THREE3.Box3().setFromPoints(allRenderedNodePositions);
13271
+ const boundingBox = new THREE3.Box3().setFromPoints(
13272
+ allRenderedNodePositions
13273
+ );
12604
13274
  const center = new THREE3.Vector3();
12605
13275
  boundingBox.getCenter(center);
12606
13276
  const size = new THREE3.Vector3();
12607
13277
  boundingBox.getSize(size);
12608
13278
  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));
13279
+ const fitZoom = Math.min(
13280
+ 1,
13281
+ x_view_config.CAMERA_ZOOM_DISTANCE / (maxDim * 1.5)
13282
+ );
12610
13283
  const defaultBase = new THREE3.Vector3(-50, 40, 20);
12611
13284
  const rotatedCinematicAngle = defaultBase.clone().applyAxisAngle(new THREE3.Vector3(0, 1, 0), baseRotation);
12612
13285
  tweenToTarget(center, fitZoom, rotatedCinematicAngle);
@@ -12625,210 +13298,280 @@ function XViewScene({
12625
13298
  tweenToTarget(targetPos, focusZoomFactor, rotatedCinematicAngle);
12626
13299
  }
12627
13300
  },
12628
- [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, readingMode.isActive, ancestryMode.isActive]
13301
+ [
13302
+ addOrUpdateNodeMesh,
13303
+ tweenToTarget,
13304
+ buildFullAncestryTree,
13305
+ readingMode.isActive,
13306
+ ancestryMode.isActive
13307
+ ]
12629
13308
  );
12630
- const handleRenderAbstractionTree = (0, import_react26.useCallback)((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
- }
13309
+ const handleRenderAbstractionTree = (0, import_react26.useCallback)(
13310
+ (ancestryObject, targetNodeId = null) => {
13311
+ setContextMenu(
13312
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13313
+ );
13314
+ if (!ancestryObject || !ancestryObject.abstraction_tree) return;
13315
+ const { ancestryGroup, nodeObjects, renderer, renderedAncestries } = stateRef.current;
13316
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
13317
+ (f) => f.nodes
13318
+ );
13319
+ let fullTree = buildFullAncestryTree(
13320
+ ancestryObject.abstraction_tree,
13321
+ allParentNodes,
13322
+ ancestryDataRef.current
13323
+ );
13324
+ if (!fullTree || !fullTree.node) return;
13325
+ if (targetNodeId) {
13326
+ const pruneTreeToPath = (treeNode, targetId) => {
13327
+ var _a2;
13328
+ if (!treeNode) return null;
13329
+ const currentId = treeNode.is_section ? treeNode.section_id : String((_a2 = treeNode.node) == null ? void 0 : _a2.id);
13330
+ if (String(currentId) === String(targetId)) {
13331
+ return { ...treeNode, children: [] };
12651
13332
  }
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 = (0, import_react26.useCallback)((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));
13333
+ if (treeNode.children && treeNode.children.length > 0) {
13334
+ for (let child of treeNode.children) {
13335
+ const prunedChild = pruneTreeToPath(child, targetId);
13336
+ if (prunedChild) {
13337
+ return { ...treeNode, children: [prunedChild] };
12739
13338
  }
12740
13339
  }
12741
13340
  }
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
- }
13341
+ return null;
13342
+ };
13343
+ const pruned = pruneTreeToPath(fullTree, targetNodeId);
13344
+ if (pruned) fullTree = pruned;
13345
+ }
13346
+ const absId = ancestryObject.ancestry_id + "_abs";
13347
+ handleClearAncestryVisuals(absId);
13348
+ const colorHex = 9133302;
13349
+ const resolution = new THREE3.Vector2(
13350
+ renderer.domElement.clientWidth,
13351
+ renderer.domElement.clientHeight
13352
+ );
13353
+ const ancestryEntry = { id: absId, lines: [], isFullRender: true };
13354
+ renderedAncestries.push(ancestryEntry);
13355
+ const rootNodeId = String(fullTree.node.id);
13356
+ let rootNodeMesh = nodeObjects[rootNodeId];
13357
+ let rootTargetPos = rootNodeMesh ? rootNodeMesh.position.clone() : new THREE3.Vector3(0, 0, 0);
13358
+ if (!rootNodeMesh) {
13359
+ rootNodeMesh = addOrUpdateNodeMesh(fullTree.node, rootTargetPos, true);
12754
13360
  }
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);
13361
+ const SPACING_Y = -40;
13362
+ const SPACING_X = 55;
13363
+ const renderVertical = (treeNode, parentMesh, parentPos, level) => {
13364
+ if (!treeNode.children || treeNode.children.length === 0) return;
13365
+ const totalSiblings = treeNode.children.length;
13366
+ treeNode.children.forEach((childItem, i) => {
13367
+ if (!childItem.node) return;
13368
+ const childX = parentPos.x + (i - (totalSiblings - 1) / 2) * (SPACING_X / Math.max(1, level * 0.4));
13369
+ const childY = parentPos.y + SPACING_Y;
13370
+ const childPos = new THREE3.Vector3(childX, childY, parentPos.z);
13371
+ const childMesh = addOrUpdateNodeMesh(childItem.node, childPos, true);
13372
+ const line = createAncestryLinkLine(
13373
+ parentMesh,
13374
+ childMesh,
13375
+ resolution,
13376
+ {},
13377
+ colorHex
13378
+ );
13379
+ ancestryGroup.add(line);
13380
+ ancestryEntry.lines.push(line);
13381
+ renderVertical(childItem, childMesh, childPos, level + 1);
13382
+ });
13383
+ };
13384
+ renderVertical(fullTree, rootNodeMesh, rootTargetPos, 1);
13385
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
13386
+ (a) => a.lines
13387
+ );
13388
+ tweenToTarget(rootTargetPos, 0.7);
13389
+ },
13390
+ [
13391
+ addOrUpdateNodeMesh,
13392
+ tweenToTarget,
13393
+ buildFullAncestryTree,
13394
+ handleClearAncestryVisuals
13395
+ ]
13396
+ );
13397
+ const handleReadModeBranchNav = (0, import_react26.useCallback)(
13398
+ (nodeId, action, direction = "right") => {
13399
+ const { ancestry, branchStack } = readingMode;
13400
+ if (!ancestry || !ancestry.tree) return;
13401
+ const allAncestries = ancestryDataRef.current || [];
13402
+ const fullTree = buildFullAncestryTree(
13403
+ ancestry.tree,
13404
+ Object.values(parentDataRef.current).flatMap((f) => f.nodes),
13405
+ allAncestries
13406
+ );
13407
+ if (action === "open") {
13408
+ let currentPtr = fullTree;
13409
+ for (const step of branchStack) {
13410
+ const found = findNodePath3(currentPtr, step.nodeId);
12771
13411
  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 };
13412
+ const branch = found.node.parallel_branches.find(
13413
+ (b) => b.id === step.branchId
13414
+ );
13415
+ if (branch) currentPtr = branch.tree;
13416
+ }
13417
+ }
13418
+ const foundTarget = findNodePath3(currentPtr, nodeId);
13419
+ if (foundTarget && foundTarget.node && foundTarget.node.parallel_branches && foundTarget.node.parallel_branches.length > 0) {
13420
+ const branchToOpen = foundTarget.node.parallel_branches.find(
13421
+ (b) => (b.direction || "right") === direction
13422
+ );
13423
+ if (!branchToOpen) return;
13424
+ if (branchToOpen && branchToOpen.tree) {
13425
+ const parentIndexToSave = stateRef.current.readMode.currentMaxIndex;
13426
+ const savedBranchProgress = 0;
13427
+ stateRef.current.readMode.currentMaxIndex = savedBranchProgress;
13428
+ stateRef.current.maxAncestryRenderIndex = savedBranchProgress;
13429
+ const newStack = [
13430
+ ...branchStack,
13431
+ {
13432
+ nodeId,
13433
+ branchId: branchToOpen.id,
13434
+ savedMaxIndex: parentIndexToSave,
13435
+ entryDirection: direction
13436
+ }
13437
+ ];
13438
+ setReadingMode((prev) => ({
13439
+ ...prev,
13440
+ branchStack: newStack,
13441
+ initialSectionId: null
13442
+ }));
13443
+ const branchAncestryObj = {
13444
+ ancestry_id: branchToOpen.id,
13445
+ ancestral_node: branchToOpen.tree.node.id,
13446
+ name: branchToOpen.name,
13447
+ description: branchToOpen.description,
13448
+ description_sections: branchToOpen.description_sections,
13449
+ tree: branchToOpen.tree,
13450
+ _originNodeId: nodeId,
13451
+ _branchDirection: direction
13452
+ };
13453
+ const allowedIds = /* @__PURE__ */ new Set(["preamble", 0, "0"]);
13454
+ const branchSections = parseDescriptionSections(
13455
+ branchToOpen.description || "",
13456
+ branchToOpen.description_sections || []
13457
+ );
13458
+ for (let i = 0; i <= savedBranchProgress; i++) {
13459
+ if (branchSections[i]) {
13460
+ if (branchSections[i].id)
13461
+ allowedIds.add(String(branchSections[i].id));
13462
+ if (branchSections[i].numericId !== void 0 && branchSections[i].numericId !== null) {
13463
+ allowedIds.add(branchSections[i].numericId);
13464
+ allowedIds.add(String(branchSections[i].numericId));
13465
+ }
13466
+ }
12776
13467
  }
13468
+ const rotation = newStack.reduce((acc, step) => {
13469
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13470
+ }, 0);
13471
+ const initialFocusId = "preamble";
13472
+ handleRenderAncestry(
13473
+ branchAncestryObj,
13474
+ allowedIds,
13475
+ initialFocusId,
13476
+ rotation,
13477
+ false
13478
+ );
12777
13479
  }
12778
13480
  }
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;
13481
+ } else if (action === "back") {
13482
+ if (branchStack.length === 0) return;
13483
+ const newStack = [...branchStack];
13484
+ const popped = newStack.pop();
13485
+ if (popped && popped.branchId) {
13486
+ stateRef.current.readMode.progressMap[popped.branchId] = stateRef.current.readMode.currentMaxIndex;
13487
+ }
13488
+ const parentSavedIndex = popped.savedMaxIndex !== void 0 ? popped.savedMaxIndex : 0;
13489
+ stateRef.current.readMode.currentMaxIndex = parentSavedIndex;
13490
+ stateRef.current.maxAncestryRenderIndex = parentSavedIndex;
13491
+ let targetTreeToRender = fullTree;
13492
+ let targetAncestryInfo = ancestry;
13493
+ if (newStack.length > 0) {
13494
+ let ptr = fullTree;
13495
+ for (const step of newStack) {
13496
+ const found = findNodePath3(ptr, step.nodeId);
13497
+ if (found && found.node.parallel_branches) {
13498
+ const branch = found.node.parallel_branches.find(
13499
+ (b) => b.id === step.branchId
13500
+ );
13501
+ if (branch) {
13502
+ ptr = branch.tree;
13503
+ targetAncestryInfo = {
13504
+ ...branch,
13505
+ ancestry_id: branch.id,
13506
+ ancestral_node: branch.tree.node.id
13507
+ };
13508
+ }
13509
+ }
13510
+ }
13511
+ targetTreeToRender = ptr;
13512
+ }
13513
+ const activeParentStackItem = newStack.length > 0 ? newStack[newStack.length - 1] : null;
13514
+ const parentAncestryObj = {
13515
+ ...targetAncestryInfo,
13516
+ tree: targetTreeToRender,
13517
+ _originNodeId: activeParentStackItem ? activeParentStackItem.nodeId : null,
13518
+ _branchDirection: activeParentStackItem ? activeParentStackItem.entryDirection : null
13519
+ };
13520
+ const descriptionText = targetAncestryInfo.description || "";
13521
+ const savedSections = targetAncestryInfo.description_sections || [];
13522
+ const sections = parseDescriptionSections(
13523
+ descriptionText,
13524
+ savedSections
13525
+ );
13526
+ let focusTargetId = null;
13527
+ if (popped && popped.nodeId) {
13528
+ const mentionTag = `[[MENTION:node:${popped.nodeId}]]`;
13529
+ for (let i = 0; i < sections.length; i++) {
13530
+ if (sections[i].content && sections[i].content.includes(mentionTag)) {
13531
+ const section = sections[i];
13532
+ focusTargetId = section.numericId !== void 0 ? section.numericId : section.id;
13533
+ break;
13534
+ }
12799
13535
  }
12800
13536
  }
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));
13537
+ const allowedIds = /* @__PURE__ */ new Set();
13538
+ allowedIds.add("preamble");
13539
+ allowedIds.add(0);
13540
+ allowedIds.add("0");
13541
+ for (let i = 0; i <= parentSavedIndex; i++) {
13542
+ if (sections[i]) {
13543
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13544
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13545
+ allowedIds.add(sections[i].numericId);
13546
+ allowedIds.add(String(sections[i].numericId));
13547
+ }
12812
13548
  }
12813
13549
  }
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);
13550
+ const rotation = newStack.reduce((acc, step) => {
13551
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13552
+ }, 0);
13553
+ handleRenderAncestry(
13554
+ parentAncestryObj,
13555
+ allowedIds,
13556
+ focusTargetId,
13557
+ rotation,
13558
+ false
13559
+ );
13560
+ if (popped && popped.nodeId) {
13561
+ const nodeMesh = stateRef.current.nodeObjects[String(popped.nodeId)];
13562
+ if (nodeMesh) {
13563
+ tweenToTarget(nodeMesh);
13564
+ }
12823
13565
  }
13566
+ setReadingMode((prev) => ({
13567
+ ...prev,
13568
+ branchStack: newStack,
13569
+ initialSectionId: focusTargetId
13570
+ }));
12824
13571
  }
12825
- setReadingMode((prev) => ({
12826
- ...prev,
12827
- branchStack: newStack,
12828
- initialSectionId: focusTargetId
12829
- }));
12830
- }
12831
- }, [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]);
13572
+ },
13573
+ [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]
13574
+ );
12832
13575
  const handleReadModeHighlight = (0, import_react26.useCallback)((nodeId) => {
12833
13576
  if (stateRef.current.highlightedNodeId !== nodeId) {
12834
13577
  stateRef.current.highlightedNodeId = nodeId;
@@ -12836,7 +13579,8 @@ function XViewScene({
12836
13579
  setHighlightedNodeId(nodeId);
12837
13580
  }, []);
12838
13581
  const activeNodeBranches = (0, import_react26.useMemo)(() => {
12839
- if (!highlightedNodeId || !readingMode.ancestry || !readingMode.ancestry.tree) return null;
13582
+ if (!highlightedNodeId || !readingMode.ancestry || !readingMode.ancestry.tree)
13583
+ return null;
12840
13584
  const fullTree = buildFullAncestryTree(
12841
13585
  readingMode.ancestry.tree,
12842
13586
  Object.values(parentDataRef.current).flatMap((f) => f.nodes),
@@ -12871,7 +13615,13 @@ function XViewScene({
12871
13615
  };
12872
13616
  }
12873
13617
  return null;
12874
- }, [highlightedNodeId, readingMode.ancestry, buildFullAncestryTree, readingMode.branchStack, ancestryDataRef.current]);
13618
+ }, [
13619
+ highlightedNodeId,
13620
+ readingMode.ancestry,
13621
+ buildFullAncestryTree,
13622
+ readingMode.branchStack,
13623
+ ancestryDataRef.current
13624
+ ]);
12875
13625
  const backNavigationInfo = (0, import_react26.useMemo)(() => {
12876
13626
  const { branchStack } = readingMode;
12877
13627
  if (!branchStack || branchStack.length === 0) return null;
@@ -12907,7 +13657,9 @@ function XViewScene({
12907
13657
  for (const step of branchStack) {
12908
13658
  const found = findNodePath3(currentPtr, step.nodeId);
12909
13659
  if (found && found.node.parallel_branches) {
12910
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
13660
+ const branch = found.node.parallel_branches.find(
13661
+ (b) => b.id === step.branchId
13662
+ );
12911
13663
  if (branch) {
12912
13664
  currentPtr = branch.tree;
12913
13665
  currentMeta = branch;
@@ -12928,17 +13680,26 @@ function XViewScene({
12928
13680
  if (!readingMode.isActive || !readingMode.ancestry || !readingMode.ancestry.abstraction_tree) {
12929
13681
  return null;
12930
13682
  }
12931
- const allNodes = Object.values(parentDataRef.current || {}).flatMap((f) => f.nodes || []);
13683
+ const allNodes = Object.values(parentDataRef.current || {}).flatMap(
13684
+ (f) => f.nodes || []
13685
+ );
12932
13686
  const allAncestries = ancestryDataRef.current || [];
12933
13687
  return buildFullAncestryTree(
12934
13688
  readingMode.ancestry.abstraction_tree,
12935
13689
  allNodes,
12936
13690
  allAncestries
12937
13691
  );
12938
- }, [readingMode.isActive, readingMode.ancestry, buildFullAncestryTree, sceneVersion]);
13692
+ }, [
13693
+ readingMode.isActive,
13694
+ readingMode.ancestry,
13695
+ buildFullAncestryTree,
13696
+ sceneVersion
13697
+ ]);
12939
13698
  const handleStartReadingAncestry = (0, import_react26.useCallback)(
12940
- async (ancestryObject) => {
12941
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
13699
+ async (ancestryObject, renderMode = "full") => {
13700
+ setContextMenu(
13701
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13702
+ );
12942
13703
  if (!ancestryObject || !ancestryObject.tree) {
12943
13704
  console.warn("Ancestralidade inv\xE1lida para leitura.");
12944
13705
  return;
@@ -12951,14 +13712,20 @@ function XViewScene({
12951
13712
  const hasDescription = ancestryObject.description && ancestryObject.description.trim() !== "";
12952
13713
  const hasMainTreeNodes = ancestryObject.tree.children && ancestryObject.tree.children.length > 0;
12953
13714
  const hasAbstractionNodes = ancestryObject.abstraction_tree && ancestryObject.abstraction_tree.children && ancestryObject.abstraction_tree.children.length > 0;
12954
- const shouldAutoRenderAbstraction = !hasDescription && !hasMainTreeNodes && hasAbstractionNodes;
13715
+ const isFull = renderMode === "full";
13716
+ const isAncestryOnly = renderMode === "ancestry_only";
13717
+ const isAbstractionOnly = renderMode === "abstraction_only";
13718
+ let shouldRenderAbstraction = isAbstractionOnly;
13719
+ if (isFull) {
13720
+ shouldRenderAbstraction = !hasDescription && !hasMainTreeNodes && hasAbstractionNodes;
13721
+ }
12955
13722
  setReadingMode({
12956
- isActive: true,
13723
+ isActive: isFull ? hasDescription : false,
12957
13724
  ancestry: ancestryObject,
12958
13725
  branchStack: [],
12959
- autoAbstraction: shouldAutoRenderAbstraction
13726
+ autoAbstraction: shouldRenderAbstraction
12960
13727
  });
12961
- if (shouldAutoRenderAbstraction) {
13728
+ if (shouldRenderAbstraction) {
12962
13729
  handleRenderAbstractionTree(ancestryObject, null);
12963
13730
  } else {
12964
13731
  const initialSections = /* @__PURE__ */ new Set(["preamble", 0, "0"]);
@@ -12971,148 +13738,190 @@ function XViewScene({
12971
13738
  },
12972
13739
  [handleRenderAncestry, handleRenderAbstractionTree]
12973
13740
  );
12974
- const handleReadModeSectionChange = (0, import_react26.useCallback)((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;
13741
+ const handleReadModeSectionChange = (0, import_react26.useCallback)(
13742
+ (activeSectionId) => {
13743
+ const { ancestry, branchStack } = readingMode;
13744
+ if (!ancestry || !readingMode.isActive) return;
13745
+ let targetObj = ancestry;
13746
+ let targetTree = ancestry.tree;
13747
+ if (branchStack.length > 0) {
13748
+ const allNodes = Object.values(parentDataRef.current).flatMap(
13749
+ (f) => f.nodes
13750
+ );
13751
+ const fullTree = buildFullAncestryTree(
13752
+ ancestry.tree,
13753
+ allNodes,
13754
+ ancestryDataRef.current
13755
+ );
13756
+ let currentPtr = fullTree;
13757
+ for (const step of branchStack) {
13758
+ const found = findNodePath3(currentPtr, step.nodeId);
13759
+ if (found && found.node && found.node.parallel_branches) {
13760
+ const branch = found.node.parallel_branches.find(
13761
+ (b) => b.id === step.branchId
13762
+ );
13763
+ if (branch) {
13764
+ targetObj = branch;
13765
+ targetTree = branch.tree;
13766
+ currentPtr = branch.tree;
13767
+ }
12995
13768
  }
12996
13769
  }
12997
13770
  }
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));
13771
+ const descriptionText = targetObj.description || "";
13772
+ const savedSections = targetObj.description_sections || [];
13773
+ const sections = parseDescriptionSections(descriptionText, savedSections);
13774
+ let activeIndex = sections.findIndex(
13775
+ (s) => String(s.id) === String(activeSectionId)
13776
+ );
13777
+ if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13778
+ activeIndex = sections.findIndex(
13779
+ (s) => s.numericId === parseInt(activeSectionId)
13780
+ );
13781
+ }
13782
+ if (activeIndex === -1) activeIndex = 0;
13783
+ stateRef.current.readMode.currentMaxIndex = activeIndex;
13784
+ stateRef.current.maxAncestryRenderIndex = activeIndex;
13785
+ const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13786
+ const allowedIds = /* @__PURE__ */ new Set();
13787
+ allowedIds.add("preamble");
13788
+ allowedIds.add(0);
13789
+ allowedIds.add("0");
13790
+ for (let i = 0; i <= renderLimitIndex; i++) {
13791
+ if (sections[i]) {
13792
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13793
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13794
+ allowedIds.add(sections[i].numericId);
13795
+ allowedIds.add(String(sections[i].numericId));
13796
+ }
13020
13797
  }
13021
13798
  }
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]);
13799
+ const activeSection = sections[activeIndex];
13800
+ let focusTargetId = activeSectionId;
13801
+ if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13802
+ focusTargetId = activeSection.numericId;
13803
+ }
13804
+ const currentStackItem = branchStack.length > 0 ? branchStack[branchStack.length - 1] : null;
13805
+ const renderPayload = {
13806
+ ...targetObj,
13807
+ ancestry_id: targetObj.ancestry_id || targetObj.id,
13808
+ ancestral_node: targetTree.node ? targetTree.node.id : targetTree.node_id,
13809
+ tree: targetTree,
13810
+ _originNodeId: currentStackItem ? currentStackItem.nodeId : null,
13811
+ _branchDirection: currentStackItem ? currentStackItem.entryDirection : null,
13812
+ _forceUpdate: true
13813
+ };
13814
+ const rotation = branchStack.reduce((acc, step) => {
13815
+ return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
13816
+ }, 0);
13817
+ handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13818
+ },
13819
+ [
13820
+ readingMode,
13821
+ handleRenderAncestry,
13822
+ buildFullAncestryTree,
13823
+ ancestryDataRef.current
13824
+ ]
13825
+ );
13043
13826
  const handleCloseReadMode = (0, import_react26.useCallback)(() => {
13044
13827
  setReadingMode({ isActive: false, ancestry: null, branchStack: [] });
13045
13828
  }, []);
13046
- const handleAncestrySectionChange = (0, import_react26.useCallback)((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));
13829
+ const handleAncestrySectionChange = (0, import_react26.useCallback)(
13830
+ (activeSectionId, ancestryOverride = null, rotation = 0) => {
13831
+ var _a2, _b2;
13832
+ const currentMode = stateRef.current.ancestry;
13833
+ let targetObj = ancestryOverride;
13834
+ if (!targetObj) {
13835
+ const currentAncestryId = currentMode.currentAncestryId;
13836
+ const ancestryObj = (ancestryDataRef.current || []).find(
13837
+ (a) => String(a.ancestry_id) === String(currentAncestryId)
13838
+ );
13839
+ targetObj = ancestryObj || (currentMode.isActive ? {
13840
+ ...currentMode,
13841
+ ancestry_id: "temp_creating",
13842
+ ancestral_node: (_b2 = (_a2 = currentMode.tree) == null ? void 0 : _a2.node) == null ? void 0 : _b2.id
13843
+ } : null);
13844
+ }
13845
+ if (!targetObj) return;
13846
+ const targetId = targetObj.ancestry_id || targetObj.id;
13847
+ if (stateRef.current.lastRenderedAncestryId !== targetId) {
13848
+ stateRef.current.maxAncestryRenderIndex = 0;
13849
+ stateRef.current.lastRenderedAncestryId = targetId;
13850
+ }
13851
+ const descriptionText = (ancestryOverride ? targetObj.description : currentMode.ancestryDescription) || targetObj.description || "";
13852
+ const savedSections = (ancestryOverride ? targetObj.description_sections : currentMode.ancestryDescriptionSections) || targetObj.description_sections || [];
13853
+ const sections = parseDescriptionSections(descriptionText, savedSections);
13854
+ let activeIndex = sections.findIndex(
13855
+ (s) => String(s.id) === String(activeSectionId)
13856
+ );
13857
+ if (activeIndex === -1 && !isNaN(parseInt(activeSectionId))) {
13858
+ activeIndex = sections.findIndex(
13859
+ (s) => s.numericId === parseInt(activeSectionId)
13860
+ );
13861
+ }
13862
+ if (activeIndex === -1) activeIndex = 0;
13863
+ if (stateRef.current.maxAncestryRenderIndex === void 0)
13864
+ stateRef.current.maxAncestryRenderIndex = 0;
13865
+ stateRef.current.maxAncestryRenderIndex = activeIndex;
13866
+ const renderLimitIndex = stateRef.current.maxAncestryRenderIndex;
13867
+ const allowedIds = /* @__PURE__ */ new Set();
13868
+ allowedIds.add("preamble");
13869
+ allowedIds.add(0);
13870
+ allowedIds.add("0");
13871
+ for (let i = 0; i <= renderLimitIndex; i++) {
13872
+ if (sections[i]) {
13873
+ if (sections[i].id) allowedIds.add(String(sections[i].id));
13874
+ if (sections[i].numericId !== void 0 && sections[i].numericId !== null) {
13875
+ allowedIds.add(sections[i].numericId);
13876
+ allowedIds.add(String(sections[i].numericId));
13877
+ }
13086
13878
  }
13087
13879
  }
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]);
13880
+ const activeSection = sections[activeIndex];
13881
+ let focusTargetId = activeSectionId;
13882
+ if (activeSection && activeSection.numericId !== void 0 && activeSection.numericId !== null) {
13883
+ focusTargetId = activeSection.numericId;
13884
+ }
13885
+ const treeToRender = ancestryOverride ? ancestryOverride.tree : currentMode.isActive && currentMode.tree ? currentMode.tree : targetObj.tree;
13886
+ const renderPayload = { ...targetObj, tree: treeToRender };
13887
+ handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
13888
+ },
13889
+ [handleRenderAncestry]
13890
+ );
13098
13891
  const handleEditAncestry = (0, import_react26.useCallback)(
13099
13892
  async (ancestryObject) => {
13100
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
13893
+ setContextMenu(
13894
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
13895
+ );
13101
13896
  if (!ancestryObject || !ancestryObject.tree) {
13102
- alert("N\xE3o foi poss\xEDvel carregar os dados desta ancestralidade para edi\xE7\xE3o.");
13897
+ alert(
13898
+ "N\xE3o foi poss\xEDvel carregar os dados desta ancestralidade para edi\xE7\xE3o."
13899
+ );
13103
13900
  return;
13104
13901
  }
13105
13902
  stateRef.current.maxAncestryRenderIndex = 0;
13106
13903
  const initialSections = /* @__PURE__ */ new Set(["preamble", 0]);
13107
13904
  await handleRenderAncestry(ancestryObject, initialSections);
13108
- const allParentNodes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes);
13905
+ const allParentNodes = Object.values(parentDataRef.current).flatMap(
13906
+ (fileData) => fileData.nodes
13907
+ );
13109
13908
  const allAncestries = ancestryDataRef.current || [];
13110
- const fullTree = buildFullAncestryTree(ancestryObject.tree, allParentNodes, allAncestries);
13909
+ const fullTree = buildFullAncestryTree(
13910
+ ancestryObject.tree,
13911
+ allParentNodes,
13912
+ allAncestries
13913
+ );
13111
13914
  if (!fullTree) {
13112
- alert("Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando.");
13915
+ alert(
13916
+ "Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando."
13917
+ );
13113
13918
  return;
13114
13919
  }
13115
- const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, allAncestries) : { node: fullTree.node, children: [] };
13920
+ const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(
13921
+ ancestryObject.abstraction_tree,
13922
+ allParentNodes,
13923
+ allAncestries
13924
+ ) : { node: fullTree.node, children: [] };
13116
13925
  setAncestryMode({
13117
13926
  isActive: true,
13118
13927
  ...ancestryObject,
@@ -13164,7 +13973,9 @@ function XViewScene({
13164
13973
  const treeToUse = treeOverride || ancestryMode.tree;
13165
13974
  const { isEditMode, currentAncestryId } = ancestryMode;
13166
13975
  if (!treeToUse || !treeToUse.node) {
13167
- alert("Erro: A estrutura da ancestralidade \xE9 inv\xE1lida (Node raiz ausente).");
13976
+ alert(
13977
+ "Erro: A estrutura da ancestralidade \xE9 inv\xE1lida (Node raiz ausente)."
13978
+ );
13168
13979
  return;
13169
13980
  }
13170
13981
  if (!save_view_data || !stateRef.current.nodeIdToParentFileMap) return;
@@ -13197,14 +14008,18 @@ function XViewScene({
13197
14008
  };
13198
14009
  };
13199
14010
  const treeWithIds = convertTreeToIds(treeToUse);
13200
- const abstractionTreeWithIds = convertTreeToIds(ancestryMode.abstraction_tree);
14011
+ const abstractionTreeWithIds = convertTreeToIds(
14012
+ ancestryMode.abstraction_tree
14013
+ );
13201
14014
  if (!treeWithIds) {
13202
14015
  alert("Erro ao processar a \xE1rvore da ancestralidade.");
13203
14016
  return;
13204
14017
  }
13205
14018
  let originalAncestryObj = null;
13206
14019
  if (isEditMode && currentAncestryId) {
13207
- originalAncestryObj = (ancestryDataRef.current || []).find((anc) => String(anc.ancestry_id) === String(currentAncestryId));
14020
+ originalAncestryObj = (ancestryDataRef.current || []).find(
14021
+ (anc) => String(anc.ancestry_id) === String(currentAncestryId)
14022
+ );
13208
14023
  }
13209
14024
  const ancestryObjectToSave = {
13210
14025
  ancestry_id: isEditMode && currentAncestryId ? currentAncestryId : `${import_short_uuid2.default.generate()}`,
@@ -13287,14 +14102,20 @@ function XViewScene({
13287
14102
  try {
13288
14103
  if (isExternalSave) {
13289
14104
  try {
13290
- const remoteResponse = await get_ancestry_file(targetFileIdForCache, targetOwnerIdForCache);
14105
+ const remoteResponse = await get_ancestry_file(
14106
+ targetFileIdForCache,
14107
+ targetOwnerIdForCache
14108
+ );
13291
14109
  if (remoteResponse.success && Array.isArray(remoteResponse.data)) {
13292
14110
  masterAncestryList = remoteResponse.data;
13293
14111
  } else {
13294
14112
  masterAncestryList = [];
13295
14113
  }
13296
14114
  } catch (fetchErr) {
13297
- console.warn("Arquivo de destino n\xE3o existe ou erro ao buscar, criando novo:", fetchErr);
14115
+ console.warn(
14116
+ "Arquivo de destino n\xE3o existe ou erro ao buscar, criando novo:",
14117
+ fetchErr
14118
+ );
13298
14119
  masterAncestryList = [];
13299
14120
  }
13300
14121
  } else {
@@ -13314,7 +14135,9 @@ function XViewScene({
13314
14135
  const result = await save_view_data(finalSaveUrl, masterAncestryList);
13315
14136
  if (result.success) {
13316
14137
  const localList = [...ancestryDataRef.current || []];
13317
- const localIndex = localList.findIndex((a) => String(a.ancestry_id) === String(ancestryObjectToSave.ancestry_id));
14138
+ const localIndex = localList.findIndex(
14139
+ (a) => String(a.ancestry_id) === String(ancestryObjectToSave.ancestry_id)
14140
+ );
13318
14141
  if (localIndex !== -1) {
13319
14142
  localList[localIndex] = ancestryObjectToSave;
13320
14143
  } else {
@@ -13342,11 +14165,29 @@ function XViewScene({
13342
14165
  return;
13343
14166
  }
13344
14167
  if (!keepOpen) {
13345
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
14168
+ setAncestryMode({
14169
+ isActive: false,
14170
+ tree: null,
14171
+ selectedParentId: null,
14172
+ isEditMode: false,
14173
+ currentAncestryId: null,
14174
+ ancestryName: "",
14175
+ ancestryDescription: "",
14176
+ ancestryDescriptionSections: [],
14177
+ isAddingNodes: false
14178
+ });
13346
14179
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13347
14180
  }
13348
14181
  },
13349
- [ancestryMode, ancestry_save_url, handleRenderAncestry, save_view_data, sceneConfigId, ownerId, get_ancestry_file]
14182
+ [
14183
+ ancestryMode,
14184
+ ancestry_save_url,
14185
+ handleRenderAncestry,
14186
+ save_view_data,
14187
+ sceneConfigId,
14188
+ ownerId,
14189
+ get_ancestry_file
14190
+ ]
13350
14191
  );
13351
14192
  const handleOpenAncestryRelEditor = (path, currentData) => {
13352
14193
  setEditingAncestryRel({ visible: true, data: currentData, path });
@@ -13376,7 +14217,9 @@ function XViewScene({
13376
14217
  if (ancestryToDelete && delete_file_action) {
13377
14218
  const urls = extractFileUrlsFromProperties(ancestryToDelete);
13378
14219
  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));
14220
+ Promise.all(urls.map((url) => delete_file_action(url))).catch(
14221
+ (err) => console.error("Erro ao deletar arquivos da ancestralidade:", err)
14222
+ );
13380
14223
  }
13381
14224
  }
13382
14225
  if (!ancestryToDelete) {
@@ -13386,7 +14229,9 @@ function XViewScene({
13386
14229
  const sourceFileId = ancestryToDelete._source_file_id;
13387
14230
  const sourceOwnerId = ancestryToDelete._source_owner_id;
13388
14231
  if (!sourceFileId || !sourceOwnerId) {
13389
- alert("N\xE3o foi poss\xEDvel identificar o arquivo de origem desta ancestralidade.");
14232
+ alert(
14233
+ "N\xE3o foi poss\xEDvel identificar o arquivo de origem desta ancestralidade."
14234
+ );
13390
14235
  return;
13391
14236
  }
13392
14237
  const finalSaveUrl = `x_view_ancestry/${sourceOwnerId}/${sourceFileId}`;
@@ -13394,13 +14239,18 @@ function XViewScene({
13394
14239
  (anc) => anc._source_file_id === sourceFileId && String(anc.ancestry_id) !== String(ancestryIdToDelete)
13395
14240
  );
13396
14241
  try {
13397
- const result = await save_view_data(finalSaveUrl, updatedAncestriesForFile);
14242
+ const result = await save_view_data(
14243
+ finalSaveUrl,
14244
+ updatedAncestriesForFile
14245
+ );
13398
14246
  if (result.success) {
13399
14247
  ancestryDataRef.current = (ancestryDataRef.current || []).filter(
13400
14248
  (ancestry) => String(ancestry.ancestry_id) !== String(ancestryIdToDelete)
13401
14249
  );
13402
14250
  const { renderedAncestries, ancestryGroup } = stateRef.current;
13403
- const renderIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryIdToDelete));
14251
+ const renderIndex = renderedAncestries.findIndex(
14252
+ (a) => String(a.id) === String(ancestryIdToDelete)
14253
+ );
13404
14254
  if (renderIndex !== -1) {
13405
14255
  const toRemove = renderedAncestries[renderIndex];
13406
14256
  toRemove.lines.forEach((line) => {
@@ -13409,18 +14259,29 @@ function XViewScene({
13409
14259
  if (line.material) line.material.dispose();
13410
14260
  });
13411
14261
  renderedAncestries.splice(renderIndex, 1);
13412
- stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
14262
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap(
14263
+ (a) => a.lines
14264
+ );
13413
14265
  }
13414
14266
  setSceneVersion((v) => v + 1);
13415
14267
  } else {
13416
- throw new Error(result.error || "Ocorreu um erro desconhecido ao excluir.");
14268
+ throw new Error(
14269
+ result.error || "Ocorreu um erro desconhecido ao excluir."
14270
+ );
13417
14271
  }
13418
14272
  } catch (error) {
13419
14273
  console.error("Falha ao excluir a ancestralidade:", error);
13420
14274
  alert(`Erro ao excluir a ancestralidade: ${error.message}`);
13421
14275
  return;
13422
14276
  }
13423
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "" });
14277
+ setAncestryMode({
14278
+ isActive: false,
14279
+ tree: null,
14280
+ selectedParentId: null,
14281
+ isEditMode: false,
14282
+ currentAncestryId: null,
14283
+ ancestryName: ""
14284
+ });
13424
14285
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13425
14286
  },
13426
14287
  [save_view_data, delete_file_action]
@@ -13428,24 +14289,38 @@ function XViewScene({
13428
14289
  const handleOpenAncestryBoard = (0, import_react26.useCallback)(() => {
13429
14290
  setIsAncestryBoardOpen(true);
13430
14291
  }, []);
13431
- const handleSelectAncestryFromBoard = (0, import_react26.useCallback)((ancestry) => {
13432
- setIsAncestryBoardOpen(false);
13433
- setIsSidebarOpen(false);
13434
- handleStartReadingAncestry(ancestry);
13435
- }, [handleStartReadingAncestry]);
13436
- const handleSaveAncestryBoard = (0, import_react26.useCallback)(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]);
14292
+ const handleSelectAncestryFromBoard = (0, import_react26.useCallback)(
14293
+ (ancestry) => {
14294
+ setIsAncestryBoardOpen(false);
14295
+ setIsSidebarOpen(false);
14296
+ handleStartReadingAncestry(ancestry);
14297
+ },
14298
+ [handleStartReadingAncestry]
14299
+ );
14300
+ const handleSaveAncestryBoard = (0, import_react26.useCallback)(
14301
+ async (groups) => {
14302
+ if (!sceneConfigId || !viewParams || !session) return;
14303
+ const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
14304
+ await save_ancestry_board_action(
14305
+ sceneConfigId,
14306
+ sceneType,
14307
+ groups,
14308
+ session,
14309
+ ownerId
14310
+ );
14311
+ },
14312
+ [sceneConfigId, viewParams, session, save_ancestry_board_action, ownerId]
14313
+ );
13441
14314
  const existingNodeTypes = (0, import_react26.useMemo)(() => {
13442
14315
  if (!parentDataRef.current) {
13443
14316
  return [];
13444
14317
  }
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");
14318
+ const allTypes = Object.values(parentDataRef.current).flatMap(
14319
+ (fileData) => fileData.nodes.flatMap((node) => {
14320
+ if (Array.isArray(node.type)) return node.type;
14321
+ return [node.type];
14322
+ })
14323
+ ).filter((t) => Boolean(t) && String(t).toLowerCase() !== "quest");
13449
14324
  return [...new Set(allTypes)];
13450
14325
  }, [parentDataRef.current, sceneVersion]);
13451
14326
  const searchableDbNodes = (0, import_react26.useMemo)(() => {
@@ -13459,7 +14334,10 @@ function XViewScene({
13459
14334
  }, [parentDataRef.current, sceneVersion]);
13460
14335
  const handleAddExistingNode = (0, import_react26.useCallback)(
13461
14336
  (nodeId) => {
13462
- return userActionHandlers.handleAddExistingNodeById(actionHandlerContext, nodeId);
14337
+ return userActionHandlers.handleAddExistingNodeById(
14338
+ actionHandlerContext,
14339
+ nodeId
14340
+ );
13463
14341
  },
13464
14342
  [actionHandlerContext]
13465
14343
  );
@@ -13467,7 +14345,9 @@ function XViewScene({
13467
14345
  var _a2, _b2, _c2;
13468
14346
  const { nodeObjects, allLinks } = stateRef.current;
13469
14347
  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.");
14348
+ console.warn(
14349
+ "N\xE3o \xE9 poss\xEDvel salvar a cena: estado n\xE3o inicializado ou URL de salvamento ausente."
14350
+ );
13471
14351
  return;
13472
14352
  }
13473
14353
  if (!save_view_data) return;
@@ -13504,48 +14384,68 @@ function XViewScene({
13504
14384
  }, [sceneSaveUrl, save_view_data, sceneConfigId, viewParams == null ? void 0 : viewParams.type]);
13505
14385
  const allAvailableNodes = (0, import_react26.useMemo)(() => {
13506
14386
  if (!parentDataRef.current) return [];
13507
- return Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes || []);
14387
+ return Object.values(parentDataRef.current).flatMap(
14388
+ (fileData) => fileData.nodes || []
14389
+ );
13508
14390
  }, [sceneVersion, isInitialized]);
13509
14391
  const allAvailableAncestries = (0, import_react26.useMemo)(() => {
13510
14392
  return ancestryDataRef.current || [];
13511
14393
  }, [sceneVersion, isInitialized]);
13512
- const handleOpenReference = (0, import_react26.useCallback)((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);
14394
+ const handleOpenReference = (0, import_react26.useCallback)(
14395
+ (referenceData) => {
14396
+ const { type, id } = referenceData;
14397
+ if (type === "node") {
14398
+ const targetNode = allAvailableNodes.find(
14399
+ (n) => String(n.id) === String(id)
14400
+ );
14401
+ if (targetNode) {
14402
+ setAncestryLinkDetails(null);
14403
+ setDetailsLink(null);
14404
+ setDetailsNode(targetNode);
14405
+ const sceneMesh = stateRef.current.nodeObjects[String(id)];
14406
+ if (sceneMesh) {
14407
+ tweenToTarget(sceneMesh);
14408
+ }
14409
+ } else {
14410
+ alert("Node original n\xE3o encontrado neste contexto.");
14411
+ }
14412
+ } else if (type === "ancestry") {
14413
+ const targetAncestry = allAvailableAncestries.find(
14414
+ (a) => String(a.ancestry_id) === String(id)
14415
+ );
14416
+ if (targetAncestry) {
14417
+ setDetailsNode(null);
14418
+ setDetailsLink(null);
14419
+ setAncestryLinkDetails(null);
14420
+ handleEditAncestry(targetAncestry);
14421
+ } else {
14422
+ alert("Ancestralidade original n\xE3o encontrada neste contexto.");
13523
14423
  }
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
14424
  }
13537
- }
13538
- }, [allAvailableNodes, allAvailableAncestries, handleEditAncestry, tweenToTarget]);
14425
+ },
14426
+ [
14427
+ allAvailableNodes,
14428
+ allAvailableAncestries,
14429
+ handleEditAncestry,
14430
+ tweenToTarget
14431
+ ]
14432
+ );
13539
14433
  const handleToggleAncestryAddMode = (0, import_react26.useCallback)(() => {
13540
- setAncestryMode((prev) => ({ ...prev, isAddingNodes: !prev.isAddingNodes }));
14434
+ setAncestryMode((prev) => ({
14435
+ ...prev,
14436
+ isAddingNodes: !prev.isAddingNodes
14437
+ }));
13541
14438
  }, []);
13542
- const handleFocusNode = (0, import_react26.useCallback)((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]);
14439
+ const handleFocusNode = (0, import_react26.useCallback)(
14440
+ (nodeData) => {
14441
+ if (!nodeData) return;
14442
+ const nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
14443
+ if (nodeMesh) {
14444
+ tweenToTarget(nodeMesh, 1.2);
14445
+ }
14446
+ },
14447
+ [tweenToTarget]
14448
+ );
13549
14449
  const availableDatasets = (0, import_react26.useMemo)(() => {
13550
14450
  if (!sceneDataRef.current || !parentDataRef.current) return [];
13551
14451
  return sceneDataRef.current.parent_dbs.map((db) => {
@@ -13556,7 +14456,9 @@ function XViewScene({
13556
14456
  };
13557
14457
  });
13558
14458
  }, [sceneVersion, isInitialized]);
13559
- const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(String(creationMode.sourceNodeData.id))) == null ? void 0 : _b.parentFileId : null;
14459
+ const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(
14460
+ String(creationMode.sourceNodeData.id)
14461
+ )) == null ? void 0 : _b.parentFileId : null;
13560
14462
  const detailsNodeDatasetInfo = detailsNode ? stateRef.current.nodeIdToParentFileMap.get(String(detailsNode.id)) : null;
13561
14463
  (0, import_react26.useEffect)(() => {
13562
14464
  if (isInitialized && focusNodeId && !hasFocusedInitial) {
@@ -13569,13 +14471,24 @@ function XViewScene({
13569
14471
  }, 300);
13570
14472
  } else {
13571
14473
  setHasFocusedInitial(true);
14474
+ setInvalidTargetError(
14475
+ "O link aponta para um item que n\xE3o foi encontrado ou foi exclu\xEDdo."
14476
+ );
13572
14477
  }
13573
14478
  }
13574
- }, [isInitialized, sceneVersion, focusNodeId, hasFocusedInitial, tweenToTarget]);
14479
+ }, [
14480
+ isInitialized,
14481
+ sceneVersion,
14482
+ focusNodeId,
14483
+ hasFocusedInitial,
14484
+ tweenToTarget
14485
+ ]);
13575
14486
  (0, import_react26.useEffect)(() => {
13576
14487
  if (isInitialized && focusAncestryId && !hasOpenedInitialAncestry) {
13577
14488
  const ancestries = ancestryDataRef.current || [];
13578
- const targetAncestry = ancestries.find((a) => String(a.ancestry_id) === String(focusAncestryId));
14489
+ const targetAncestry = ancestries.find(
14490
+ (a) => String(a.ancestry_id) === String(focusAncestryId)
14491
+ );
13579
14492
  if (targetAncestry) {
13580
14493
  setTimeout(() => {
13581
14494
  handleStartReadingAncestry(targetAncestry);
@@ -13583,20 +14496,43 @@ function XViewScene({
13583
14496
  }, 300);
13584
14497
  } else {
13585
14498
  setHasOpenedInitialAncestry(true);
14499
+ setInvalidTargetError(
14500
+ "O link aponta para uma ancestralidade que n\xE3o foi encontrada ou foi exclu\xEDda."
14501
+ );
13586
14502
  }
13587
14503
  }
13588
- }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
14504
+ }, [
14505
+ isInitialized,
14506
+ sceneVersion,
14507
+ focusAncestryId,
14508
+ hasOpenedInitialAncestry,
14509
+ handleStartReadingAncestry
14510
+ ]);
13589
14511
  (0, import_react26.useEffect)(() => {
13590
14512
  function handleKeyDown(event) {
13591
14513
  var _a2, _b2, _c2;
13592
14514
  const context = actionHandlerContext;
13593
14515
  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);
14516
+ if (stateRef.current.connection.isActive)
14517
+ userActionHandlers.handleCancelConnection(context);
14518
+ if (stateRef.current.relink.isActive)
14519
+ userActionHandlers.handleCancelRelink(context);
14520
+ if (stateRef.current.creation.isActive)
14521
+ userActionHandlers.handleCancelCreation(context);
14522
+ if ((_a2 = stateRef.current.versionMode) == null ? void 0 : _a2.isActive)
14523
+ userActionHandlers.handleCancelVersioning(context);
13598
14524
  if (stateRef.current.ancestry.isActive) {
13599
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
14525
+ setAncestryMode({
14526
+ isActive: false,
14527
+ tree: null,
14528
+ selectedParentId: null,
14529
+ isEditMode: false,
14530
+ currentAncestryId: null,
14531
+ ancestryName: "",
14532
+ ancestryDescription: "",
14533
+ ancestryDescriptionSections: [],
14534
+ isAddingNodes: false
14535
+ });
13600
14536
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13601
14537
  }
13602
14538
  if (questMode.isActive) {
@@ -13605,7 +14541,9 @@ function XViewScene({
13605
14541
  if (stateRef.current.selectedNodes.size > 0) {
13606
14542
  stateRef.current.selectedNodes.clear();
13607
14543
  }
13608
- setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
14544
+ setContextMenu(
14545
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
14546
+ );
13609
14547
  setMultiContextMenu((prev) => ({ ...prev, visible: false }));
13610
14548
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13611
14549
  }
@@ -13624,7 +14562,9 @@ function XViewScene({
13624
14562
  let attempts = 0;
13625
14563
  const MIN_CLEARANCE = 15;
13626
14564
  while (isOccupied && attempts < 30) {
13627
- isOccupied = existingNodes.some((mesh) => mesh.position.distanceTo(ghostPosition) < MIN_CLEARANCE);
14565
+ isOccupied = existingNodes.some(
14566
+ (mesh) => mesh.position.distanceTo(ghostPosition) < MIN_CLEARANCE
14567
+ );
13628
14568
  if (isOccupied) {
13629
14569
  ghostPosition.x = controls.target.x + Math.cos(angle) * radius;
13630
14570
  ghostPosition.y = controls.target.y + (Math.random() - 0.5) * 8;
@@ -13643,7 +14583,11 @@ function XViewScene({
13643
14583
  intensity: 0,
13644
14584
  type: ["quest"]
13645
14585
  };
13646
- const ghostNode = createNodeMesh(ghostData, ghostPosition, glowTexture);
14586
+ const ghostNode = createNodeMesh(
14587
+ ghostData,
14588
+ ghostPosition,
14589
+ glowTexture
14590
+ );
13647
14591
  ghostNode.traverse((child) => {
13648
14592
  if (child.isMesh) {
13649
14593
  child.material.transparent = true;
@@ -13690,13 +14634,49 @@ function XViewScene({
13690
14634
  return /* @__PURE__ */ import_react26.default.createElement(LoadingScreen, null);
13691
14635
  }
13692
14636
  if (permissionStatus === "denied") {
13693
- return /* @__PURE__ */ import_react26.default.createElement("div", { className: "flex flex-col items-center justify-center min-h-screen w-full bg-slate-950 text-white" }, /* @__PURE__ */ import_react26.default.createElement("div", { className: "bg-slate-900/50 p-8 rounded-2xl border border-slate-800 shadow-2xl text-center max-w-md" }, /* @__PURE__ */ import_react26.default.createElement("div", { className: "mb-4 text-red-500" }, /* @__PURE__ */ import_react26.default.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__ */ import_react26.default.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__ */ import_react26.default.createElement("h2", { className: "text-2xl font-bold mb-2" }, "Acesso Negado"), /* @__PURE__ */ import_react26.default.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__ */ import_react26.default.createElement(
14637
+ return /* @__PURE__ */ import_react26.default.createElement("div", { className: "flex flex-col items-center justify-center min-h-screen w-full bg-slate-950 text-white" }, /* @__PURE__ */ import_react26.default.createElement("div", { className: "bg-slate-900/50 p-8 rounded-2xl border border-slate-800 shadow-2xl text-center max-w-md" }, /* @__PURE__ */ import_react26.default.createElement("div", { className: "mb-4 text-red-500" }, /* @__PURE__ */ import_react26.default.createElement(
14638
+ "svg",
14639
+ {
14640
+ xmlns: "http://www.w3.org/2000/svg",
14641
+ fill: "none",
14642
+ viewBox: "0 0 24 24",
14643
+ strokeWidth: 1.5,
14644
+ stroke: "currentColor",
14645
+ className: "w-16 h-16 mx-auto"
14646
+ },
14647
+ /* @__PURE__ */ import_react26.default.createElement(
14648
+ "path",
14649
+ {
14650
+ strokeLinecap: "round",
14651
+ strokeLinejoin: "round",
14652
+ 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"
14653
+ }
14654
+ )
14655
+ )), /* @__PURE__ */ import_react26.default.createElement("h2", { className: "text-2xl font-bold mb-2" }, "Acesso Negado"), /* @__PURE__ */ import_react26.default.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__ */ import_react26.default.createElement(
13694
14656
  "button",
13695
14657
  {
13696
14658
  onClick: () => router.push("/dashboard/scenes"),
13697
14659
  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
14660
  },
13699
- /* @__PURE__ */ import_react26.default.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__ */ import_react26.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" })),
14661
+ /* @__PURE__ */ import_react26.default.createElement(
14662
+ "svg",
14663
+ {
14664
+ xmlns: "http://www.w3.org/2000/svg",
14665
+ fill: "none",
14666
+ viewBox: "0 0 24 24",
14667
+ strokeWidth: 2,
14668
+ stroke: "currentColor",
14669
+ className: "w-5 h-5"
14670
+ },
14671
+ /* @__PURE__ */ import_react26.default.createElement(
14672
+ "path",
14673
+ {
14674
+ strokeLinecap: "round",
14675
+ strokeLinejoin: "round",
14676
+ d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
14677
+ }
14678
+ )
14679
+ ),
13700
14680
  "Voltar para Scenes"
13701
14681
  )));
13702
14682
  }
@@ -13743,7 +14723,14 @@ function XViewScene({
13743
14723
  onImageChange: handleGhostNodeImageChange,
13744
14724
  onOpenImageViewer: handleOpenImageViewer,
13745
14725
  onMentionClick: handleAddExistingNode,
13746
- style: { position: "absolute", left: `${formPosition.left}px`, top: `${formPosition.top}px`, opacity: formPosition.opacity, zIndex: 20, transition: "opacity 200ms ease-out" },
14726
+ style: {
14727
+ position: "absolute",
14728
+ left: `${formPosition.left}px`,
14729
+ top: `${formPosition.top}px`,
14730
+ opacity: formPosition.opacity,
14731
+ zIndex: 20,
14732
+ transition: "opacity 200ms ease-out"
14733
+ },
13747
14734
  refEl: formRef,
13748
14735
  existingTypes: existingNodeTypes,
13749
14736
  initialColor: (_c = creationMode.sourceNodeData) == null ? void 0 : _c.color,
@@ -13767,7 +14754,14 @@ function XViewScene({
13767
14754
  onImageChange: handleGhostNodeImageChange,
13768
14755
  onOpenImageViewer: handleOpenImageViewer,
13769
14756
  onMentionClick: handleAddExistingNode,
13770
- style: { position: "absolute", left: `${formPosition.left}px`, top: `${formPosition.top}px`, opacity: formPosition.opacity, zIndex: 20, transition: "opacity 200ms ease-out" },
14757
+ style: {
14758
+ position: "absolute",
14759
+ left: `${formPosition.left}px`,
14760
+ top: `${formPosition.top}px`,
14761
+ opacity: formPosition.opacity,
14762
+ zIndex: 20,
14763
+ transition: "opacity 200ms ease-out"
14764
+ },
13771
14765
  refEl: formRef,
13772
14766
  fixedType: (_e = versionMode.sourceNodeData) == null ? void 0 : _e.type,
13773
14767
  fixedColor: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.color,
@@ -13784,7 +14778,13 @@ function XViewScene({
13784
14778
  onNameChange: handleGhostNodeNameChange,
13785
14779
  onColorChange: handleGhostNodeColorChange,
13786
14780
  onSizeChange: handleGhostNodeSizeChange,
13787
- style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
14781
+ style: {
14782
+ position: "absolute",
14783
+ left: `16px`,
14784
+ top: `16px`,
14785
+ zIndex: 20,
14786
+ transition: "opacity 200ms ease-out"
14787
+ },
13788
14788
  refEl: formRef,
13789
14789
  onOpenImageViewer: handleOpenImageViewer,
13790
14790
  onMentionClick: handleAddExistingNode,
@@ -13799,7 +14799,14 @@ function XViewScene({
13799
14799
  "div",
13800
14800
  {
13801
14801
  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" }
14802
+ style: {
14803
+ top: 16,
14804
+ right: 16,
14805
+ zIndex: 1100,
14806
+ maxHeight: "calc(100vh - 32px)",
14807
+ width: `${readModeWidth}px`,
14808
+ maxWidth: "92vw"
14809
+ }
13803
14810
  },
13804
14811
  /* @__PURE__ */ import_react26.default.createElement(
13805
14812
  "div",
@@ -13857,7 +14864,16 @@ function XViewScene({
13857
14864
  onSave: handleSaveAncestry,
13858
14865
  onEditRelationship: handleOpenAncestryRelEditor,
13859
14866
  onClose: () => {
13860
- setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", isAddingNodes: false });
14867
+ setAncestryMode({
14868
+ isActive: false,
14869
+ tree: null,
14870
+ selectedParentId: null,
14871
+ isEditMode: false,
14872
+ currentAncestryId: null,
14873
+ ancestryName: "",
14874
+ ancestryDescription: "",
14875
+ isAddingNodes: false
14876
+ });
13861
14877
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13862
14878
  },
13863
14879
  availableNodes: allAvailableNodes,
@@ -13922,7 +14938,12 @@ function XViewScene({
13922
14938
  onOpenImageViewer: handleOpenImageViewer,
13923
14939
  existingTypes: existingNodeTypes,
13924
14940
  onImageChange: (useImage, url, currentColorOverride) => {
13925
- const updatedNode = { ...detailsNode, useImageAsTexture: useImage, textureImageUrl: url, color: currentColorOverride || detailsNode.color };
14941
+ const updatedNode = {
14942
+ ...detailsNode,
14943
+ useImageAsTexture: useImage,
14944
+ textureImageUrl: url,
14945
+ color: currentColorOverride || detailsNode.color
14946
+ };
13926
14947
  updateExistingNodeVisuals(stateRef.current, updatedNode);
13927
14948
  setDetailsNode(updatedNode);
13928
14949
  },
@@ -13996,7 +15017,9 @@ function XViewScene({
13996
15017
  parentData: parentDataRef.current,
13997
15018
  sceneData: sceneDataRef.current,
13998
15019
  ancestryData: ancestryDataRef.current,
13999
- onClose: () => setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev),
15020
+ onClose: () => setContextMenu(
15021
+ (prev) => prev.visible ? { ...prev, visible: false } : prev
15022
+ ),
14000
15023
  onStartCreation: (data) => userActionHandlers.handleStartCreation(actionHandlerContext, data),
14001
15024
  onStartConnection: (data) => userActionHandlers.handleStartConnection(actionHandlerContext, data),
14002
15025
  onStartVersioning: handleStartVersioning,
@@ -14004,7 +15027,11 @@ function XViewScene({
14004
15027
  onDeleteNode: (data) => userActionHandlers.handleDeleteNode(actionHandlerContext, data),
14005
15028
  onDismissNode: (data) => userActionHandlers.handleDismissNode(actionHandlerContext, data),
14006
15029
  onDismissOtherNodes: (data) => userActionHandlers.handleDismissOtherNodes(actionHandlerContext, data),
14007
- onExpandConnections: (sourceNode, links) => userActionHandlers.handleExpandConnections(actionHandlerContext, sourceNode, links),
15030
+ onExpandConnections: (sourceNode, links) => userActionHandlers.handleExpandConnections(
15031
+ actionHandlerContext,
15032
+ sourceNode,
15033
+ links
15034
+ ),
14008
15035
  onRenderAncestry: handleStartReadingAncestry,
14009
15036
  onEditAncestry: handleEditAncestry,
14010
15037
  onDeleteAncestry: (ancestryId) => handleDeleteAncestry(ancestryId),
@@ -14017,9 +15044,18 @@ function XViewScene({
14017
15044
  data: multiContextMenu,
14018
15045
  userRole: userPermissionRole,
14019
15046
  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)
15047
+ onDismissNodes: (ids) => userActionHandlers.handleDismissMultipleNodes(
15048
+ actionHandlerContext,
15049
+ ids
15050
+ ),
15051
+ onDismissOtherNodes: (ids) => userActionHandlers.handleDismissOtherMultipleNodes(
15052
+ actionHandlerContext,
15053
+ ids
15054
+ ),
15055
+ onDeleteNodes: (ids) => userActionHandlers.handleDeleteMultipleNodes(
15056
+ actionHandlerContext,
15057
+ ids
15058
+ )
14023
15059
  }
14024
15060
  ),
14025
15061
  /* @__PURE__ */ import_react26.default.createElement(
@@ -14040,7 +15076,13 @@ function XViewScene({
14040
15076
  onDelete: (data) => userActionHandlers.handleDeleteLink(actionHandlerContext, data)
14041
15077
  }
14042
15078
  ),
14043
- /* @__PURE__ */ import_react26.default.createElement(ImageViewer, { data: imageViewer, onClose: () => setImageViewer({ ...imageViewer, visible: false }) }),
15079
+ /* @__PURE__ */ import_react26.default.createElement(
15080
+ ImageViewer,
15081
+ {
15082
+ data: imageViewer,
15083
+ onClose: () => setImageViewer({ ...imageViewer, visible: false })
15084
+ }
15085
+ ),
14044
15086
  /* @__PURE__ */ import_react26.default.createElement(
14045
15087
  AncestryBoard,
14046
15088
  {
@@ -14066,6 +15108,83 @@ function XViewScene({
14066
15108
  currentViewName: viewParams == null ? void 0 : viewParams.name,
14067
15109
  currentAncestries: ancestryDataRef.current || []
14068
15110
  }
15111
+ ),
15112
+ invalidTargetError && /* @__PURE__ */ import_react26.default.createElement(
15113
+ "div",
15114
+ {
15115
+ className: "ui-overlay",
15116
+ style: {
15117
+ position: "fixed",
15118
+ top: "24px",
15119
+ left: "50%",
15120
+ transform: "translateX(-50%)",
15121
+ zIndex: 1e4,
15122
+ padding: "16px 24px",
15123
+ background: "rgba(30, 20, 20, 0.85)",
15124
+ backdropFilter: "blur(12px)",
15125
+ WebkitBackdropFilter: "blur(12px)",
15126
+ border: "1px solid rgba(255, 70, 70, 0.35)",
15127
+ borderRadius: "16px",
15128
+ boxShadow: "0 12px 40px rgba(0,0,0,0.5), 0 0 30px rgba(255, 50, 50, 0.1)",
15129
+ color: "#ffa0a0",
15130
+ display: "flex",
15131
+ alignItems: "center",
15132
+ gap: "16px",
15133
+ fontFamily: "Inter, sans-serif",
15134
+ animation: "fadeInDown 0.5s cubic-bezier(0.16, 1, 0.3, 1)"
15135
+ }
15136
+ },
15137
+ /* @__PURE__ */ import_react26.default.createElement("style", null, `
15138
+ @keyframes fadeInDown {
15139
+ from { opacity: 0; transform: translate(-50%, -20px); }
15140
+ to { opacity: 1; transform: translate(-50%, 0); }
15141
+ }
15142
+ `),
15143
+ /* @__PURE__ */ import_react26.default.createElement(
15144
+ "svg",
15145
+ {
15146
+ width: "20",
15147
+ height: "20",
15148
+ viewBox: "0 0 24 24",
15149
+ fill: "none",
15150
+ stroke: "currentColor",
15151
+ strokeWidth: "2",
15152
+ strokeLinecap: "round",
15153
+ strokeLinejoin: "round"
15154
+ },
15155
+ /* @__PURE__ */ import_react26.default.createElement("circle", { cx: "12", cy: "12", r: "10" }),
15156
+ /* @__PURE__ */ import_react26.default.createElement("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
15157
+ /* @__PURE__ */ import_react26.default.createElement("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
15158
+ ),
15159
+ /* @__PURE__ */ import_react26.default.createElement("span", { style: { fontSize: "14px", fontWeight: 500 } }, invalidTargetError),
15160
+ /* @__PURE__ */ import_react26.default.createElement(
15161
+ "button",
15162
+ {
15163
+ onClick: () => setInvalidTargetError(null),
15164
+ style: {
15165
+ background: "rgba(255, 255, 255, 0.1)",
15166
+ border: "none",
15167
+ color: "white",
15168
+ padding: "4px 10px",
15169
+ borderRadius: "8px",
15170
+ cursor: "pointer",
15171
+ fontSize: "12px",
15172
+ fontWeight: 600,
15173
+ transition: "all 0.2s",
15174
+ marginLeft: "8px",
15175
+ border: "1px solid rgba(255,255,255,0.1)"
15176
+ },
15177
+ onMouseOver: (e) => {
15178
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
15179
+ e.currentTarget.style.transform = "translateY(-1px)";
15180
+ },
15181
+ onMouseOut: (e) => {
15182
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
15183
+ e.currentTarget.style.transform = "translateY(0)";
15184
+ }
15185
+ },
15186
+ "Fechar"
15187
+ )
14069
15188
  )
14070
15189
  );
14071
15190
  }