@lv-x-software-house/x_view 1.2.4-dev.16 → 1.2.4-dev.18

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 +108 -59
  2. package/dist/index.mjs +108 -59
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1895,10 +1895,6 @@ var userActionHandlers = {
1895
1895
  var _a;
1896
1896
  const isSource = String(link.source) === String(sourceNode.id);
1897
1897
  const targetNodeId = isSource ? link.target : link.source;
1898
- const linkAlreadyInSceneData = sceneDataRef.current.links.some((l) => String(l.id) === String(link.id));
1899
- if (!linkAlreadyInSceneData) {
1900
- sceneDataRef.current.links.push(link);
1901
- }
1902
1898
  if (!nodeObjects[String(targetNodeId)]) {
1903
1899
  const allParentNodes = Object.values(graphDataRef.current).flatMap((fileData) => fileData.nodes);
1904
1900
  const nodeData = allParentNodes.find((n) => String(n.id) === String(targetNodeId));
@@ -1906,9 +1902,6 @@ var userActionHandlers = {
1906
1902
  console.warn(`Dados do Node com ID ${targetNodeId} n\xE3o encontrados no cache.`);
1907
1903
  return;
1908
1904
  }
1909
- if (!sceneDataRef.current.nodes.some((n) => String(n.id) === String(nodeData.id))) {
1910
- sceneDataRef.current.nodes.push(nodeData);
1911
- }
1912
1905
  const startPosition = sourceNodeMesh.position.clone();
1913
1906
  const endPosition = startPosition.clone().add(
1914
1907
  new THREE.Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 15, (Math.random() - 0.5) * 60)
@@ -2280,12 +2273,6 @@ var userActionHandlers = {
2280
2273
  if (!nodeData || !sceneDataRef.current) return;
2281
2274
  const nodeIdToDismiss = nodeData.id;
2282
2275
  const strNodeId = String(nodeIdToDismiss);
2283
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2284
- (n) => String(n.id) !== strNodeId
2285
- );
2286
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2287
- (l) => String(l.source) !== strNodeId && String(l.target) !== strNodeId
2288
- );
2289
2276
  const { ancestryGroup, ancestryLinks } = stateRef.current;
2290
2277
  if (ancestryGroup && ancestryLinks) {
2291
2278
  const remainingAncestryLinks = [];
@@ -2327,22 +2314,12 @@ var userActionHandlers = {
2327
2314
  removeNodeFromScene(stateRef.current, nodeId);
2328
2315
  setters.setDetailsNode((prev) => String(prev == null ? void 0 : prev.id) === String(nodeId) ? null : prev);
2329
2316
  });
2330
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2331
- (n) => String(n.id) === strNodeIdToKeep
2332
- );
2333
- sceneDataRef.current.links = [];
2334
2317
  },
2335
2318
  handleDismissMultipleNodes: (context, nodeIds) => {
2336
2319
  const { stateRef, sceneDataRef, setters } = context;
2337
2320
  setters.setMultiContextMenu({ visible: false });
2338
2321
  if (!nodeIds || nodeIds.size === 0 || !sceneDataRef.current) return;
2339
2322
  const strNodeIds = Array.from(nodeIds).map(String);
2340
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2341
- (n) => !strNodeIds.includes(String(n.id))
2342
- );
2343
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2344
- (l) => !strNodeIds.includes(String(l.source)) && !strNodeIds.includes(String(l.target))
2345
- );
2346
2323
  const { ancestryGroup, ancestryLinks } = stateRef.current;
2347
2324
  if (ancestryGroup && ancestryLinks) {
2348
2325
  const remainingAncestryLinks = [];
@@ -2398,12 +2375,6 @@ var userActionHandlers = {
2398
2375
  removeNodeFromScene(stateRef.current, nodeId);
2399
2376
  setters.setDetailsNode((prev) => String(prev == null ? void 0 : prev.id) === String(nodeId) ? null : prev);
2400
2377
  });
2401
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2402
- (n) => strNodeIdsToKeep.includes(String(n.id))
2403
- );
2404
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2405
- (l) => strNodeIdsToKeep.includes(String(l.source)) && strNodeIdsToKeep.includes(String(l.target))
2406
- );
2407
2378
  stateRef.current.selectedNodes.clear();
2408
2379
  },
2409
2380
  handleDeleteMultipleNodes: async (context, nodeIds) => {
@@ -2661,7 +2632,7 @@ var userActionHandlers = {
2661
2632
  }
2662
2633
  },
2663
2634
  handleAddExistingNodeById: (context, nodeId) => {
2664
- var _a, _b;
2635
+ var _a;
2665
2636
  const { stateRef, sceneDataRef, graphDataRef, tweenToTarget, setters } = context;
2666
2637
  const state = stateRef.current;
2667
2638
  const graphFull = graphDataRef.current;
@@ -2677,16 +2648,12 @@ var userActionHandlers = {
2677
2648
  tweenToTarget(state.nodeObjects[strNodeId]);
2678
2649
  return;
2679
2650
  }
2680
- const alreadyInSceneData = (((_a = sceneDataRef.current) == null ? void 0 : _a.nodes) || []).some((n) => String(n.id) === String(strNodeId));
2681
- if (!alreadyInSceneData) {
2682
- sceneDataRef.current.nodes.push(nodeData);
2683
- }
2684
2651
  const base = state.controls ? state.controls.target.clone() : new THREE.Vector3(0, 0, 0);
2685
2652
  const offset = new THREE.Vector3((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 6, (Math.random() - 0.5) * 20);
2686
2653
  const position = base.add(offset);
2687
2654
  addStandaloneNodeToScene(state, nodeData, position);
2688
2655
  tweenToTarget(position, 1.3);
2689
- (_b = setters == null ? void 0 : setters.setSceneVersion) == null ? void 0 : _b.call(setters, (v) => v + 1);
2656
+ (_a = setters == null ? void 0 : setters.setSceneVersion) == null ? void 0 : _a.call(setters, (v) => v + 1);
2690
2657
  }
2691
2658
  };
2692
2659
 
