@lv-x-software-house/x_view 1.1.8 → 1.1.9-dev.2

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 +215 -94
  2. package/dist/index.mjs +251 -130
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5019,8 +5019,12 @@ var NodeItem = ({ nodeData, onSelectParent, onRemoveNode, onEditRelationship, on
5019
5019
  };
5020
5020
  function CreateAncestryPanel({
5021
5021
  ancestryMode,
5022
+ setAncestryMode,
5023
+ // <--- Nova prop necessária para as novas funções manipuladoras
5022
5024
  onSelectParent,
5025
+ // Mantido para compatibilidade, mas as novas funções usam setAncestryMode
5023
5026
  onRemoveNode,
5027
+ // Mantido para compatibilidade
5024
5028
  onSave,
5025
5029
  onClose,
5026
5030
  onEditRelationship,
@@ -5031,12 +5035,14 @@ function CreateAncestryPanel({
5031
5035
  onUpdateTree,
5032
5036
  onAncestrySectionChange,
5033
5037
  onToggleAddNodes,
5038
+ // Mantido para compatibilidade
5034
5039
  onRenderFullAncestry,
5035
5040
  onHighlightNode,
5036
5041
  onClearAncestryVisuals,
5037
5042
  onUploadFile,
5038
- onOpenImageViewer
5039
- // <--- ADICIONE NA LISTA DE PROPS RECEBIDAS
5043
+ onOpenImageViewer,
5044
+ onRenderAbstractionTree
5045
+ // <--- Nova prop recebida
5040
5046
  }) {
5041
5047
  const {
5042
5048
  tree: rootTree,
@@ -5072,6 +5078,91 @@ function CreateAncestryPanel({
5072
5078
  console.warn("onOpenImageViewer n\xE3o foi passado para CreateAncestryPanel");
5073
5079
  }
5074
5080
  };
5081
+ const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5082
+ setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5083
+ };
5084
+ const handleToggleAddMode = (isAbstraction = false) => {
5085
+ setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5086
+ };
5087
+ const handleRemoveNode = (0, import_react10.useCallback)((pathToRemove, isAbstraction = false) => {
5088
+ if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
5089
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
5090
+ setAncestryMode((prev) => {
5091
+ if (!prev[treeKey]) return prev;
5092
+ const newTree = JSON.parse(JSON.stringify(prev[treeKey]));
5093
+ let currentParent = newTree;
5094
+ for (let i = 0; i < pathToRemove.length - 1; i++) {
5095
+ const childIndex = pathToRemove[i];
5096
+ if (currentParent.children && currentParent.children[childIndex]) {
5097
+ currentParent = currentParent.children[childIndex];
5098
+ } else return prev;
5099
+ }
5100
+ const indexToRemove = pathToRemove[pathToRemove.length - 1];
5101
+ if (currentParent.children && currentParent.children.length > indexToRemove) {
5102
+ currentParent.children.splice(indexToRemove, 1);
5103
+ }
5104
+ return { ...prev, [treeKey]: newTree };
5105
+ });
5106
+ }, [setAncestryMode]);
5107
+ const handleMoveNode = (sourceId, targetId, isAbstraction = false) => {
5108
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
5109
+ const activeTreeContext = ancestryMode[treeKey];
5110
+ if (!activeTreeContext) return;
5111
+ const rootTreeClone = JSON.parse(JSON.stringify(ancestryMode[treeKey]));
5112
+ let targetTreeContext = rootTreeClone;
5113
+ if (!isAbstraction && branchStack.length > 0) {
5114
+ let ptr = rootTreeClone;
5115
+ for (const step of branchStack) {
5116
+ const found = findNodePath(ptr, step.nodeId);
5117
+ if (found && found.node.parallel_branches) {
5118
+ const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
5119
+ if (branch) ptr = branch.tree;
5120
+ }
5121
+ }
5122
+ targetTreeContext = ptr;
5123
+ }
5124
+ let movedNodeData = null;
5125
+ const removeSourceNode = (node) => {
5126
+ if (!node.children) return false;
5127
+ const idx = node.children.findIndex((child) => {
5128
+ var _a;
5129
+ return String(child.is_section ? child.id : (_a = child.node) == null ? void 0 : _a.id) === String(sourceId);
5130
+ });
5131
+ if (idx !== -1) {
5132
+ movedNodeData = node.children[idx];
5133
+ node.children.splice(idx, 1);
5134
+ return true;
5135
+ }
5136
+ for (const child of node.children) {
5137
+ if (removeSourceNode(child)) return true;
5138
+ }
5139
+ return false;
5140
+ };
5141
+ const insertTargetNode = (node) => {
5142
+ var _a;
5143
+ if (String(node.is_section ? node.id : (_a = node.node) == null ? void 0 : _a.id) === String(targetId)) {
5144
+ if (!node.children) node.children = [];
5145
+ node.children.push(movedNodeData);
5146
+ return true;
5147
+ }
5148
+ if (node.children) {
5149
+ for (const child of node.children) {
5150
+ if (insertTargetNode(child)) return true;
5151
+ }
5152
+ }
5153
+ return false;
5154
+ };
5155
+ if (removeSourceNode(targetTreeContext) && movedNodeData) {
5156
+ if (insertTargetNode(targetTreeContext)) {
5157
+ if (!isAbstraction) {
5158
+ updateGlobalTree(rootTreeClone);
5159
+ }
5160
+ setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
5161
+ } else {
5162
+ alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5163
+ }
5164
+ }
5165
+ };
5075
5166
  const takeSnapshot = (tree, name, desc, sections, customProps2, isPrivateVal) => {
5076
5167
  return {
5077
5168
  tree: JSON.stringify(tree),
@@ -5842,66 +5933,6 @@ function CreateAncestryPanel({
5842
5933
  updateGlobalTree(newRootTree);
5843
5934
  }
5844
5935
  }, [description, existingSections]);
5845
- const handleMoveNode = (sourceId, targetId) => {
5846
- if (!activeTree) return;
5847
- const rootTreeClone = JSON.parse(JSON.stringify(ancestryMode.tree));
5848
- let targetTreeContext = rootTreeClone;
5849
- if (branchStack.length > 0) {
5850
- let ptr = rootTreeClone;
5851
- for (const step of branchStack) {
5852
- const found = findNodePath(ptr, step.nodeId);
5853
- if (found && found.node.parallel_branches) {
5854
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
5855
- if (branch) ptr = branch.tree;
5856
- }
5857
- }
5858
- targetTreeContext = ptr;
5859
- }
5860
- let movedNodeData = null;
5861
- const removeSourceNode = (node) => {
5862
- if (!node.children) return;
5863
- const idx = node.children.findIndex((child) => {
5864
- var _a;
5865
- const cId = child.is_section ? child.id : String((_a = child.node) == null ? void 0 : _a.id);
5866
- return String(cId) === String(sourceId);
5867
- });
5868
- if (idx !== -1) {
5869
- movedNodeData = node.children[idx];
5870
- node.children.splice(idx, 1);
5871
- return true;
5872
- }
5873
- for (const child of node.children) {
5874
- if (removeSourceNode(child)) return true;
5875
- }
5876
- return false;
5877
- };
5878
- const insertTargetNode = (node) => {
5879
- var _a;
5880
- const nodeId = node.is_section ? node.id : String((_a = node.node) == null ? void 0 : _a.id);
5881
- if (String(nodeId) === String(targetId)) {
5882
- if (!node.children) node.children = [];
5883
- node.children.push(movedNodeData);
5884
- return true;
5885
- }
5886
- if (node.children) {
5887
- for (const child of node.children) {
5888
- if (insertTargetNode(child)) return true;
5889
- }
5890
- }
5891
- return false;
5892
- };
5893
- const foundAndRemoved = removeSourceNode(targetTreeContext);
5894
- if (!foundAndRemoved || !movedNodeData) {
5895
- console.error("Node de origem n\xE3o encontrado na \xE1rvore.");
5896
- return;
5897
- }
5898
- const inserted = insertTargetNode(targetTreeContext);
5899
- if (!inserted) {
5900
- alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5901
- return;
5902
- }
5903
- updateGlobalTree(rootTreeClone);
5904
- };
5905
5936
  const handleTriggerFullRender = () => {
5906
5937
  var _a;
5907
5938
  if (onRenderFullAncestry && activeTree) {
@@ -6153,7 +6184,7 @@ function CreateAncestryPanel({
6153
6184
  ), /* @__PURE__ */ import_react10.default.createElement(
6154
6185
  "button",
6155
6186
  {
6156
- onClick: onToggleAddNodes,
6187
+ onClick: () => handleToggleAddMode(false),
6157
6188
  className: `p-1.5 rounded-md transition-colors ${isAddingNodes ? "bg-cyan-500 text-white shadow-lg shadow-cyan-500/30" : "bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600"}`,
6158
6189
  title: isAddingNodes ? "Concluir edi\xE7\xE3o da estrutura" : "Editar estrutura e adicionar nodes"
6159
6190
  },
@@ -6162,17 +6193,54 @@ function CreateAncestryPanel({
6162
6193
  NodeItem,
6163
6194
  {
6164
6195
  nodeData: activeTree,
6165
- onSelectParent,
6166
- onRemoveNode,
6196
+ onSelectParent: (id) => handleSelectAncestryParent(id, false),
6197
+ onRemoveNode: (path) => handleRemoveNode(path, false),
6167
6198
  onEditRelationship,
6168
- onMoveNode: handleMoveNode,
6199
+ onMoveNode: (s, t) => handleMoveNode(s, t, false),
6169
6200
  selectedParentId,
6170
6201
  level: 0,
6171
6202
  isLast: true,
6172
6203
  path: [],
6173
6204
  isEditable: isAddingNodes
6174
6205
  }
6175
- ), (!activeTree || activeTree.children.length === 0) && !isAddingNodes && /* @__PURE__ */ import_react10.default.createElement("div", { className: "text-center py-4 text-xs text-slate-500 italic" }, "A estrutura est\xE1 vazia. Clique no l\xE1pis acima para adicionar nodes."))), branchStack.length === 0 && /* @__PURE__ */ import_react10.default.createElement("div", { className: "mt-3 flex items-center justify-end px-1" }, /* @__PURE__ */ import_react10.default.createElement("label", { className: "flex items-center gap-2 cursor-pointer group select-none" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center transition-colors ${isPrivate ? "bg-indigo-500 border-indigo-500" : "border-slate-500 bg-transparent"}` }, isPrivate && /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiCheck, { size: 12, className: "text-white" })), /* @__PURE__ */ import_react10.default.createElement(
6206
+ ), (!activeTree || activeTree.children.length === 0) && !isAddingNodes && /* @__PURE__ */ import_react10.default.createElement("div", { className: "text-center py-4 text-xs text-slate-500 italic" }, "A estrutura est\xE1 vazia. Clique no l\xE1pis acima para adicionar nodes."))), branchStack.length === 0 && ancestryMode.abstraction_tree && /* @__PURE__ */ import_react10.default.createElement("div", { className: `mt-6 rounded-lg border transition-all duration-300 overflow-hidden ${ancestryMode.isAddingAbstractionNodes ? "border-purple-500/40 bg-slate-900/60 ring-1 ring-purple-500/20" : "border-white/10 bg-slate-800/60"}` }, /* @__PURE__ */ import_react10.default.createElement("div", { className: `flex items-center justify-between px-3 py-2 border-b ${ancestryMode.isAddingAbstractionNodes ? "bg-purple-900/20 border-purple-500/20" : "bg-white/5 border-white/5"}` }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react10.default.createElement("span", { className: `text-xs font-semibold uppercase tracking-wider ${ancestryMode.isAddingAbstractionNodes ? "text-purple-300" : "text-slate-400"}` }, "Estrutura de Abstra\xE7\xE3o"), ancestryMode.isAddingAbstractionNodes && /* @__PURE__ */ import_react10.default.createElement("span", { className: "text-[10px] bg-purple-500/20 text-purple-300 px-1.5 py-0.5 rounded animate-pulse" }, "Editando")), /* @__PURE__ */ import_react10.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react10.default.createElement(
6207
+ "button",
6208
+ {
6209
+ type: "button",
6210
+ onClick: () => {
6211
+ const tempPayload = {
6212
+ ancestry_id: currentAncestryId || "temp_rendering",
6213
+ abstraction_tree: ancestryMode.abstraction_tree
6214
+ };
6215
+ if (onRenderAbstractionTree) onRenderAbstractionTree(tempPayload);
6216
+ },
6217
+ className: "p-1.5 rounded-md bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600 transition-colors",
6218
+ title: "Renderizar Verticalmente no Cen\xE1rio"
6219
+ },
6220
+ /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiLayers, { size: 14 })
6221
+ ), /* @__PURE__ */ import_react10.default.createElement(
6222
+ "button",
6223
+ {
6224
+ onClick: () => handleToggleAddMode(true),
6225
+ className: `p-1.5 rounded-md transition-colors ${ancestryMode.isAddingAbstractionNodes ? "bg-purple-500 text-white shadow-lg shadow-purple-500/30" : "bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600"}`,
6226
+ title: "Editar estrutura da abstra\xE7\xE3o"
6227
+ },
6228
+ ancestryMode.isAddingAbstractionNodes ? /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiCheck, { size: 14 }) : /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiEdit2, { size: 14 })
6229
+ ))), /* @__PURE__ */ import_react10.default.createElement("div", { className: "p-4 space-y-2" }, ancestryMode.isAddingAbstractionNodes && /* @__PURE__ */ import_react10.default.createElement("div", { className: "mb-3 p-2 rounded bg-purple-900/20 border border-purple-500/20 text-xs text-purple-200 flex items-start gap-2" }, /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiMousePointer, { className: "mt-0.5 flex-shrink-0" }), /* @__PURE__ */ import_react10.default.createElement("span", null, "Clique nos nodes do cen\xE1rio para adicion\xE1-los. Arraste e solte para organizar a hierarquia.")), /* @__PURE__ */ import_react10.default.createElement(
6230
+ NodeItem,
6231
+ {
6232
+ nodeData: ancestryMode.abstraction_tree,
6233
+ onSelectParent: (id) => handleSelectAncestryParent(id, true),
6234
+ onRemoveNode: (path) => handleRemoveNode(path, true),
6235
+ onEditRelationship,
6236
+ onMoveNode: (s, t) => handleMoveNode(s, t, true),
6237
+ selectedParentId: ancestryMode.selectedAbstractionParentId,
6238
+ level: 0,
6239
+ isLast: true,
6240
+ path: [],
6241
+ isEditable: ancestryMode.isAddingAbstractionNodes
6242
+ }
6243
+ ))), branchStack.length === 0 && /* @__PURE__ */ import_react10.default.createElement("div", { className: "mt-3 flex items-center justify-end px-1" }, /* @__PURE__ */ import_react10.default.createElement("label", { className: "flex items-center gap-2 cursor-pointer group select-none" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center transition-colors ${isPrivate ? "bg-indigo-500 border-indigo-500" : "border-slate-500 bg-transparent"}` }, isPrivate && /* @__PURE__ */ import_react10.default.createElement(import_fi9.FiCheck, { size: 12, className: "text-white" })), /* @__PURE__ */ import_react10.default.createElement(
6176
6244
  "input",
6177
6245
  {
6178
6246
  type: "checkbox",
@@ -9740,43 +9808,38 @@ function XViewScene({
9740
9808
  return;
9741
9809
  }
9742
9810
  if (ancestry.isActive) {
9743
- if (ancestry.isAddingNodes) {
9811
+ if (ancestry.isAddingNodes || ancestry.isAddingAbstractionNodes) {
9744
9812
  const clickedNode = stateRef.current.dragCandidate || tryPickNode();
9745
9813
  stateRef.current.dragCandidate = null;
9746
9814
  stateRef.current.pointerDown.isDown = false;
9747
9815
  stateRef.current.controls.enabled = true;
9748
- if (clickedNode && ancestry.selectedParentId) {
9816
+ const isAbstraction = ancestry.isAddingAbstractionNodes;
9817
+ const currentSelectedParent = isAbstraction ? ancestry.selectedAbstractionParentId : ancestry.selectedParentId;
9818
+ if (clickedNode && currentSelectedParent) {
9749
9819
  const clickedNodeId = String(clickedNode.userData.id);
9750
- const parentId = String(ancestry.selectedParentId);
9820
+ const parentId = String(currentSelectedParent);
9751
9821
  if (clickedNodeId === parentId) {
9752
9822
  alert("Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo.");
9753
9823
  return;
9754
9824
  }
9755
9825
  const parentInfo = stateRef.current.nodeIdToParentFileMap.get(clickedNodeId);
9756
- if (!parentInfo) {
9757
- console.warn(`Node ${clickedNodeId} n\xE3o encontrado em nenhum arquivo pai.`);
9758
- return;
9759
- }
9826
+ if (!parentInfo) return;
9760
9827
  const fullNodeData = (_a2 = parentDataRef.current[parentInfo.parentFileId]) == null ? void 0 : _a2.nodes.find((n) => String(n.id) === clickedNodeId);
9761
- if (!fullNodeData) {
9762
- console.error(`Falha ao encontrar dados completos do Node ${clickedNodeId} mesmo com parentInfo.`);
9763
- return;
9764
- }
9828
+ if (!fullNodeData) return;
9765
9829
  const addChildToNode = (current, targetParentId, childNode) => {
9766
9830
  const currentId = current.is_section ? current.id || current.section_id : String(current.node.id);
9767
9831
  if (String(currentId) === String(targetParentId)) {
9768
9832
  const alreadyExists = current.children.some((child) => !child.is_section && String(child.node.id) === String(childNode.id));
9769
9833
  if (alreadyExists) return current;
9770
- const newChildren = [...current.children, { node: childNode, children: [], relationship: {} }];
9771
- return { ...current, children: newChildren };
9834
+ return { ...current, children: [...current.children, { node: childNode, children: [], relationship: {} }] };
9772
9835
  }
9773
- const updatedChildren = current.children.map((c) => addChildToNode(c, targetParentId, childNode));
9774
- return { ...current, children: updatedChildren };
9836
+ return { ...current, children: current.children.map((c) => addChildToNode(c, targetParentId, childNode)) };
9775
9837
  };
9776
9838
  setAncestryMode((prev) => {
9777
- if (!prev.tree) return prev;
9778
- const newTree = addChildToNode(prev.tree, parentId, fullNodeData);
9779
- return { ...prev, tree: newTree };
9839
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
9840
+ if (!prev[treeKey]) return prev;
9841
+ const newTree = addChildToNode(prev[treeKey], parentId, fullNodeData);
9842
+ return { ...prev, [treeKey]: newTree };
9780
9843
  });
9781
9844
  }
9782
9845
  return;
@@ -10259,12 +10322,15 @@ function XViewScene({
10259
10322
  setAncestryMode({
10260
10323
  isActive: true,
10261
10324
  tree: { node: nodeData, children: [] },
10325
+ abstraction_tree: { node: nodeData, children: [] },
10262
10326
  selectedParentId: nodeData.id,
10327
+ selectedAbstractionParentId: nodeData.id,
10263
10328
  isEditMode: false,
10264
10329
  currentAncestryId: null,
10265
10330
  ancestryName: `Ancestralidade ${nodeData.name}`,
10266
10331
  ancestryDescription: "",
10267
- isAddingNodes: false
10332
+ isAddingNodes: false,
10333
+ isAddingAbstractionNodes: false
10268
10334
  });
10269
10335
  if (mountRef.current) {
10270
10336
  mountRef.current.style.cursor = "default";
@@ -10770,6 +10836,46 @@ function XViewScene({
10770
10836
  },
10771
10837
  [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, readingMode.isActive, ancestryMode.isActive]
10772
10838
  );
10839
+ const handleRenderAbstractionTree = (0, import_react23.useCallback)((ancestryObject) => {
10840
+ setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
10841
+ if (!ancestryObject || !ancestryObject.abstraction_tree) return;
10842
+ const { ancestryGroup, nodeObjects, renderer, renderedAncestries } = stateRef.current;
10843
+ const allParentNodes = Object.values(parentDataRef.current).flatMap((f) => f.nodes);
10844
+ const fullTree = buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, ancestryDataRef.current);
10845
+ if (!fullTree || !fullTree.node) return;
10846
+ const absId = ancestryObject.ancestry_id + "_abs";
10847
+ handleClearAncestryVisuals(absId);
10848
+ const colorHex = 9133302;
10849
+ const resolution = new THREE3.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
10850
+ const ancestryEntry = { id: absId, lines: [], isFullRender: true };
10851
+ renderedAncestries.push(ancestryEntry);
10852
+ const rootNodeId = String(fullTree.node.id);
10853
+ let rootNodeMesh = nodeObjects[rootNodeId];
10854
+ let rootTargetPos = rootNodeMesh ? rootNodeMesh.position.clone() : new THREE3.Vector3(0, 0, 0);
10855
+ if (!rootNodeMesh) {
10856
+ rootNodeMesh = addOrUpdateNodeMesh(fullTree.node, rootTargetPos, true);
10857
+ }
10858
+ const SPACING_Y = -40;
10859
+ const SPACING_X = 55;
10860
+ const renderVertical = (treeNode, parentMesh, parentPos, level) => {
10861
+ if (!treeNode.children || treeNode.children.length === 0) return;
10862
+ const totalSiblings = treeNode.children.length;
10863
+ treeNode.children.forEach((childItem, i) => {
10864
+ if (!childItem.node) return;
10865
+ const childX = parentPos.x + (i - (totalSiblings - 1) / 2) * (SPACING_X / Math.max(1, level * 0.4));
10866
+ const childY = parentPos.y + SPACING_Y;
10867
+ const childPos = new THREE3.Vector3(childX, childY, parentPos.z);
10868
+ const childMesh = addOrUpdateNodeMesh(childItem.node, childPos, true);
10869
+ const line = createAncestryLinkLine(parentMesh, childMesh, resolution, {}, colorHex);
10870
+ ancestryGroup.add(line);
10871
+ ancestryEntry.lines.push(line);
10872
+ renderVertical(childItem, childMesh, childPos, level + 1);
10873
+ });
10874
+ };
10875
+ renderVertical(fullTree, rootNodeMesh, rootTargetPos, 1);
10876
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
10877
+ tweenToTarget(rootTargetPos, 0.7);
10878
+ }, [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, handleClearAncestryVisuals]);
10773
10879
  const handleReadModeBranchNav = (0, import_react23.useCallback)((nodeId, action, direction = "right") => {
10774
10880
  const { ancestry, branchStack } = readingMode;
10775
10881
  if (!ancestry || !ancestry.tree) return;
@@ -11179,22 +11285,24 @@ function XViewScene({
11179
11285
  alert("Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando.");
11180
11286
  return;
11181
11287
  }
11288
+ const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, allAncestries) : { node: fullTree.node, children: [] };
11182
11289
  setAncestryMode({
11183
11290
  isActive: true,
11184
- // --- CORREÇÃO AQUI ---
11185
- // 1. Espalhamos as propriedades do objeto (custom props, ids, etc) PRIMEIRO.
11186
11291
  ...ancestryObject,
11187
- // 2. Definimos a 'tree' DEPOIS. Isso garante que a árvore 'fullTree' (hidratada com os nodes reais)
11188
- // prevaleça sobre a árvore 'crua' (apenas IDs) que veio dentro de 'ancestryObject'.
11189
11292
  tree: fullTree,
11190
- // 3. Garantimos que o resto das configurações de controle estejam corretas
11293
+ abstraction_tree: fullAbstractionTree,
11294
+ // NOVO
11191
11295
  selectedParentId: ancestryObject.ancestral_node,
11296
+ selectedAbstractionParentId: ancestryObject.ancestral_node,
11297
+ // NOVO
11192
11298
  isEditMode: true,
11193
11299
  currentAncestryId: ancestryObject.ancestry_id,
11194
11300
  ancestryName: ancestryObject.name || `Ancestralidade ${fullTree.node.name}`,
11195
11301
  ancestryDescription: ancestryObject.description || "",
11196
11302
  ancestryDescriptionSections: ancestryObject.description_sections || [],
11197
- isAddingNodes: false
11303
+ isAddingNodes: false,
11304
+ isAddingAbstractionNodes: false
11305
+ // NOVO
11198
11306
  });
11199
11307
  },
11200
11308
  [handleRenderAncestry, buildFullAncestryTree]
@@ -11265,6 +11373,7 @@ function XViewScene({
11265
11373
  };
11266
11374
  };
11267
11375
  const treeWithIds = convertTreeToIds(treeToUse);
11376
+ const abstractionTreeWithIds = convertTreeToIds(ancestryMode.abstraction_tree);
11268
11377
  if (!treeWithIds) {
11269
11378
  alert("Erro ao processar a \xE1rvore da ancestralidade.");
11270
11379
  return;
@@ -11280,6 +11389,7 @@ function XViewScene({
11280
11389
  description: ancestryDescription,
11281
11390
  description_sections: ancestrySections,
11282
11391
  tree: treeWithIds,
11392
+ abstraction_tree: abstractionTreeWithIds,
11283
11393
  _imported_from_view_id: originalAncestryObj == null ? void 0 : originalAncestryObj._imported_from_view_id,
11284
11394
  _imported_from_view_owner_id: originalAncestryObj == null ? void 0 : originalAncestryObj._imported_from_view_owner_id,
11285
11395
  _origin_db_ids: originalAncestryObj == null ? void 0 : originalAncestryObj._origin_db_ids,
@@ -11724,6 +11834,7 @@ function XViewScene({
11724
11834
  CreateAncestryPanel,
11725
11835
  {
11726
11836
  ancestryMode,
11837
+ setAncestryMode,
11727
11838
  onSelectParent: handleSelectAncestryParent,
11728
11839
  onRemoveNode: handleRemoveFromAncestry,
11729
11840
  onSave: handleSaveAncestry,
@@ -11742,7 +11853,8 @@ function XViewScene({
11742
11853
  onRenderFullAncestry: (data, allowed, focus, rot) => handleRenderAncestry(data, allowed, focus, rot),
11743
11854
  onClearAncestryVisuals: handleClearAncestryVisuals,
11744
11855
  onUploadFile: upload_file_action,
11745
- onOpenImageViewer: handleOpenImageViewer
11856
+ onOpenImageViewer: handleOpenImageViewer,
11857
+ onRenderAbstractionTree: (data) => handleRenderAbstractionTree(data)
11746
11858
  }
11747
11859
  ),
11748
11860
  editingAncestryRel.visible && /* @__PURE__ */ import_react23.default.createElement(
@@ -12112,7 +12224,16 @@ async function get_scene_view_data_logic(db_services, scene_config, owner_id) {
12112
12224
  if (!parentNodeIds.has(String(ancestry.ancestral_node))) return null;
12113
12225
  const cleanedTree = pruneTree(ancestry.tree);
12114
12226
  if (!cleanedTree) return null;
12115
- return { ...ancestry, tree: cleanedTree };
12227
+ let cleanedAbstraction = null;
12228
+ if (ancestry.abstraction_tree) {
12229
+ cleanedAbstraction = pruneTree(ancestry.abstraction_tree);
12230
+ }
12231
+ return {
12232
+ ...ancestry,
12233
+ tree: cleanedTree,
12234
+ abstraction_tree: cleanedAbstraction
12235
+ // Salva a árvore limpa
12236
+ };
12116
12237
  }).filter(Boolean);
12117
12238
  if (JSON.stringify(cleanedAncestryData) !== originalAncestryDataString) {
12118
12239
  ancestryData = cleanedAncestryData;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/XViewScene.jsx
2
- import React23, { useCallback as useCallback2, useEffect as useEffect20, useRef as useRef17, useState as useState22, useMemo as useMemo12 } from "react";
2
+ import React23, { useCallback as useCallback3, useEffect as useEffect20, useRef as useRef17, useState as useState22, useMemo as useMemo12 } from "react";
3
3
  import { useRouter } from "next/navigation";
4
4
  import { useSession } from "next-auth/react";
5
5
  import CryptoJS from "crypto-js";
@@ -4757,7 +4757,7 @@ function AncestryRelationshipPanel({
4757
4757
  }
4758
4758
 
4759
4759
  // src/components/CreateAncestryPanel.jsx
4760
- import React10, { useState as useState10, useEffect as useEffect9, useMemo as useMemo8, useRef as useRef8 } from "react";
4760
+ import React10, { useState as useState10, useEffect as useEffect9, useMemo as useMemo8, useRef as useRef8, useCallback } from "react";
4761
4761
  import {
4762
4762
  FiEdit2 as FiEdit23,
4763
4763
  FiBookOpen as FiBookOpen2,
@@ -5000,8 +5000,12 @@ var NodeItem = ({ nodeData, onSelectParent, onRemoveNode, onEditRelationship, on
5000
5000
  };
5001
5001
  function CreateAncestryPanel({
5002
5002
  ancestryMode,
5003
+ setAncestryMode,
5004
+ // <--- Nova prop necessária para as novas funções manipuladoras
5003
5005
  onSelectParent,
5006
+ // Mantido para compatibilidade, mas as novas funções usam setAncestryMode
5004
5007
  onRemoveNode,
5008
+ // Mantido para compatibilidade
5005
5009
  onSave,
5006
5010
  onClose,
5007
5011
  onEditRelationship,
@@ -5012,12 +5016,14 @@ function CreateAncestryPanel({
5012
5016
  onUpdateTree,
5013
5017
  onAncestrySectionChange,
5014
5018
  onToggleAddNodes,
5019
+ // Mantido para compatibilidade
5015
5020
  onRenderFullAncestry,
5016
5021
  onHighlightNode,
5017
5022
  onClearAncestryVisuals,
5018
5023
  onUploadFile,
5019
- onOpenImageViewer
5020
- // <--- ADICIONE NA LISTA DE PROPS RECEBIDAS
5024
+ onOpenImageViewer,
5025
+ onRenderAbstractionTree
5026
+ // <--- Nova prop recebida
5021
5027
  }) {
5022
5028
  const {
5023
5029
  tree: rootTree,
@@ -5053,6 +5059,91 @@ function CreateAncestryPanel({
5053
5059
  console.warn("onOpenImageViewer n\xE3o foi passado para CreateAncestryPanel");
5054
5060
  }
5055
5061
  };
5062
+ const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5063
+ setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5064
+ };
5065
+ const handleToggleAddMode = (isAbstraction = false) => {
5066
+ setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5067
+ };
5068
+ const handleRemoveNode = useCallback((pathToRemove, isAbstraction = false) => {
5069
+ if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
5070
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
5071
+ setAncestryMode((prev) => {
5072
+ if (!prev[treeKey]) return prev;
5073
+ const newTree = JSON.parse(JSON.stringify(prev[treeKey]));
5074
+ let currentParent = newTree;
5075
+ for (let i = 0; i < pathToRemove.length - 1; i++) {
5076
+ const childIndex = pathToRemove[i];
5077
+ if (currentParent.children && currentParent.children[childIndex]) {
5078
+ currentParent = currentParent.children[childIndex];
5079
+ } else return prev;
5080
+ }
5081
+ const indexToRemove = pathToRemove[pathToRemove.length - 1];
5082
+ if (currentParent.children && currentParent.children.length > indexToRemove) {
5083
+ currentParent.children.splice(indexToRemove, 1);
5084
+ }
5085
+ return { ...prev, [treeKey]: newTree };
5086
+ });
5087
+ }, [setAncestryMode]);
5088
+ const handleMoveNode = (sourceId, targetId, isAbstraction = false) => {
5089
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
5090
+ const activeTreeContext = ancestryMode[treeKey];
5091
+ if (!activeTreeContext) return;
5092
+ const rootTreeClone = JSON.parse(JSON.stringify(ancestryMode[treeKey]));
5093
+ let targetTreeContext = rootTreeClone;
5094
+ if (!isAbstraction && branchStack.length > 0) {
5095
+ let ptr = rootTreeClone;
5096
+ for (const step of branchStack) {
5097
+ const found = findNodePath(ptr, step.nodeId);
5098
+ if (found && found.node.parallel_branches) {
5099
+ const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
5100
+ if (branch) ptr = branch.tree;
5101
+ }
5102
+ }
5103
+ targetTreeContext = ptr;
5104
+ }
5105
+ let movedNodeData = null;
5106
+ const removeSourceNode = (node) => {
5107
+ if (!node.children) return false;
5108
+ const idx = node.children.findIndex((child) => {
5109
+ var _a;
5110
+ return String(child.is_section ? child.id : (_a = child.node) == null ? void 0 : _a.id) === String(sourceId);
5111
+ });
5112
+ if (idx !== -1) {
5113
+ movedNodeData = node.children[idx];
5114
+ node.children.splice(idx, 1);
5115
+ return true;
5116
+ }
5117
+ for (const child of node.children) {
5118
+ if (removeSourceNode(child)) return true;
5119
+ }
5120
+ return false;
5121
+ };
5122
+ const insertTargetNode = (node) => {
5123
+ var _a;
5124
+ if (String(node.is_section ? node.id : (_a = node.node) == null ? void 0 : _a.id) === String(targetId)) {
5125
+ if (!node.children) node.children = [];
5126
+ node.children.push(movedNodeData);
5127
+ return true;
5128
+ }
5129
+ if (node.children) {
5130
+ for (const child of node.children) {
5131
+ if (insertTargetNode(child)) return true;
5132
+ }
5133
+ }
5134
+ return false;
5135
+ };
5136
+ if (removeSourceNode(targetTreeContext) && movedNodeData) {
5137
+ if (insertTargetNode(targetTreeContext)) {
5138
+ if (!isAbstraction) {
5139
+ updateGlobalTree(rootTreeClone);
5140
+ }
5141
+ setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
5142
+ } else {
5143
+ alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5144
+ }
5145
+ }
5146
+ };
5056
5147
  const takeSnapshot = (tree, name, desc, sections, customProps2, isPrivateVal) => {
5057
5148
  return {
5058
5149
  tree: JSON.stringify(tree),
@@ -5823,66 +5914,6 @@ function CreateAncestryPanel({
5823
5914
  updateGlobalTree(newRootTree);
5824
5915
  }
5825
5916
  }, [description, existingSections]);
5826
- const handleMoveNode = (sourceId, targetId) => {
5827
- if (!activeTree) return;
5828
- const rootTreeClone = JSON.parse(JSON.stringify(ancestryMode.tree));
5829
- let targetTreeContext = rootTreeClone;
5830
- if (branchStack.length > 0) {
5831
- let ptr = rootTreeClone;
5832
- for (const step of branchStack) {
5833
- const found = findNodePath(ptr, step.nodeId);
5834
- if (found && found.node.parallel_branches) {
5835
- const branch = found.node.parallel_branches.find((b) => b.id === step.branchId);
5836
- if (branch) ptr = branch.tree;
5837
- }
5838
- }
5839
- targetTreeContext = ptr;
5840
- }
5841
- let movedNodeData = null;
5842
- const removeSourceNode = (node) => {
5843
- if (!node.children) return;
5844
- const idx = node.children.findIndex((child) => {
5845
- var _a;
5846
- const cId = child.is_section ? child.id : String((_a = child.node) == null ? void 0 : _a.id);
5847
- return String(cId) === String(sourceId);
5848
- });
5849
- if (idx !== -1) {
5850
- movedNodeData = node.children[idx];
5851
- node.children.splice(idx, 1);
5852
- return true;
5853
- }
5854
- for (const child of node.children) {
5855
- if (removeSourceNode(child)) return true;
5856
- }
5857
- return false;
5858
- };
5859
- const insertTargetNode = (node) => {
5860
- var _a;
5861
- const nodeId = node.is_section ? node.id : String((_a = node.node) == null ? void 0 : _a.id);
5862
- if (String(nodeId) === String(targetId)) {
5863
- if (!node.children) node.children = [];
5864
- node.children.push(movedNodeData);
5865
- return true;
5866
- }
5867
- if (node.children) {
5868
- for (const child of node.children) {
5869
- if (insertTargetNode(child)) return true;
5870
- }
5871
- }
5872
- return false;
5873
- };
5874
- const foundAndRemoved = removeSourceNode(targetTreeContext);
5875
- if (!foundAndRemoved || !movedNodeData) {
5876
- console.error("Node de origem n\xE3o encontrado na \xE1rvore.");
5877
- return;
5878
- }
5879
- const inserted = insertTargetNode(targetTreeContext);
5880
- if (!inserted) {
5881
- alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5882
- return;
5883
- }
5884
- updateGlobalTree(rootTreeClone);
5885
- };
5886
5917
  const handleTriggerFullRender = () => {
5887
5918
  var _a;
5888
5919
  if (onRenderFullAncestry && activeTree) {
@@ -6134,7 +6165,7 @@ function CreateAncestryPanel({
6134
6165
  ), /* @__PURE__ */ React10.createElement(
6135
6166
  "button",
6136
6167
  {
6137
- onClick: onToggleAddNodes,
6168
+ onClick: () => handleToggleAddMode(false),
6138
6169
  className: `p-1.5 rounded-md transition-colors ${isAddingNodes ? "bg-cyan-500 text-white shadow-lg shadow-cyan-500/30" : "bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600"}`,
6139
6170
  title: isAddingNodes ? "Concluir edi\xE7\xE3o da estrutura" : "Editar estrutura e adicionar nodes"
6140
6171
  },
@@ -6143,17 +6174,54 @@ function CreateAncestryPanel({
6143
6174
  NodeItem,
6144
6175
  {
6145
6176
  nodeData: activeTree,
6146
- onSelectParent,
6147
- onRemoveNode,
6177
+ onSelectParent: (id) => handleSelectAncestryParent(id, false),
6178
+ onRemoveNode: (path) => handleRemoveNode(path, false),
6148
6179
  onEditRelationship,
6149
- onMoveNode: handleMoveNode,
6180
+ onMoveNode: (s, t) => handleMoveNode(s, t, false),
6150
6181
  selectedParentId,
6151
6182
  level: 0,
6152
6183
  isLast: true,
6153
6184
  path: [],
6154
6185
  isEditable: isAddingNodes
6155
6186
  }
6156
- ), (!activeTree || activeTree.children.length === 0) && !isAddingNodes && /* @__PURE__ */ React10.createElement("div", { className: "text-center py-4 text-xs text-slate-500 italic" }, "A estrutura est\xE1 vazia. Clique no l\xE1pis acima para adicionar nodes."))), branchStack.length === 0 && /* @__PURE__ */ React10.createElement("div", { className: "mt-3 flex items-center justify-end px-1" }, /* @__PURE__ */ React10.createElement("label", { className: "flex items-center gap-2 cursor-pointer group select-none" }, /* @__PURE__ */ React10.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center transition-colors ${isPrivate ? "bg-indigo-500 border-indigo-500" : "border-slate-500 bg-transparent"}` }, isPrivate && /* @__PURE__ */ React10.createElement(FiCheck4, { size: 12, className: "text-white" })), /* @__PURE__ */ React10.createElement(
6187
+ ), (!activeTree || activeTree.children.length === 0) && !isAddingNodes && /* @__PURE__ */ React10.createElement("div", { className: "text-center py-4 text-xs text-slate-500 italic" }, "A estrutura est\xE1 vazia. Clique no l\xE1pis acima para adicionar nodes."))), branchStack.length === 0 && ancestryMode.abstraction_tree && /* @__PURE__ */ React10.createElement("div", { className: `mt-6 rounded-lg border transition-all duration-300 overflow-hidden ${ancestryMode.isAddingAbstractionNodes ? "border-purple-500/40 bg-slate-900/60 ring-1 ring-purple-500/20" : "border-white/10 bg-slate-800/60"}` }, /* @__PURE__ */ React10.createElement("div", { className: `flex items-center justify-between px-3 py-2 border-b ${ancestryMode.isAddingAbstractionNodes ? "bg-purple-900/20 border-purple-500/20" : "bg-white/5 border-white/5"}` }, /* @__PURE__ */ React10.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React10.createElement("span", { className: `text-xs font-semibold uppercase tracking-wider ${ancestryMode.isAddingAbstractionNodes ? "text-purple-300" : "text-slate-400"}` }, "Estrutura de Abstra\xE7\xE3o"), ancestryMode.isAddingAbstractionNodes && /* @__PURE__ */ React10.createElement("span", { className: "text-[10px] bg-purple-500/20 text-purple-300 px-1.5 py-0.5 rounded animate-pulse" }, "Editando")), /* @__PURE__ */ React10.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React10.createElement(
6188
+ "button",
6189
+ {
6190
+ type: "button",
6191
+ onClick: () => {
6192
+ const tempPayload = {
6193
+ ancestry_id: currentAncestryId || "temp_rendering",
6194
+ abstraction_tree: ancestryMode.abstraction_tree
6195
+ };
6196
+ if (onRenderAbstractionTree) onRenderAbstractionTree(tempPayload);
6197
+ },
6198
+ className: "p-1.5 rounded-md bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600 transition-colors",
6199
+ title: "Renderizar Verticalmente no Cen\xE1rio"
6200
+ },
6201
+ /* @__PURE__ */ React10.createElement(FiLayers5, { size: 14 })
6202
+ ), /* @__PURE__ */ React10.createElement(
6203
+ "button",
6204
+ {
6205
+ onClick: () => handleToggleAddMode(true),
6206
+ className: `p-1.5 rounded-md transition-colors ${ancestryMode.isAddingAbstractionNodes ? "bg-purple-500 text-white shadow-lg shadow-purple-500/30" : "bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600"}`,
6207
+ title: "Editar estrutura da abstra\xE7\xE3o"
6208
+ },
6209
+ ancestryMode.isAddingAbstractionNodes ? /* @__PURE__ */ React10.createElement(FiCheck4, { size: 14 }) : /* @__PURE__ */ React10.createElement(FiEdit23, { size: 14 })
6210
+ ))), /* @__PURE__ */ React10.createElement("div", { className: "p-4 space-y-2" }, ancestryMode.isAddingAbstractionNodes && /* @__PURE__ */ React10.createElement("div", { className: "mb-3 p-2 rounded bg-purple-900/20 border border-purple-500/20 text-xs text-purple-200 flex items-start gap-2" }, /* @__PURE__ */ React10.createElement(FiMousePointer, { className: "mt-0.5 flex-shrink-0" }), /* @__PURE__ */ React10.createElement("span", null, "Clique nos nodes do cen\xE1rio para adicion\xE1-los. Arraste e solte para organizar a hierarquia.")), /* @__PURE__ */ React10.createElement(
6211
+ NodeItem,
6212
+ {
6213
+ nodeData: ancestryMode.abstraction_tree,
6214
+ onSelectParent: (id) => handleSelectAncestryParent(id, true),
6215
+ onRemoveNode: (path) => handleRemoveNode(path, true),
6216
+ onEditRelationship,
6217
+ onMoveNode: (s, t) => handleMoveNode(s, t, true),
6218
+ selectedParentId: ancestryMode.selectedAbstractionParentId,
6219
+ level: 0,
6220
+ isLast: true,
6221
+ path: [],
6222
+ isEditable: ancestryMode.isAddingAbstractionNodes
6223
+ }
6224
+ ))), branchStack.length === 0 && /* @__PURE__ */ React10.createElement("div", { className: "mt-3 flex items-center justify-end px-1" }, /* @__PURE__ */ React10.createElement("label", { className: "flex items-center gap-2 cursor-pointer group select-none" }, /* @__PURE__ */ React10.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center transition-colors ${isPrivate ? "bg-indigo-500 border-indigo-500" : "border-slate-500 bg-transparent"}` }, isPrivate && /* @__PURE__ */ React10.createElement(FiCheck4, { size: 12, className: "text-white" })), /* @__PURE__ */ React10.createElement(
6157
6225
  "input",
6158
6226
  {
6159
6227
  type: "checkbox",
@@ -6201,7 +6269,7 @@ function CreateAncestryPanel({
6201
6269
  }
6202
6270
 
6203
6271
  // src/components/ImageViewer.jsx
6204
- import React11, { useState as useState11, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useCallback } from "react";
6272
+ import React11, { useState as useState11, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useCallback as useCallback2 } from "react";
6205
6273
  import { FiX as FiX2, FiChevronLeft as FiChevronLeft3, FiChevronRight as FiChevronRight5 } from "react-icons/fi";
6206
6274
  function ImageViewer({ data, onClose }) {
6207
6275
  var _a;
@@ -6214,12 +6282,12 @@ function ImageViewer({ data, onClose }) {
6214
6282
  setCurrentIndex(startIndex);
6215
6283
  }
6216
6284
  }, [visible, startIndex]);
6217
- const handleNext = useCallback(() => {
6285
+ const handleNext = useCallback2(() => {
6218
6286
  if (images.length > 1) {
6219
6287
  setCurrentIndex((prev) => (prev + 1) % images.length);
6220
6288
  }
6221
6289
  }, [images.length]);
6222
- const handlePrev = useCallback(() => {
6290
+ const handlePrev = useCallback2(() => {
6223
6291
  if (images.length > 1) {
6224
6292
  setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
6225
6293
  }
@@ -8865,10 +8933,10 @@ function XViewScene({
8865
8933
  }
8866
8934
  stateRef.current.nodeIdToParentFileMap = map;
8867
8935
  }, [isInitialized, sceneVersion]);
8868
- const handleNavigateBack = useCallback2(() => {
8936
+ const handleNavigateBack = useCallback3(() => {
8869
8937
  router.push("/dashboard/scenes");
8870
8938
  }, [router]);
8871
- const handleConfirmImport = useCallback2(
8939
+ const handleConfirmImport = useCallback3(
8872
8940
  async (importPayload) => {
8873
8941
  var _a2, _b2;
8874
8942
  let files = [];
@@ -8967,7 +9035,7 @@ function XViewScene({
8967
9035
  const handleOpenImageViewer = (images, startIndex) => {
8968
9036
  setImageViewer({ visible: true, images, startIndex });
8969
9037
  };
8970
- const tweenToTarget = useCallback2((target, zoomFactor = 1, forcedDirection = null) => {
9038
+ const tweenToTarget = useCallback3((target, zoomFactor = 1, forcedDirection = null) => {
8971
9039
  const { camera, controls, tweenGroup } = stateRef.current;
8972
9040
  if (!camera || !controls || !tweenGroup) return;
8973
9041
  const targetPos = target instanceof THREE3.Mesh ? target.getWorldPosition(new THREE3.Vector3()) : target;
@@ -8990,7 +9058,7 @@ function XViewScene({
8990
9058
  if (!t || typeof t.closest !== "function") return false;
8991
9059
  return !!t.closest(".ui-overlay");
8992
9060
  };
8993
- const buildFullAncestryTree = useCallback2((idTree, nodes, ancestries = []) => {
9061
+ const buildFullAncestryTree = useCallback3((idTree, nodes, ancestries = []) => {
8994
9062
  if (!idTree) return null;
8995
9063
  const nodeMap = new Map(nodes.map((n) => [String(n.id), n]));
8996
9064
  const ancestryMap = new Map(ancestries.map((a) => [String(a.ancestry_id), a]));
@@ -9070,7 +9138,7 @@ function XViewScene({
9070
9138
  }
9071
9139
  return recursiveBuild(idTree);
9072
9140
  }, []);
9073
- const handleActivateTimeline = useCallback2(() => {
9141
+ const handleActivateTimeline = useCallback3(() => {
9074
9142
  const { nodeObjects, tweenGroup, timelineIntervalsGroup } = stateRef.current;
9075
9143
  if (!nodeObjects || !tweenGroup || !timelineIntervalsGroup) return;
9076
9144
  while (timelineIntervalsGroup.children.length > 0) {
@@ -9223,7 +9291,7 @@ function XViewScene({
9223
9291
  }
9224
9292
  });
9225
9293
  }, []);
9226
- const handleVersionTimeline = useCallback2((sourceMesh, versionMeshes) => {
9294
+ const handleVersionTimeline = useCallback3((sourceMesh, versionMeshes) => {
9227
9295
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
9228
9296
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0) return;
9229
9297
  versionMeshes.forEach((mesh) => {
@@ -9404,12 +9472,12 @@ function XViewScene({
9404
9472
  isInitialized,
9405
9473
  permissionStatus
9406
9474
  ]);
9407
- const isNodeInView = useCallback2((id) => {
9475
+ const isNodeInView = useCallback3((id) => {
9408
9476
  const key = String(id);
9409
9477
  const objs = stateRef.current.nodeObjects || {};
9410
9478
  return !!objs[key];
9411
9479
  }, []);
9412
- const addOrUpdateNodeMesh = useCallback2((nodeData, position, suppressVersionUpdate = false) => {
9480
+ const addOrUpdateNodeMesh = useCallback3((nodeData, position, suppressVersionUpdate = false) => {
9413
9481
  const { graphGroup, nodeObjects, clickableNodes, glowTexture, tweenGroup } = stateRef.current;
9414
9482
  const nodeId = String(nodeData.id);
9415
9483
  if (nodeObjects[nodeId]) {
@@ -9734,43 +9802,38 @@ function XViewScene({
9734
9802
  return;
9735
9803
  }
9736
9804
  if (ancestry.isActive) {
9737
- if (ancestry.isAddingNodes) {
9805
+ if (ancestry.isAddingNodes || ancestry.isAddingAbstractionNodes) {
9738
9806
  const clickedNode = stateRef.current.dragCandidate || tryPickNode();
9739
9807
  stateRef.current.dragCandidate = null;
9740
9808
  stateRef.current.pointerDown.isDown = false;
9741
9809
  stateRef.current.controls.enabled = true;
9742
- if (clickedNode && ancestry.selectedParentId) {
9810
+ const isAbstraction = ancestry.isAddingAbstractionNodes;
9811
+ const currentSelectedParent = isAbstraction ? ancestry.selectedAbstractionParentId : ancestry.selectedParentId;
9812
+ if (clickedNode && currentSelectedParent) {
9743
9813
  const clickedNodeId = String(clickedNode.userData.id);
9744
- const parentId = String(ancestry.selectedParentId);
9814
+ const parentId = String(currentSelectedParent);
9745
9815
  if (clickedNodeId === parentId) {
9746
9816
  alert("Erro: N\xE3o \xE9 poss\xEDvel adicionar um Node como filho dele mesmo.");
9747
9817
  return;
9748
9818
  }
9749
9819
  const parentInfo = stateRef.current.nodeIdToParentFileMap.get(clickedNodeId);
9750
- if (!parentInfo) {
9751
- console.warn(`Node ${clickedNodeId} n\xE3o encontrado em nenhum arquivo pai.`);
9752
- return;
9753
- }
9820
+ if (!parentInfo) return;
9754
9821
  const fullNodeData = (_a2 = parentDataRef.current[parentInfo.parentFileId]) == null ? void 0 : _a2.nodes.find((n) => String(n.id) === clickedNodeId);
9755
- if (!fullNodeData) {
9756
- console.error(`Falha ao encontrar dados completos do Node ${clickedNodeId} mesmo com parentInfo.`);
9757
- return;
9758
- }
9822
+ if (!fullNodeData) return;
9759
9823
  const addChildToNode = (current, targetParentId, childNode) => {
9760
9824
  const currentId = current.is_section ? current.id || current.section_id : String(current.node.id);
9761
9825
  if (String(currentId) === String(targetParentId)) {
9762
9826
  const alreadyExists = current.children.some((child) => !child.is_section && String(child.node.id) === String(childNode.id));
9763
9827
  if (alreadyExists) return current;
9764
- const newChildren = [...current.children, { node: childNode, children: [], relationship: {} }];
9765
- return { ...current, children: newChildren };
9828
+ return { ...current, children: [...current.children, { node: childNode, children: [], relationship: {} }] };
9766
9829
  }
9767
- const updatedChildren = current.children.map((c) => addChildToNode(c, targetParentId, childNode));
9768
- return { ...current, children: updatedChildren };
9830
+ return { ...current, children: current.children.map((c) => addChildToNode(c, targetParentId, childNode)) };
9769
9831
  };
9770
9832
  setAncestryMode((prev) => {
9771
- if (!prev.tree) return prev;
9772
- const newTree = addChildToNode(prev.tree, parentId, fullNodeData);
9773
- return { ...prev, tree: newTree };
9833
+ const treeKey = isAbstraction ? "abstraction_tree" : "tree";
9834
+ if (!prev[treeKey]) return prev;
9835
+ const newTree = addChildToNode(prev[treeKey], parentId, fullNodeData);
9836
+ return { ...prev, [treeKey]: newTree };
9774
9837
  });
9775
9838
  }
9776
9839
  return;
@@ -10063,7 +10126,7 @@ function XViewScene({
10063
10126
  }
10064
10127
  };
10065
10128
  }, [isInitialized, tweenToTarget, dbSaveUrl, isNodeInView, addOrUpdateNodeMesh, handleActivateTimeline, get_scene_view_data, save_view_data]);
10066
- const handleGhostNodeImageChange = useCallback2((useImage, imageUrl) => {
10129
+ const handleGhostNodeImageChange = useCallback3((useImage, imageUrl) => {
10067
10130
  const { node: ghostNode, line: ghostLine, aura: ghostAura } = stateRef.current.ghostElements;
10068
10131
  const { graphGroup, glowTexture } = stateRef.current;
10069
10132
  if (!ghostNode || !graphGroup) return;
@@ -10105,7 +10168,7 @@ function XViewScene({
10105
10168
  aura: newGhostNode.getObjectByName("aura")
10106
10169
  };
10107
10170
  }, []);
10108
- const handleGhostNodeIntensityChange = useCallback2((newIntensity) => {
10171
+ const handleGhostNodeIntensityChange = useCallback3((newIntensity) => {
10109
10172
  const { node: ghostNode, aura: ghostAura } = stateRef.current.ghostElements;
10110
10173
  if (!ghostNode) return;
10111
10174
  const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
@@ -10126,7 +10189,7 @@ function XViewScene({
10126
10189
  ghostAura.material.opacity = Math.min(0.8, newIntensity * 0.15);
10127
10190
  }
10128
10191
  }, []);
10129
- const handleDetailNodeIntensityChange = useCallback2((nodeId, newIntensity) => {
10192
+ const handleDetailNodeIntensityChange = useCallback3((nodeId, newIntensity) => {
10130
10193
  const mesh = stateRef.current.nodeObjects[String(nodeId)];
10131
10194
  if (!mesh) return;
10132
10195
  const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
@@ -10253,18 +10316,21 @@ function XViewScene({
10253
10316
  setAncestryMode({
10254
10317
  isActive: true,
10255
10318
  tree: { node: nodeData, children: [] },
10319
+ abstraction_tree: { node: nodeData, children: [] },
10256
10320
  selectedParentId: nodeData.id,
10321
+ selectedAbstractionParentId: nodeData.id,
10257
10322
  isEditMode: false,
10258
10323
  currentAncestryId: null,
10259
10324
  ancestryName: `Ancestralidade ${nodeData.name}`,
10260
10325
  ancestryDescription: "",
10261
- isAddingNodes: false
10326
+ isAddingNodes: false,
10327
+ isAddingAbstractionNodes: false
10262
10328
  });
10263
10329
  if (mountRef.current) {
10264
10330
  mountRef.current.style.cursor = "default";
10265
10331
  }
10266
10332
  };
10267
- const handleAncestryTreeUpdate = useCallback2((newTree, extraData = null) => {
10333
+ const handleAncestryTreeUpdate = useCallback3((newTree, extraData = null) => {
10268
10334
  setAncestryMode((prev) => {
10269
10335
  const prevTreeStr = JSON.stringify(prev.tree);
10270
10336
  const newTreeStr = JSON.stringify(newTree);
@@ -10334,7 +10400,7 @@ function XViewScene({
10334
10400
  const handleStartVersioning = (nodeData) => {
10335
10401
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
10336
10402
  };
10337
- const handleClearAncestryVisuals = useCallback2((ancestryId) => {
10403
+ const handleClearAncestryVisuals = useCallback3((ancestryId) => {
10338
10404
  const { renderedAncestries, ancestryGroup } = stateRef.current;
10339
10405
  const renderIndex = renderedAncestries.findIndex((a) => String(a.id) === String(ancestryId));
10340
10406
  if (renderIndex !== -1) {
@@ -10348,7 +10414,7 @@ function XViewScene({
10348
10414
  stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
10349
10415
  }
10350
10416
  }, []);
10351
- const handleRenderAncestry = useCallback2(
10417
+ const handleRenderAncestry = useCallback3(
10352
10418
  async (ancestryObject, allowedSectionIds = null, activeSectionIdForFocus = null, baseRotation = 0, forceReprocess = true) => {
10353
10419
  setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
10354
10420
  if (!ancestryObject || !ancestryObject.tree) {
@@ -10764,7 +10830,47 @@ function XViewScene({
10764
10830
  },
10765
10831
  [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, readingMode.isActive, ancestryMode.isActive]
10766
10832
  );
10767
- const handleReadModeBranchNav = useCallback2((nodeId, action, direction = "right") => {
10833
+ const handleRenderAbstractionTree = useCallback3((ancestryObject) => {
10834
+ setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
10835
+ if (!ancestryObject || !ancestryObject.abstraction_tree) return;
10836
+ const { ancestryGroup, nodeObjects, renderer, renderedAncestries } = stateRef.current;
10837
+ const allParentNodes = Object.values(parentDataRef.current).flatMap((f) => f.nodes);
10838
+ const fullTree = buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, ancestryDataRef.current);
10839
+ if (!fullTree || !fullTree.node) return;
10840
+ const absId = ancestryObject.ancestry_id + "_abs";
10841
+ handleClearAncestryVisuals(absId);
10842
+ const colorHex = 9133302;
10843
+ const resolution = new THREE3.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
10844
+ const ancestryEntry = { id: absId, lines: [], isFullRender: true };
10845
+ renderedAncestries.push(ancestryEntry);
10846
+ const rootNodeId = String(fullTree.node.id);
10847
+ let rootNodeMesh = nodeObjects[rootNodeId];
10848
+ let rootTargetPos = rootNodeMesh ? rootNodeMesh.position.clone() : new THREE3.Vector3(0, 0, 0);
10849
+ if (!rootNodeMesh) {
10850
+ rootNodeMesh = addOrUpdateNodeMesh(fullTree.node, rootTargetPos, true);
10851
+ }
10852
+ const SPACING_Y = -40;
10853
+ const SPACING_X = 55;
10854
+ const renderVertical = (treeNode, parentMesh, parentPos, level) => {
10855
+ if (!treeNode.children || treeNode.children.length === 0) return;
10856
+ const totalSiblings = treeNode.children.length;
10857
+ treeNode.children.forEach((childItem, i) => {
10858
+ if (!childItem.node) return;
10859
+ const childX = parentPos.x + (i - (totalSiblings - 1) / 2) * (SPACING_X / Math.max(1, level * 0.4));
10860
+ const childY = parentPos.y + SPACING_Y;
10861
+ const childPos = new THREE3.Vector3(childX, childY, parentPos.z);
10862
+ const childMesh = addOrUpdateNodeMesh(childItem.node, childPos, true);
10863
+ const line = createAncestryLinkLine(parentMesh, childMesh, resolution, {}, colorHex);
10864
+ ancestryGroup.add(line);
10865
+ ancestryEntry.lines.push(line);
10866
+ renderVertical(childItem, childMesh, childPos, level + 1);
10867
+ });
10868
+ };
10869
+ renderVertical(fullTree, rootNodeMesh, rootTargetPos, 1);
10870
+ stateRef.current.ancestryLinks = renderedAncestries.flatMap((a) => a.lines);
10871
+ tweenToTarget(rootTargetPos, 0.7);
10872
+ }, [addOrUpdateNodeMesh, tweenToTarget, buildFullAncestryTree, handleClearAncestryVisuals]);
10873
+ const handleReadModeBranchNav = useCallback3((nodeId, action, direction = "right") => {
10768
10874
  const { ancestry, branchStack } = readingMode;
10769
10875
  if (!ancestry || !ancestry.tree) return;
10770
10876
  const allAncestries = ancestryDataRef.current || [];
@@ -10908,7 +11014,7 @@ function XViewScene({
10908
11014
  }));
10909
11015
  }
10910
11016
  }, [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]);
10911
- const handleReadModeHighlight = useCallback2((nodeId) => {
11017
+ const handleReadModeHighlight = useCallback3((nodeId) => {
10912
11018
  if (stateRef.current.highlightedNodeId !== nodeId) {
10913
11019
  stateRef.current.highlightedNodeId = nodeId;
10914
11020
  }
@@ -11006,7 +11112,7 @@ function XViewScene({
11006
11112
  // <--- ADICIONADO
11007
11113
  };
11008
11114
  }, [readingMode, buildFullAncestryTree, ancestryDataRef.current]);
11009
- const handleStartReadingAncestry = useCallback2(
11115
+ const handleStartReadingAncestry = useCallback3(
11010
11116
  async (ancestryObject) => {
11011
11117
  setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
11012
11118
  if (!ancestryObject || !ancestryObject.tree) {
@@ -11032,7 +11138,7 @@ function XViewScene({
11032
11138
  },
11033
11139
  [handleRenderAncestry]
11034
11140
  );
11035
- const handleReadModeSectionChange = useCallback2((activeSectionId) => {
11141
+ const handleReadModeSectionChange = useCallback3((activeSectionId) => {
11036
11142
  const { ancestry, branchStack } = readingMode;
11037
11143
  if (!ancestry || !readingMode.isActive) return;
11038
11144
  let targetObj = ancestry;
@@ -11101,10 +11207,10 @@ function XViewScene({
11101
11207
  }, 0);
11102
11208
  handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
11103
11209
  }, [readingMode, handleRenderAncestry, buildFullAncestryTree, ancestryDataRef.current]);
11104
- const handleCloseReadMode = useCallback2(() => {
11210
+ const handleCloseReadMode = useCallback3(() => {
11105
11211
  setReadingMode({ isActive: false, ancestry: null, branchStack: [] });
11106
11212
  }, []);
11107
- const handleAncestrySectionChange = useCallback2((activeSectionId, ancestryOverride = null, rotation = 0) => {
11213
+ const handleAncestrySectionChange = useCallback3((activeSectionId, ancestryOverride = null, rotation = 0) => {
11108
11214
  var _a2, _b2;
11109
11215
  const currentMode = stateRef.current.ancestry;
11110
11216
  let targetObj = ancestryOverride;
@@ -11156,7 +11262,7 @@ function XViewScene({
11156
11262
  const renderPayload = { ...targetObj, tree: treeToRender };
11157
11263
  handleRenderAncestry(renderPayload, allowedIds, focusTargetId, rotation);
11158
11264
  }, [handleRenderAncestry]);
11159
- const handleEditAncestry = useCallback2(
11265
+ const handleEditAncestry = useCallback3(
11160
11266
  async (ancestryObject) => {
11161
11267
  setContextMenu((prev) => prev.visible ? { ...prev, visible: false } : prev);
11162
11268
  if (!ancestryObject || !ancestryObject.tree) {
@@ -11173,22 +11279,24 @@ function XViewScene({
11173
11279
  alert("Falha ao reconstruir a \xE1rvore de ancestralidade. Alguns Nodes podem estar faltando.");
11174
11280
  return;
11175
11281
  }
11282
+ const fullAbstractionTree = ancestryObject.abstraction_tree ? buildFullAncestryTree(ancestryObject.abstraction_tree, allParentNodes, allAncestries) : { node: fullTree.node, children: [] };
11176
11283
  setAncestryMode({
11177
11284
  isActive: true,
11178
- // --- CORREÇÃO AQUI ---
11179
- // 1. Espalhamos as propriedades do objeto (custom props, ids, etc) PRIMEIRO.
11180
11285
  ...ancestryObject,
11181
- // 2. Definimos a 'tree' DEPOIS. Isso garante que a árvore 'fullTree' (hidratada com os nodes reais)
11182
- // prevaleça sobre a árvore 'crua' (apenas IDs) que veio dentro de 'ancestryObject'.
11183
11286
  tree: fullTree,
11184
- // 3. Garantimos que o resto das configurações de controle estejam corretas
11287
+ abstraction_tree: fullAbstractionTree,
11288
+ // NOVO
11185
11289
  selectedParentId: ancestryObject.ancestral_node,
11290
+ selectedAbstractionParentId: ancestryObject.ancestral_node,
11291
+ // NOVO
11186
11292
  isEditMode: true,
11187
11293
  currentAncestryId: ancestryObject.ancestry_id,
11188
11294
  ancestryName: ancestryObject.name || `Ancestralidade ${fullTree.node.name}`,
11189
11295
  ancestryDescription: ancestryObject.description || "",
11190
11296
  ancestryDescriptionSections: ancestryObject.description_sections || [],
11191
- isAddingNodes: false
11297
+ isAddingNodes: false,
11298
+ isAddingAbstractionNodes: false
11299
+ // NOVO
11192
11300
  });
11193
11301
  },
11194
11302
  [handleRenderAncestry, buildFullAncestryTree]
@@ -11196,7 +11304,7 @@ function XViewScene({
11196
11304
  const handleSelectAncestryParent = (nodeId) => {
11197
11305
  setAncestryMode((prev) => ({ ...prev, selectedParentId: nodeId }));
11198
11306
  };
11199
- const handleRemoveFromAncestry = useCallback2((pathToRemove) => {
11307
+ const handleRemoveFromAncestry = useCallback3((pathToRemove) => {
11200
11308
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) {
11201
11309
  console.warn("Tentativa de remover a raiz ou caminho inv\xE1lido.");
11202
11310
  return;
@@ -11221,7 +11329,7 @@ function XViewScene({
11221
11329
  return { ...prev, tree: newTree };
11222
11330
  });
11223
11331
  }, []);
11224
- const handleSaveAncestry = useCallback2(
11332
+ const handleSaveAncestry = useCallback3(
11225
11333
  async (ancestryName, ancestryDescription, ancestrySections, keepOpen = false, treeOverride = null, ancestryCustomProps = {}) => {
11226
11334
  const treeToUse = treeOverride || ancestryMode.tree;
11227
11335
  const { isEditMode, currentAncestryId } = ancestryMode;
@@ -11259,6 +11367,7 @@ function XViewScene({
11259
11367
  };
11260
11368
  };
11261
11369
  const treeWithIds = convertTreeToIds(treeToUse);
11370
+ const abstractionTreeWithIds = convertTreeToIds(ancestryMode.abstraction_tree);
11262
11371
  if (!treeWithIds) {
11263
11372
  alert("Erro ao processar a \xE1rvore da ancestralidade.");
11264
11373
  return;
@@ -11274,6 +11383,7 @@ function XViewScene({
11274
11383
  description: ancestryDescription,
11275
11384
  description_sections: ancestrySections,
11276
11385
  tree: treeWithIds,
11386
+ abstraction_tree: abstractionTreeWithIds,
11277
11387
  _imported_from_view_id: originalAncestryObj == null ? void 0 : originalAncestryObj._imported_from_view_id,
11278
11388
  _imported_from_view_owner_id: originalAncestryObj == null ? void 0 : originalAncestryObj._imported_from_view_owner_id,
11279
11389
  _origin_db_ids: originalAncestryObj == null ? void 0 : originalAncestryObj._origin_db_ids,
@@ -11423,7 +11533,7 @@ function XViewScene({
11423
11533
  });
11424
11534
  setEditingAncestryRel({ visible: false, data: null, path: null });
11425
11535
  };
11426
- const handleDeleteAncestry = useCallback2(
11536
+ const handleDeleteAncestry = useCallback3(
11427
11537
  async (ancestryIdToDelete) => {
11428
11538
  if (!ancestryIdToDelete) {
11429
11539
  alert("ID da ancestralidade n\xE3o encontrado.");
@@ -11485,15 +11595,15 @@ function XViewScene({
11485
11595
  },
11486
11596
  [save_view_data, delete_file_action]
11487
11597
  );
11488
- const handleOpenAncestryBoard = useCallback2(() => {
11598
+ const handleOpenAncestryBoard = useCallback3(() => {
11489
11599
  setIsAncestryBoardOpen(true);
11490
11600
  }, []);
11491
- const handleSelectAncestryFromBoard = useCallback2((ancestry) => {
11601
+ const handleSelectAncestryFromBoard = useCallback3((ancestry) => {
11492
11602
  setIsAncestryBoardOpen(false);
11493
11603
  setIsSidebarOpen(false);
11494
11604
  handleStartReadingAncestry(ancestry);
11495
11605
  }, [handleStartReadingAncestry]);
11496
- const handleSaveAncestryBoard = useCallback2(async (groups) => {
11606
+ const handleSaveAncestryBoard = useCallback3(async (groups) => {
11497
11607
  if (!sceneConfigId || !viewParams || !session) return;
11498
11608
  const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
11499
11609
  await save_ancestry_board_action(sceneConfigId, sceneType, groups, session, ownerId);
@@ -11517,13 +11627,13 @@ function XViewScene({
11517
11627
  return !((_a2 = node.version_node) == null ? void 0 : _a2.is_version);
11518
11628
  });
11519
11629
  }, [parentDataRef.current, sceneVersion]);
11520
- const handleAddExistingNode = useCallback2(
11630
+ const handleAddExistingNode = useCallback3(
11521
11631
  (nodeId) => {
11522
11632
  return userActionHandlers.handleAddExistingNodeById(actionHandlerContext, nodeId);
11523
11633
  },
11524
11634
  [actionHandlerContext]
11525
11635
  );
11526
- const handleSaveCurrentView = useCallback2(async () => {
11636
+ const handleSaveCurrentView = useCallback3(async () => {
11527
11637
  const { nodeObjects, allLinks } = stateRef.current;
11528
11638
  if (!nodeObjects || !allLinks || !sceneSaveUrl || !parentDataRef.current) {
11529
11639
  console.warn("N\xE3o \xE9 poss\xEDvel salvar a cena: estado n\xE3o inicializado ou URL de salvamento ausente.");
@@ -11563,7 +11673,7 @@ function XViewScene({
11563
11673
  const allAvailableAncestries = useMemo12(() => {
11564
11674
  return ancestryDataRef.current || [];
11565
11675
  }, [sceneVersion, isInitialized]);
11566
- const handleOpenReference = useCallback2((referenceData) => {
11676
+ const handleOpenReference = useCallback3((referenceData) => {
11567
11677
  const { type, id } = referenceData;
11568
11678
  if (type === "node") {
11569
11679
  const targetNode = allAvailableNodes.find((n) => String(n.id) === String(id));
@@ -11590,7 +11700,7 @@ function XViewScene({
11590
11700
  }
11591
11701
  }
11592
11702
  }, [allAvailableNodes, allAvailableAncestries, handleEditAncestry, tweenToTarget]);
11593
- const handleToggleAncestryAddMode = useCallback2(() => {
11703
+ const handleToggleAncestryAddMode = useCallback3(() => {
11594
11704
  setAncestryMode((prev) => ({ ...prev, isAddingNodes: !prev.isAddingNodes }));
11595
11705
  }, []);
11596
11706
  if (isLoading || status === "loading" || permissionStatus === "loading") {
@@ -11718,6 +11828,7 @@ function XViewScene({
11718
11828
  CreateAncestryPanel,
11719
11829
  {
11720
11830
  ancestryMode,
11831
+ setAncestryMode,
11721
11832
  onSelectParent: handleSelectAncestryParent,
11722
11833
  onRemoveNode: handleRemoveFromAncestry,
11723
11834
  onSave: handleSaveAncestry,
@@ -11736,7 +11847,8 @@ function XViewScene({
11736
11847
  onRenderFullAncestry: (data, allowed, focus, rot) => handleRenderAncestry(data, allowed, focus, rot),
11737
11848
  onClearAncestryVisuals: handleClearAncestryVisuals,
11738
11849
  onUploadFile: upload_file_action,
11739
- onOpenImageViewer: handleOpenImageViewer
11850
+ onOpenImageViewer: handleOpenImageViewer,
11851
+ onRenderAbstractionTree: (data) => handleRenderAbstractionTree(data)
11740
11852
  }
11741
11853
  ),
11742
11854
  editingAncestryRel.visible && /* @__PURE__ */ React23.createElement(
@@ -12106,7 +12218,16 @@ async function get_scene_view_data_logic(db_services, scene_config, owner_id) {
12106
12218
  if (!parentNodeIds.has(String(ancestry.ancestral_node))) return null;
12107
12219
  const cleanedTree = pruneTree(ancestry.tree);
12108
12220
  if (!cleanedTree) return null;
12109
- return { ...ancestry, tree: cleanedTree };
12221
+ let cleanedAbstraction = null;
12222
+ if (ancestry.abstraction_tree) {
12223
+ cleanedAbstraction = pruneTree(ancestry.abstraction_tree);
12224
+ }
12225
+ return {
12226
+ ...ancestry,
12227
+ tree: cleanedTree,
12228
+ abstraction_tree: cleanedAbstraction
12229
+ // Salva a árvore limpa
12230
+ };
12110
12231
  }).filter(Boolean);
12111
12232
  if (JSON.stringify(cleanedAncestryData) !== originalAncestryDataString) {
12112
12233
  ancestryData = cleanedAncestryData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lv-x-software-house/x_view",
3
- "version": "1.1.8",
3
+ "version": "1.1.9-dev.2",
4
4
  "description": "Pacote privado contendo os componentes e lógica de renderização 3D do X View.",
5
5
  "author": "iv.x - Engenharia de Software - ivxsoftwarehouse@gmail.com",
6
6
  "license": "UNLICENSED",