@@ -8000,7 +7967,11 @@ function InSceneQuestForm({
8000
7967
  availableNodes = [],
8001
7968
  availableAncestries = [],
8002
7969
  onMentionClick,
8003
- onUploadFile
7970
+ onUploadFile,
7971
+ // NOVAS PROPS PARA O GHOST NODE
7972
+ onNameChange,
7973
+ onColorChange,
7974
+ onSizeChange
8004
7975
  }) {
8005
7976
  const [name, setName] = (0, import_react16.useState)("");
8006
7977
  const [types, setTypes] = (0, import_react16.useState)(["quest"]);
@@ -8112,12 +8083,26 @@ function InSceneQuestForm({
8112
8083
  onClick: () => {
8113
8084
  setStatus(s);
8114
8085
  setIsStatusDropdownOpen(false);
8086
+ onColorChange == null ? void 0 : onColorChange(QUEST_STATUS_COLORS[s]);
8115
8087
  },
8116
8088
  className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${status === s ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
8117
8089
  },
8118
8090
  /* @__PURE__ */ import_react16.default.createElement("span", { className: "w-3 h-3 rounded-full", style: { backgroundColor: QUEST_STATUS_COLORS[s] } }),
8119
8091
  s
8120
- )))))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Quest"), /* @__PURE__ */ import_react16.default.createElement("input", { required: true, type: "text", placeholder: "Ex.: Refatorar M\xF3dulo X", value: name, onChange: (e) => setName(e.target.value), className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-indigo-400/60" })), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Tipos Adicionais"), /* @__PURE__ */ import_react16.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 focus-within:ring-2 focus-within:ring-indigo-400/60 transition-all" }, types.map((t, index) => /* @__PURE__ */ import_react16.default.createElement("span", { key: index, className: `flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium border ${t === "quest" ? "bg-sky-500/20 text-sky-200 border-sky-500/30" : "bg-indigo-500/30 text-indigo-100 border-indigo-500/20"}` }, t, t !== "quest" && /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: () => handleRemoveType(index), className: "hover:text-white transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiX, { size: 12 })))), /* @__PURE__ */ import_react16.default.createElement(
8092
+ )))))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Quest"), /* @__PURE__ */ import_react16.default.createElement(
8093
+ "input",
8094
+ {
8095
+ required: true,
8096
+ type: "text",
8097
+ placeholder: "Ex.: Refatorar M\xF3dulo X",
8098
+ value: name,
8099
+ onChange: (e) => {
8100
+ setName(e.target.value);
8101
+ onNameChange == null ? void 0 : onNameChange(e.target.value);
8102
+ },
8103
+ className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-indigo-400/60"
8104
+ }
8105
+ )), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Tipos Adicionais"), /* @__PURE__ */ import_react16.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 focus-within:ring-2 focus-within:ring-indigo-400/60 transition-all" }, types.map((t, index) => /* @__PURE__ */ import_react16.default.createElement("span", { key: index, className: `flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium border ${t === "quest" ? "bg-sky-500/20 text-sky-200 border-sky-500/30" : "bg-indigo-500/30 text-indigo-100 border-indigo-500/20"}` }, t, t !== "quest" && /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: () => handleRemoveType(index), className: "hover:text-white transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiX, { size: 12 })))), /* @__PURE__ */ import_react16.default.createElement(
8121
8106
  "input",
8122
8107
  {
8123
8108
  type: "text",
@@ -8140,7 +8125,20 @@ function InSceneQuestForm({
8140
8125
  onMentionClick,
8141
8126
  onSaveDescription: (newDesc) => setDescription(newDesc)
8142
8127
  }
8143
- ), /* @__PURE__ */ import_react16.default.createElement("div", { className: "absolute top-0 right-0 flex bg-slate-900/50 rounded-bl-lg backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity overflow-hidden border-b border-l border-white/5" }, /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: () => setIsDescriptionModalOpen(true), className: "p-2 text-slate-400 hover:text-white hover:bg-white/10 transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiEdit2, { size: 14 }))), !description && /* @__PURE__ */ import_react16.default.createElement("div", { onClick: () => setIsDescriptionModalOpen(true), className: "absolute inset-0 flex items-center justify-center text-xs text-slate-500 cursor-text" }, "Adicionar descri\xE7\xE3o..."))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Tamanho no Node (Size)"), /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex items-center gap-5" }, ["small", "medium", "large"].map((s) => /* @__PURE__ */ import_react16.default.createElement("button", { key: s, type: "button", onClick: () => setSize(s), className: "flex items-center gap-2 group cursor-pointer focus:outline-none" }, /* @__PURE__ */ import_react16.default.createElement("div", { className: `w-4 h-4 rounded-[4px] border flex items-center justify-center transition-all duration-200 ${size === s ? "bg-indigo-500 border-indigo-500" : "border-slate-600 bg-transparent group-hover:border-slate-500"}` }, size === s && /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiCheck, { size: 12, className: "text-white" })), /* @__PURE__ */ import_react16.default.createElement("span", { className: `text-sm capitalize transition-colors ${size === s ? "text-white font-medium" : "text-slate-400 group-hover:text-slate-300"}` }, s))))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ import_react16.default.createElement("h3", { className: "text-sm font-medium" }, "Propriedades Adicionais"), /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: handleAddProp, className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-md bg-slate-800/70 hover:bg-slate-700/70 border border-white/10 transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiPlus, { size: 14 }), " Adicionar")), /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex flex-col gap-3" }, customProps.map((prop, index) => /* @__PURE__ */ import_react16.default.createElement(
8128
+ ), /* @__PURE__ */ import_react16.default.createElement("div", { className: "absolute top-0 right-0 flex bg-slate-900/50 rounded-bl-lg backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity overflow-hidden border-b border-l border-white/5" }, /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: () => setIsDescriptionModalOpen(true), className: "p-2 text-slate-400 hover:text-white hover:bg-white/10 transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiEdit2, { size: 14 }))), !description && /* @__PURE__ */ import_react16.default.createElement("div", { onClick: () => setIsDescriptionModalOpen(true), className: "absolute inset-0 flex items-center justify-center text-xs text-slate-500 cursor-text" }, "Adicionar descri\xE7\xE3o..."))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react16.default.createElement("label", { className: "text-xs text-slate-300" }, "Tamanho no Node (Size)"), /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex items-center gap-5" }, ["small", "medium", "large"].map((s) => /* @__PURE__ */ import_react16.default.createElement(
8129
+ "button",
8130
+ {
8131
+ key: s,
8132
+ type: "button",
8133
+ onClick: () => {
8134
+ setSize(s);
8135
+ onSizeChange == null ? void 0 : onSizeChange(s);
8136
+ },
8137
+ className: "flex items-center gap-2 group cursor-pointer focus:outline-none"
8138
+ },
8139
+ /* @__PURE__ */ import_react16.default.createElement("div", { className: `w-4 h-4 rounded-[4px] border flex items-center justify-center transition-all duration-200 ${size === s ? "bg-indigo-500 border-indigo-500" : "border-slate-600 bg-transparent group-hover:border-slate-500"}` }, size === s && /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiCheck, { size: 12, className: "text-white" })),
8140
+ /* @__PURE__ */ import_react16.default.createElement("span", { className: `text-sm capitalize transition-colors ${size === s ? "text-white font-medium" : "text-slate-400 group-hover:text-slate-300"}` }, s)
8141
+ )))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ import_react16.default.createElement("h3", { className: "text-sm font-medium" }, "Propriedades Adicionais"), /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", onClick: handleAddProp, className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-md bg-slate-800/70 hover:bg-slate-700/70 border border-white/10 transition-colors" }, /* @__PURE__ */ import_react16.default.createElement(import_fi14.FiPlus, { size: 14 }), " Adicionar")), /* @__PURE__ */ import_react16.default.createElement("div", { className: "flex flex-col gap-3" }, customProps.map((prop, index) => /* @__PURE__ */ import_react16.default.createElement(
8144
8142
  CustomPropertyDisplay,
8145
8143
  {
8146
8144
  key: prop.id,
@@ -11636,6 +11634,26 @@ function XViewScene({
11636
11634
  const handleStartVersioning = (nodeData) => {
11637
11635
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
11638
11636
  };
11637
+ const handleCancelQuest = (0, import_react25.useCallback)(() => {
11638
+ const { graphGroup, ghostElements } = stateRef.current;
11639
+ if (ghostElements.node && graphGroup) {
11640
+ if (ghostElements.node.userData.labelObject) {
11641
+ graphGroup.remove(ghostElements.node.userData.labelObject);
11642
+ if (ghostElements.node.userData.labelObject.material.map) ghostElements.node.userData.labelObject.material.map.dispose();
11643
+ ghostElements.node.userData.labelObject.material.dispose();
11644
+ }
11645
+ graphGroup.remove(ghostElements.node);
11646
+ ghostElements.node.traverse((child) => {
11647
+ if (child.material) {
11648
+ if (Array.isArray(child.material)) child.material.forEach((m) => m.dispose());
11649
+ else child.material.dispose();
11650
+ }
11651
+ if (child.geometry) child.geometry.dispose();
11652
+ });
11653
+ }
11654
+ stateRef.current.ghostElements = { node: null, line: null, aura: null };
11655
+ setQuestMode({ isActive: false });
11656
+ }, []);
11639
11657
  const handleSaveQuestNode = async (context, newQuestData) => {
11640
11658
  const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
11641
11659
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
@@ -11652,9 +11670,7 @@ function XViewScene({
11652
11670
  const sceneFileData = {
11653
11671
  parent_dbs: sceneDataRef2.current.parent_dbs,
11654
11672
  nodes: sceneDataRef2.current.nodes,
11655
- // <-- Mantém o cenário inicial inalterado
11656
11673
  links: sceneDataRef2.current.links,
11657
- // <-- Mantém o cenário inicial inalterado
11658
11674
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
11659
11675
  quest_links: graphDataRef.current[sceneConfigId2].links
11660
11676
  };
@@ -11665,9 +11681,13 @@ function XViewScene({
11665
11681
  ownerId: ownerId2,
11666
11682
  datasetName: "Quests Internas (View)"
11667
11683
  });
11668
- const basePosition = stateRef2.current.controls.target.clone();
11669
- const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
11670
- const finalPosition = basePosition.add(offset);
11684
+ const finalPosition = stateRef2.current.ghostElements.node ? stateRef2.current.ghostElements.node.position.clone() : stateRef2.current.controls.target.clone();
11685
+ const { graphGroup, ghostElements } = stateRef2.current;
11686
+ if (ghostElements.node && graphGroup) {
11687
+ if (ghostElements.node.userData.labelObject) graphGroup.remove(ghostElements.node.userData.labelObject);
11688
+ graphGroup.remove(ghostElements.node);
11689
+ }
11690
+ stateRef2.current.ghostElements = { node: null, line: null, aura: null };
11671
11691
  addStandaloneNodeToScene(stateRef2.current, newNode, finalPosition);
11672
11692
  context.tweenToTarget(finalPosition, 1.2);
11673
11693
  setters.setQuestMode({ isActive: false });
@@ -11705,12 +11725,9 @@ function XViewScene({
11705
11725
  const viewFilePayload = {
11706
11726
  parent_dbs: sceneDataRef2.current.parent_dbs,
11707
11727
  nodes: sceneDataRef2.current.nodes,
11708
- // <-- Usa o estado original intocado
11709
11728
  links: sceneDataRef2.current.links,
11710
- // <-- Usa o estado original intocado
11711
11729
  quest_nodes: specificParentData.nodes,
11712
11730
  quest_links: specificParentData.links
11713
- // Salva a conexão aqui!
11714
11731
  };
11715
11732
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
11716
11733
  } else {
@@ -13118,7 +13135,7 @@ function XViewScene({
13118
13135
  }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
13119
13136
  (0, import_react25.useEffect)(() => {
13120
13137
  function handleKeyDown(event) {
13121
- var _a2, _b2, _c2, _d2;
13138
+ var _a2, _b2, _c2;
13122
13139
  const context = actionHandlerContext;
13123
13140
  if (event.key === "Escape") {
13124
13141
  if (stateRef.current.connection.isActive) userActionHandlers.handleCancelConnection(context);
@@ -13129,7 +13146,9 @@ function XViewScene({
13129
13146
  setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
13130
13147
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13131
13148
  }
13132
- if ((_b2 = context.questMode) == null ? void 0 : _b2.isActive) context.setters.setQuestMode({ isActive: false });
13149
+ if (questMode.isActive) {
13150
+ handleCancelQuest();
13151
+ }
13133
13152
  if (stateRef.current.selectedNodes.size > 0) {
13134
13153
  stateRef.current.selectedNodes.clear();
13135
13154
  }
@@ -13138,16 +13157,42 @@ function XViewScene({
13138
13157
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13139
13158
  }
13140
13159
  if (event.key.toLowerCase() === "q") {
13141
- const isUiClear = !stateRef.current.creation.isActive && !stateRef.current.connection.isActive && !stateRef.current.relink.isActive && !stateRef.current.ancestry.isActive && !((_c2 = context.versionMode) == null ? void 0 : _c2.isActive) && !contextMenu.visible && !multiContextMenu.visible && !relationshipMenu.visible && !readingMode.isActive && !isImportModalOpen && !isAncestryBoardOpen && !isSidebarOpen && // Condição nova
13142
- !detailsNode && // Condição nova
13143
- !detailsLink && // Condição nova
13144
- !ancestryLinkDetails && // Condição nova
13145
- !imageViewer.visible && // Condição nova
13146
- !editingAncestryRel.visible && // Condição nova
13147
- !questMode.isActive;
13160
+ const isUiClear = !stateRef.current.creation.isActive && !stateRef.current.connection.isActive && !stateRef.current.relink.isActive && !stateRef.current.ancestry.isActive && !((_b2 = context.versionMode) == null ? void 0 : _b2.isActive) && !contextMenu.visible && !multiContextMenu.visible && !relationshipMenu.visible && !readingMode.isActive && !isImportModalOpen && !isAncestryBoardOpen && !isSidebarOpen && !detailsNode && !detailsLink && !ancestryLinkDetails && !imageViewer.visible && !editingAncestryRel.visible && !questMode.isActive;
13148
13161
  if (isUiClear) {
13149
- const isView = ((_d2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _d2.toLowerCase()) === "view";
13162
+ const isView = ((_c2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _c2.toLowerCase()) === "view";
13150
13163
  if (!isView) return;
13164
+ const { graphGroup, glowTexture, controls } = stateRef.current;
13165
+ if (graphGroup) {
13166
+ const basePosition = controls.target.clone();
13167
+ const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
13168
+ const ghostPosition = basePosition.add(offset);
13169
+ const ghostData = {
13170
+ id: "ghost_quest",
13171
+ name: "Nova Quest",
13172
+ color: "#64748b",
13173
+ // Cor padrão de "Backlog"
13174
+ size: "medium",
13175
+ intensity: 0,
13176
+ type: ["quest"]
13177
+ };
13178
+ const ghostNode = createNodeMesh(ghostData, ghostPosition, glowTexture);
13179
+ ghostNode.traverse((child) => {
13180
+ if (child.isMesh) {
13181
+ child.material.transparent = true;
13182
+ child.material.opacity = 0.75;
13183
+ }
13184
+ });
13185
+ graphGroup.add(ghostNode);
13186
+ if (ghostNode.userData.labelObject) {
13187
+ graphGroup.add(ghostNode.userData.labelObject);
13188
+ }
13189
+ stateRef.current.ghostElements = {
13190
+ node: ghostNode,
13191
+ line: null,
13192
+ // Quests não possuem linha de conexão na criação
13193
+ aura: ghostNode.getObjectByName("aura")
13194
+ };
13195
+ }
13151
13196
  setQuestMode({ isActive: true });
13152
13197
  }
13153
13198
  }
@@ -13155,7 +13200,6 @@ function XViewScene({
13155
13200
  window.addEventListener("keydown", handleKeyDown);
13156
13201
  return () => window.removeEventListener("keydown", handleKeyDown);
13157
13202
  }, [
13158
- // Dependências: sempre que um painel abrir ou fechar, o React atualiza o listener com os dados frescos
13159
13203
  contextMenu.visible,
13160
13204
  multiContextMenu.visible,
13161
13205
  relationshipMenu.visible,
@@ -13170,7 +13214,9 @@ function XViewScene({
13170
13214
  editingAncestryRel.visible,
13171
13215
  questMode.isActive,
13172
13216
  viewParams,
13173
- actionHandlerContext
13217
+ actionHandlerContext,
13218
+ handleCancelQuest
13219
+ // <-- handleCancelQuest adicionado aqui
13174
13220
  ]);
13175
13221
  if (isLoading || status === "loading" || permissionStatus === "loading") {
13176
13222
  return /* @__PURE__ */ import_react25.default.createElement(LoadingScreen, null);
@@ -13266,7 +13312,10 @@ function XViewScene({
13266
13312
  InSceneQuestForm,
13267
13313
  {
13268
13314
  onSave: (data) => handleSaveQuestNode(actionHandlerContext, data),
13269
- onCancel: () => setQuestMode({ isActive: false }),
13315
+ onCancel: handleCancelQuest,
13316
+ onNameChange: handleGhostNodeNameChange,
13317
+ onColorChange: handleGhostNodeColorChange,
13318
+ onSizeChange: handleGhostNodeSizeChange,
13270
13319
  style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
13271
13320
  refEl: formRef,
13272
13321
  onOpenImageViewer: handleOpenImageViewer,
package/dist/index.mjs CHANGED
@@ -1851,10 +1851,6 @@ var userActionHandlers = {
1851
1851
  var _a;
1852
1852
  const isSource = String(link.source) === String(sourceNode.id);
1853
1853
  const targetNodeId = isSource ? link.target : link.source;
1854
- const linkAlreadyInSceneData = sceneDataRef.current.links.some((l) => String(l.id) === String(link.id));
1855
- if (!linkAlreadyInSceneData) {
1856
- sceneDataRef.current.links.push(link);
1857
- }
1858
1854
  if (!nodeObjects[String(targetNodeId)]) {
1859
1855
  const allParentNodes = Object.values(graphDataRef.current).flatMap((fileData) => fileData.nodes);
1860
1856
  const nodeData = allParentNodes.find((n) => String(n.id) === String(targetNodeId));
@@ -1862,9 +1858,6 @@ var userActionHandlers = {
1862
1858
  console.warn(`Dados do Node com ID ${targetNodeId} n\xE3o encontrados no cache.`);
1863
1859
  return;
1864
1860
  }
1865
- if (!sceneDataRef.current.nodes.some((n) => String(n.id) === String(nodeData.id))) {
1866
- sceneDataRef.current.nodes.push(nodeData);
1867
- }
1868
1861
  const startPosition = sourceNodeMesh.position.clone();
1869
1862
  const endPosition = startPosition.clone().add(
1870
1863
  new THREE.Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 15, (Math.random() - 0.5) * 60)
@@ -2236,12 +2229,6 @@ var userActionHandlers = {
2236
2229
  if (!nodeData || !sceneDataRef.current) return;
2237
2230
  const nodeIdToDismiss = nodeData.id;
2238
2231
  const strNodeId = String(nodeIdToDismiss);
2239
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2240
- (n) => String(n.id) !== strNodeId
2241
- );
2242
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2243
- (l) => String(l.source) !== strNodeId && String(l.target) !== strNodeId
2244
- );
2245
2232
  const { ancestryGroup, ancestryLinks } = stateRef.current;
2246
2233
  if (ancestryGroup && ancestryLinks) {
2247
2234
  const remainingAncestryLinks = [];
@@ -2283,22 +2270,12 @@ var userActionHandlers = {
2283
2270
  removeNodeFromScene(stateRef.current, nodeId);
2284
2271
  setters.setDetailsNode((prev) => String(prev == null ? void 0 : prev.id) === String(nodeId) ? null : prev);
2285
2272
  });
2286
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2287
- (n) => String(n.id) === strNodeIdToKeep
2288
- );
2289
- sceneDataRef.current.links = [];
2290
2273
  },
2291
2274
  handleDismissMultipleNodes: (context, nodeIds) => {
2292
2275
  const { stateRef, sceneDataRef, setters } = context;
2293
2276
  setters.setMultiContextMenu({ visible: false });
2294
2277
  if (!nodeIds || nodeIds.size === 0 || !sceneDataRef.current) return;
2295
2278
  const strNodeIds = Array.from(nodeIds).map(String);
2296
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2297
- (n) => !strNodeIds.includes(String(n.id))
2298
- );
2299
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2300
- (l) => !strNodeIds.includes(String(l.source)) && !strNodeIds.includes(String(l.target))
2301
- );
2302
2279
  const { ancestryGroup, ancestryLinks } = stateRef.current;
2303
2280
  if (ancestryGroup && ancestryLinks) {
2304
2281
  const remainingAncestryLinks = [];
@@ -2354,12 +2331,6 @@ var userActionHandlers = {
2354
2331
  removeNodeFromScene(stateRef.current, nodeId);
2355
2332
  setters.setDetailsNode((prev) => String(prev == null ? void 0 : prev.id) === String(nodeId) ? null : prev);
2356
2333
  });
2357
- sceneDataRef.current.nodes = sceneDataRef.current.nodes.filter(
2358
- (n) => strNodeIdsToKeep.includes(String(n.id))
2359
- );
2360
- sceneDataRef.current.links = sceneDataRef.current.links.filter(
2361
- (l) => strNodeIdsToKeep.includes(String(l.source)) && strNodeIdsToKeep.includes(String(l.target))
2362
- );
2363
2334
  stateRef.current.selectedNodes.clear();
2364
2335
  },
2365
2336
  handleDeleteMultipleNodes: async (context, nodeIds) => {
@@ -2617,7 +2588,7 @@ var userActionHandlers = {
2617
2588
  }
2618
2589
  },
2619
2590
  handleAddExistingNodeById: (context, nodeId) => {
2620
- var _a, _b;
2591
+ var _a;
2621
2592
  const { stateRef, sceneDataRef, graphDataRef, tweenToTarget, setters } = context;
2622
2593
  const state = stateRef.current;
2623
2594
  const graphFull = graphDataRef.current;
@@ -2633,16 +2604,12 @@ var userActionHandlers = {
2633
2604
  tweenToTarget(state.nodeObjects[strNodeId]);
2634
2605
  return;
2635
2606
  }
2636
- const alreadyInSceneData = (((_a = sceneDataRef.current) == null ? void 0 : _a.nodes) || []).some((n) => String(n.id) === String(strNodeId));
2637
- if (!alreadyInSceneData) {
2638
- sceneDataRef.current.nodes.push(nodeData);
2639
- }
2640
2607
  const base = state.controls ? state.controls.target.clone() : new THREE.Vector3(0, 0, 0);
2641
2608
  const offset = new THREE.Vector3((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 6, (Math.random() - 0.5) * 20);
2642
2609
  const position = base.add(offset);
2643
2610
  addStandaloneNodeToScene(state, nodeData, position);
2644
2611
  tweenToTarget(position, 1.3);
2645
- (_b = setters == null ? void 0 : setters.setSceneVersion) == null ? void 0 : _b.call(setters, (v) => v + 1);
2612
+ (_a = setters == null ? void 0 : setters.setSceneVersion) == null ? void 0 : _a.call(setters, (v) => v + 1);
2646
2613
  }
2647
2614
  };
2648
2615
 
@@ -7987,7 +7954,11 @@ function InSceneQuestForm({
7987
7954
  availableNodes = [],
7988
7955
  availableAncestries = [],
7989
7956
  onMentionClick,
7990
- onUploadFile
7957
+ onUploadFile,
7958
+ // NOVAS PROPS PARA O GHOST NODE
7959
+ onNameChange,
7960
+ onColorChange,
7961
+ onSizeChange
7991
7962
  }) {
7992
7963
  const [name, setName] = useState16("");
7993
7964
  const [types, setTypes] = useState16(["quest"]);
@@ -8099,12 +8070,26 @@ function InSceneQuestForm({
8099
8070
  onClick: () => {
8100
8071
  setStatus(s);
8101
8072
  setIsStatusDropdownOpen(false);
8073
+ onColorChange == null ? void 0 : onColorChange(QUEST_STATUS_COLORS[s]);
8102
8074
  },
8103
8075
  className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${status === s ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
8104
8076
  },
8105
8077
  /* @__PURE__ */ React15.createElement("span", { className: "w-3 h-3 rounded-full", style: { backgroundColor: QUEST_STATUS_COLORS[s] } }),
8106
8078
  s
8107
- )))))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Quest"), /* @__PURE__ */ React15.createElement("input", { required: true, type: "text", placeholder: "Ex.: Refatorar M\xF3dulo X", value: name, onChange: (e) => setName(e.target.value), className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-indigo-400/60" })), /* @__PURE__ */ React15.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Tipos Adicionais"), /* @__PURE__ */ React15.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 focus-within:ring-2 focus-within:ring-indigo-400/60 transition-all" }, types.map((t, index) => /* @__PURE__ */ React15.createElement("span", { key: index, className: `flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium border ${t === "quest" ? "bg-sky-500/20 text-sky-200 border-sky-500/30" : "bg-indigo-500/30 text-indigo-100 border-indigo-500/20"}` }, t, t !== "quest" && /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: () => handleRemoveType(index), className: "hover:text-white transition-colors" }, /* @__PURE__ */ React15.createElement(FiX4, { size: 12 })))), /* @__PURE__ */ React15.createElement(
8079
+ )))))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Nome da Quest"), /* @__PURE__ */ React15.createElement(
8080
+ "input",
8081
+ {
8082
+ required: true,
8083
+ type: "text",
8084
+ placeholder: "Ex.: Refatorar M\xF3dulo X",
8085
+ value: name,
8086
+ onChange: (e) => {
8087
+ setName(e.target.value);
8088
+ onNameChange == null ? void 0 : onNameChange(e.target.value);
8089
+ },
8090
+ className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-indigo-400/60"
8091
+ }
8092
+ )), /* @__PURE__ */ React15.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Tipos Adicionais"), /* @__PURE__ */ React15.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 focus-within:ring-2 focus-within:ring-indigo-400/60 transition-all" }, types.map((t, index) => /* @__PURE__ */ React15.createElement("span", { key: index, className: `flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium border ${t === "quest" ? "bg-sky-500/20 text-sky-200 border-sky-500/30" : "bg-indigo-500/30 text-indigo-100 border-indigo-500/20"}` }, t, t !== "quest" && /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: () => handleRemoveType(index), className: "hover:text-white transition-colors" }, /* @__PURE__ */ React15.createElement(FiX4, { size: 12 })))), /* @__PURE__ */ React15.createElement(
8108
8093
  "input",
8109
8094
  {
8110
8095
  type: "text",
@@ -8127,7 +8112,20 @@ function InSceneQuestForm({
8127
8112
  onMentionClick,
8128
8113
  onSaveDescription: (newDesc) => setDescription(newDesc)
8129
8114
  }
8130
- ), /* @__PURE__ */ React15.createElement("div", { className: "absolute top-0 right-0 flex bg-slate-900/50 rounded-bl-lg backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity overflow-hidden border-b border-l border-white/5" }, /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: () => setIsDescriptionModalOpen(true), className: "p-2 text-slate-400 hover:text-white hover:bg-white/10 transition-colors" }, /* @__PURE__ */ React15.createElement(FiEdit26, { size: 14 }))), !description && /* @__PURE__ */ React15.createElement("div", { onClick: () => setIsDescriptionModalOpen(true), className: "absolute inset-0 flex items-center justify-center text-xs text-slate-500 cursor-text" }, "Adicionar descri\xE7\xE3o..."))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Tamanho no Node (Size)"), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-5" }, ["small", "medium", "large"].map((s) => /* @__PURE__ */ React15.createElement("button", { key: s, type: "button", onClick: () => setSize(s), className: "flex items-center gap-2 group cursor-pointer focus:outline-none" }, /* @__PURE__ */ React15.createElement("div", { className: `w-4 h-4 rounded-[4px] border flex items-center justify-center transition-all duration-200 ${size === s ? "bg-indigo-500 border-indigo-500" : "border-slate-600 bg-transparent group-hover:border-slate-500"}` }, size === s && /* @__PURE__ */ React15.createElement(FiCheck9, { size: 12, className: "text-white" })), /* @__PURE__ */ React15.createElement("span", { className: `text-sm capitalize transition-colors ${size === s ? "text-white font-medium" : "text-slate-400 group-hover:text-slate-300"}` }, s))))), /* @__PURE__ */ React15.createElement("div", { className: "pt-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ React15.createElement("h3", { className: "text-sm font-medium" }, "Propriedades Adicionais"), /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: handleAddProp, className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-md bg-slate-800/70 hover:bg-slate-700/70 border border-white/10 transition-colors" }, /* @__PURE__ */ React15.createElement(FiPlus5, { size: 14 }), " Adicionar")), /* @__PURE__ */ React15.createElement("div", { className: "flex flex-col gap-3" }, customProps.map((prop, index) => /* @__PURE__ */ React15.createElement(
8115
+ ), /* @__PURE__ */ React15.createElement("div", { className: "absolute top-0 right-0 flex bg-slate-900/50 rounded-bl-lg backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity overflow-hidden border-b border-l border-white/5" }, /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: () => setIsDescriptionModalOpen(true), className: "p-2 text-slate-400 hover:text-white hover:bg-white/10 transition-colors" }, /* @__PURE__ */ React15.createElement(FiEdit26, { size: 14 }))), !description && /* @__PURE__ */ React15.createElement("div", { onClick: () => setIsDescriptionModalOpen(true), className: "absolute inset-0 flex items-center justify-center text-xs text-slate-500 cursor-text" }, "Adicionar descri\xE7\xE3o..."))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("label", { className: "text-xs text-slate-300" }, "Tamanho no Node (Size)"), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-5" }, ["small", "medium", "large"].map((s) => /* @__PURE__ */ React15.createElement(
8116
+ "button",
8117
+ {
8118
+ key: s,
8119
+ type: "button",
8120
+ onClick: () => {
8121
+ setSize(s);
8122
+ onSizeChange == null ? void 0 : onSizeChange(s);
8123
+ },
8124
+ className: "flex items-center gap-2 group cursor-pointer focus:outline-none"
8125
+ },
8126
+ /* @__PURE__ */ React15.createElement("div", { className: `w-4 h-4 rounded-[4px] border flex items-center justify-center transition-all duration-200 ${size === s ? "bg-indigo-500 border-indigo-500" : "border-slate-600 bg-transparent group-hover:border-slate-500"}` }, size === s && /* @__PURE__ */ React15.createElement(FiCheck9, { size: 12, className: "text-white" })),
8127
+ /* @__PURE__ */ React15.createElement("span", { className: `text-sm capitalize transition-colors ${size === s ? "text-white font-medium" : "text-slate-400 group-hover:text-slate-300"}` }, s)
8128
+ )))), /* @__PURE__ */ React15.createElement("div", { className: "pt-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ React15.createElement("h3", { className: "text-sm font-medium" }, "Propriedades Adicionais"), /* @__PURE__ */ React15.createElement("button", { type: "button", onClick: handleAddProp, className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-md bg-slate-800/70 hover:bg-slate-700/70 border border-white/10 transition-colors" }, /* @__PURE__ */ React15.createElement(FiPlus5, { size: 14 }), " Adicionar")), /* @__PURE__ */ React15.createElement("div", { className: "flex flex-col gap-3" }, customProps.map((prop, index) => /* @__PURE__ */ React15.createElement(
8131
8129
  CustomPropertyDisplay,
8132
8130
  {
8133
8131
  key: prop.id,
@@ -11636,6 +11634,26 @@ function XViewScene({
11636
11634
  const handleStartVersioning = (nodeData) => {
11637
11635
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
11638
11636
  };
11637
+ const handleCancelQuest = useCallback4(() => {
11638
+ const { graphGroup, ghostElements } = stateRef.current;
11639
+ if (ghostElements.node && graphGroup) {
11640
+ if (ghostElements.node.userData.labelObject) {
11641
+ graphGroup.remove(ghostElements.node.userData.labelObject);
11642
+ if (ghostElements.node.userData.labelObject.material.map) ghostElements.node.userData.labelObject.material.map.dispose();
11643
+ ghostElements.node.userData.labelObject.material.dispose();
11644
+ }
11645
+ graphGroup.remove(ghostElements.node);
11646
+ ghostElements.node.traverse((child) => {
11647
+ if (child.material) {
11648
+ if (Array.isArray(child.material)) child.material.forEach((m) => m.dispose());
11649
+ else child.material.dispose();
11650
+ }
11651
+ if (child.geometry) child.geometry.dispose();
11652
+ });
11653
+ }
11654
+ stateRef.current.ghostElements = { node: null, line: null, aura: null };
11655
+ setQuestMode({ isActive: false });
11656
+ }, []);
11639
11657
  const handleSaveQuestNode = async (context, newQuestData) => {
11640
11658
  const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
11641
11659
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
@@ -11652,9 +11670,7 @@ function XViewScene({
11652
11670
  const sceneFileData = {
11653
11671
  parent_dbs: sceneDataRef2.current.parent_dbs,
11654
11672
  nodes: sceneDataRef2.current.nodes,
11655
- // <-- Mantém o cenário inicial inalterado
11656
11673
  links: sceneDataRef2.current.links,
11657
- // <-- Mantém o cenário inicial inalterado
11658
11674
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
11659
11675
  quest_links: graphDataRef.current[sceneConfigId2].links
11660
11676
  };
@@ -11665,9 +11681,13 @@ function XViewScene({
11665
11681
  ownerId: ownerId2,
11666
11682
  datasetName: "Quests Internas (View)"
11667
11683
  });
11668
- const basePosition = stateRef2.current.controls.target.clone();
11669
- const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
11670
- const finalPosition = basePosition.add(offset);
11684
+ const finalPosition = stateRef2.current.ghostElements.node ? stateRef2.current.ghostElements.node.position.clone() : stateRef2.current.controls.target.clone();
11685
+ const { graphGroup, ghostElements } = stateRef2.current;
11686
+ if (ghostElements.node && graphGroup) {
11687
+ if (ghostElements.node.userData.labelObject) graphGroup.remove(ghostElements.node.userData.labelObject);
11688
+ graphGroup.remove(ghostElements.node);
11689
+ }
11690
+ stateRef2.current.ghostElements = { node: null, line: null, aura: null };
11671
11691
  addStandaloneNodeToScene(stateRef2.current, newNode, finalPosition);
11672
11692
  context.tweenToTarget(finalPosition, 1.2);
11673
11693
  setters.setQuestMode({ isActive: false });
@@ -11705,12 +11725,9 @@ function XViewScene({
11705
11725
  const viewFilePayload = {
11706
11726
  parent_dbs: sceneDataRef2.current.parent_dbs,
11707
11727
  nodes: sceneDataRef2.current.nodes,
11708
- // <-- Usa o estado original intocado
11709
11728
  links: sceneDataRef2.current.links,
11710
- // <-- Usa o estado original intocado
11711
11729
  quest_nodes: specificParentData.nodes,
11712
11730
  quest_links: specificParentData.links
11713
- // Salva a conexão aqui!
11714
11731
  };
11715
11732
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
11716
11733
  } else {
@@ -13118,7 +13135,7 @@ function XViewScene({
13118
13135
  }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
13119
13136
  useEffect21(() => {
13120
13137
  function handleKeyDown(event) {
13121
- var _a2, _b2, _c2, _d2;
13138
+ var _a2, _b2, _c2;
13122
13139
  const context = actionHandlerContext;
13123
13140
  if (event.key === "Escape") {
13124
13141
  if (stateRef.current.connection.isActive) userActionHandlers.handleCancelConnection(context);
@@ -13129,7 +13146,9 @@ function XViewScene({
13129
13146
  setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
13130
13147
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13131
13148
  }
13132
- if ((_b2 = context.questMode) == null ? void 0 : _b2.isActive) context.setters.setQuestMode({ isActive: false });
13149
+ if (questMode.isActive) {
13150
+ handleCancelQuest();
13151
+ }
13133
13152
  if (stateRef.current.selectedNodes.size > 0) {
13134
13153
  stateRef.current.selectedNodes.clear();
13135
13154
  }
@@ -13138,16 +13157,42 @@ function XViewScene({
13138
13157
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13139
13158
  }
13140
13159
  if (event.key.toLowerCase() === "q") {
13141
- const isUiClear = !stateRef.current.creation.isActive && !stateRef.current.connection.isActive && !stateRef.current.relink.isActive && !stateRef.current.ancestry.isActive && !((_c2 = context.versionMode) == null ? void 0 : _c2.isActive) && !contextMenu.visible && !multiContextMenu.visible && !relationshipMenu.visible && !readingMode.isActive && !isImportModalOpen && !isAncestryBoardOpen && !isSidebarOpen && // Condição nova
13142
- !detailsNode && // Condição nova
13143
- !detailsLink && // Condição nova
13144
- !ancestryLinkDetails && // Condição nova
13145
- !imageViewer.visible && // Condição nova
13146
- !editingAncestryRel.visible && // Condição nova
13147
- !questMode.isActive;
13160
+ const isUiClear = !stateRef.current.creation.isActive && !stateRef.current.connection.isActive && !stateRef.current.relink.isActive && !stateRef.current.ancestry.isActive && !((_b2 = context.versionMode) == null ? void 0 : _b2.isActive) && !contextMenu.visible && !multiContextMenu.visible && !relationshipMenu.visible && !readingMode.isActive && !isImportModalOpen && !isAncestryBoardOpen && !isSidebarOpen && !detailsNode && !detailsLink && !ancestryLinkDetails && !imageViewer.visible && !editingAncestryRel.visible && !questMode.isActive;
13148
13161
  if (isUiClear) {
13149
- const isView = ((_d2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _d2.toLowerCase()) === "view";
13162
+ const isView = ((_c2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _c2.toLowerCase()) === "view";
13150
13163
  if (!isView) return;
13164
+ const { graphGroup, glowTexture, controls } = stateRef.current;
13165
+ if (graphGroup) {
13166
+ const basePosition = controls.target.clone();
13167
+ const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
13168
+ const ghostPosition = basePosition.add(offset);
13169
+ const ghostData = {
13170
+ id: "ghost_quest",
13171
+ name: "Nova Quest",
13172
+ color: "#64748b",
13173
+ // Cor padrão de "Backlog"
13174
+ size: "medium",
13175
+ intensity: 0,
13176
+ type: ["quest"]
13177
+ };
13178
+ const ghostNode = createNodeMesh(ghostData, ghostPosition, glowTexture);
13179
+ ghostNode.traverse((child) => {
13180
+ if (child.isMesh) {
13181
+ child.material.transparent = true;
13182
+ child.material.opacity = 0.75;
13183
+ }
13184
+ });
13185
+ graphGroup.add(ghostNode);
13186
+ if (ghostNode.userData.labelObject) {
13187
+ graphGroup.add(ghostNode.userData.labelObject);
13188
+ }
13189
+ stateRef.current.ghostElements = {
13190
+ node: ghostNode,
13191
+ line: null,
13192
+ // Quests não possuem linha de conexão na criação
13193
+ aura: ghostNode.getObjectByName("aura")
13194
+ };
13195
+ }
13151
13196
  setQuestMode({ isActive: true });
13152
13197
  }
13153
13198
  }
@@ -13155,7 +13200,6 @@ function XViewScene({
13155
13200
  window.addEventListener("keydown", handleKeyDown);
13156
13201
  return () => window.removeEventListener("keydown", handleKeyDown);
13157
13202
  }, [
13158
- // Dependências: sempre que um painel abrir ou fechar, o React atualiza o listener com os dados frescos
13159
13203
  contextMenu.visible,
13160
13204
  multiContextMenu.visible,
13161
13205
  relationshipMenu.visible,
@@ -13170,7 +13214,9 @@ function XViewScene({
13170
13214
  editingAncestryRel.visible,
13171
13215
  questMode.isActive,
13172
13216
  viewParams,
13173
- actionHandlerContext
13217
+ actionHandlerContext,
13218
+ handleCancelQuest
13219
+ // <-- handleCancelQuest adicionado aqui
13174
13220
  ]);
13175
13221
  if (isLoading || status === "loading" || permissionStatus === "loading") {
13176
13222
  return /* @__PURE__ */ React24.createElement(LoadingScreen, null);
@@ -13266,7 +13312,10 @@ function XViewScene({
13266
13312
  InSceneQuestForm,
13267
13313
  {
13268
13314
  onSave: (data) => handleSaveQuestNode(actionHandlerContext, data),
13269
- onCancel: () => setQuestMode({ isActive: false }),
13315
+ onCancel: handleCancelQuest,
13316
+ onNameChange: handleGhostNodeNameChange,
13317
+ onColorChange: handleGhostNodeColorChange,
13318
+ onSizeChange: handleGhostNodeSizeChange,
13270
13319
  style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
13271
13320
  refEl: formRef,
13272
13321
  onOpenImageViewer: handleOpenImageViewer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lv-x-software-house/x_view",
3
- "version": "1.2.4-dev.16",
3
+ "version": "1.2.4-dev.18",
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",