@lv-x-software-house/x_view 1.2.4-dev.17 → 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 +106 -24
  2. package/dist/index.mjs +106 -24
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7967,7 +7967,11 @@ function InSceneQuestForm({
7967
7967
  availableNodes = [],
7968
7968
  availableAncestries = [],
7969
7969
  onMentionClick,
7970
- onUploadFile
7970
+ onUploadFile,
7971
+ // NOVAS PROPS PARA O GHOST NODE
7972
+ onNameChange,
7973
+ onColorChange,
7974
+ onSizeChange
7971
7975
  }) {
7972
7976
  const [name, setName] = (0, import_react16.useState)("");
7973
7977
  const [types, setTypes] = (0, import_react16.useState)(["quest"]);
@@ -8079,12 +8083,26 @@ function InSceneQuestForm({
8079
8083
  onClick: () => {
8080
8084
  setStatus(s);
8081
8085
  setIsStatusDropdownOpen(false);
8086
+ onColorChange == null ? void 0 : onColorChange(QUEST_STATUS_COLORS[s]);
8082
8087
  },
8083
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"}`
8084
8089
  },
8085
8090
  /* @__PURE__ */ import_react16.default.createElement("span", { className: "w-3 h-3 rounded-full", style: { backgroundColor: QUEST_STATUS_COLORS[s] } }),
8086
8091
  s
8087
- )))))), /* @__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(
8088
8106
  "input",
8089
8107
  {
8090
8108
  type: "text",
@@ -8107,7 +8125,20 @@ function InSceneQuestForm({
8107
8125
  onMentionClick,
8108
8126
  onSaveDescription: (newDesc) => setDescription(newDesc)
8109
8127
  }
8110
- ), /* @__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(
8111
8142
  CustomPropertyDisplay,
8112
8143
  {
8113
8144
  key: prop.id,
@@ -11603,6 +11634,26 @@ function XViewScene({
11603
11634
  const handleStartVersioning = (nodeData) => {
11604
11635
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
11605
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
+ }, []);
11606
11657
  const handleSaveQuestNode = async (context, newQuestData) => {
11607
11658
  const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
11608
11659
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
@@ -11619,9 +11670,7 @@ function XViewScene({
11619
11670
  const sceneFileData = {
11620
11671
  parent_dbs: sceneDataRef2.current.parent_dbs,
11621
11672
  nodes: sceneDataRef2.current.nodes,
11622
- // <-- Mantém o cenário inicial inalterado
11623
11673
  links: sceneDataRef2.current.links,
11624
- // <-- Mantém o cenário inicial inalterado
11625
11674
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
11626
11675
  quest_links: graphDataRef.current[sceneConfigId2].links
11627
11676
  };
@@ -11632,9 +11681,13 @@ function XViewScene({
11632
11681
  ownerId: ownerId2,
11633
11682
  datasetName: "Quests Internas (View)"
11634
11683
  });
11635
- const basePosition = stateRef2.current.controls.target.clone();
11636
- const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
11637
- 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 };
11638
11691
  addStandaloneNodeToScene(stateRef2.current, newNode, finalPosition);
11639
11692
  context.tweenToTarget(finalPosition, 1.2);
11640
11693
  setters.setQuestMode({ isActive: false });
@@ -11672,12 +11725,9 @@ function XViewScene({
11672
11725
  const viewFilePayload = {
11673
11726
  parent_dbs: sceneDataRef2.current.parent_dbs,
11674
11727
  nodes: sceneDataRef2.current.nodes,
11675
- // <-- Usa o estado original intocado
11676
11728
  links: sceneDataRef2.current.links,
11677
- // <-- Usa o estado original intocado
11678
11729
  quest_nodes: specificParentData.nodes,
11679
11730
  quest_links: specificParentData.links
11680
- // Salva a conexão aqui!
11681
11731
  };
11682
11732
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
11683
11733
  } else {
@@ -13085,7 +13135,7 @@ function XViewScene({
13085
13135
  }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
13086
13136
  (0, import_react25.useEffect)(() => {
13087
13137
  function handleKeyDown(event) {
13088
- var _a2, _b2, _c2, _d2;
13138
+ var _a2, _b2, _c2;
13089
13139
  const context = actionHandlerContext;
13090
13140
  if (event.key === "Escape") {
13091
13141
  if (stateRef.current.connection.isActive) userActionHandlers.handleCancelConnection(context);
@@ -13096,7 +13146,9 @@ function XViewScene({
13096
13146
  setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
13097
13147
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13098
13148
  }
13099
- if ((_b2 = context.questMode) == null ? void 0 : _b2.isActive) context.setters.setQuestMode({ isActive: false });
13149
+ if (questMode.isActive) {
13150
+ handleCancelQuest();
13151
+ }
13100
13152
  if (stateRef.current.selectedNodes.size > 0) {
13101
13153
  stateRef.current.selectedNodes.clear();
13102
13154
  }
@@ -13105,16 +13157,42 @@ function XViewScene({
13105
13157
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13106
13158
  }
13107
13159
  if (event.key.toLowerCase() === "q") {
13108
- 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
13109
- !detailsNode && // Condição nova
13110
- !detailsLink && // Condição nova
13111
- !ancestryLinkDetails && // Condição nova
13112
- !imageViewer.visible && // Condição nova
13113
- !editingAncestryRel.visible && // Condição nova
13114
- !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;
13115
13161
  if (isUiClear) {
13116
- 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";
13117
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
+ }
13118
13196
  setQuestMode({ isActive: true });
13119
13197
  }
13120
13198
  }
@@ -13122,7 +13200,6 @@ function XViewScene({
13122
13200
  window.addEventListener("keydown", handleKeyDown);
13123
13201
  return () => window.removeEventListener("keydown", handleKeyDown);
13124
13202
  }, [
13125
- // Dependências: sempre que um painel abrir ou fechar, o React atualiza o listener com os dados frescos
13126
13203
  contextMenu.visible,
13127
13204
  multiContextMenu.visible,
13128
13205
  relationshipMenu.visible,
@@ -13137,7 +13214,9 @@ function XViewScene({
13137
13214
  editingAncestryRel.visible,
13138
13215
  questMode.isActive,
13139
13216
  viewParams,
13140
- actionHandlerContext
13217
+ actionHandlerContext,
13218
+ handleCancelQuest
13219
+ // <-- handleCancelQuest adicionado aqui
13141
13220
  ]);
13142
13221
  if (isLoading || status === "loading" || permissionStatus === "loading") {
13143
13222
  return /* @__PURE__ */ import_react25.default.createElement(LoadingScreen, null);
@@ -13233,7 +13312,10 @@ function XViewScene({
13233
13312
  InSceneQuestForm,
13234
13313
  {
13235
13314
  onSave: (data) => handleSaveQuestNode(actionHandlerContext, data),
13236
- onCancel: () => setQuestMode({ isActive: false }),
13315
+ onCancel: handleCancelQuest,
13316
+ onNameChange: handleGhostNodeNameChange,
13317
+ onColorChange: handleGhostNodeColorChange,
13318
+ onSizeChange: handleGhostNodeSizeChange,
13237
13319
  style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
13238
13320
  refEl: formRef,
13239
13321
  onOpenImageViewer: handleOpenImageViewer,
package/dist/index.mjs CHANGED
@@ -7954,7 +7954,11 @@ function InSceneQuestForm({
7954
7954
  availableNodes = [],
7955
7955
  availableAncestries = [],
7956
7956
  onMentionClick,
7957
- onUploadFile
7957
+ onUploadFile,
7958
+ // NOVAS PROPS PARA O GHOST NODE
7959
+ onNameChange,
7960
+ onColorChange,
7961
+ onSizeChange
7958
7962
  }) {
7959
7963
  const [name, setName] = useState16("");
7960
7964
  const [types, setTypes] = useState16(["quest"]);
@@ -8066,12 +8070,26 @@ function InSceneQuestForm({
8066
8070
  onClick: () => {
8067
8071
  setStatus(s);
8068
8072
  setIsStatusDropdownOpen(false);
8073
+ onColorChange == null ? void 0 : onColorChange(QUEST_STATUS_COLORS[s]);
8069
8074
  },
8070
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"}`
8071
8076
  },
8072
8077
  /* @__PURE__ */ React15.createElement("span", { className: "w-3 h-3 rounded-full", style: { backgroundColor: QUEST_STATUS_COLORS[s] } }),
8073
8078
  s
8074
- )))))), /* @__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(
8075
8093
  "input",
8076
8094
  {
8077
8095
  type: "text",
@@ -8094,7 +8112,20 @@ function InSceneQuestForm({
8094
8112
  onMentionClick,
8095
8113
  onSaveDescription: (newDesc) => setDescription(newDesc)
8096
8114
  }
8097
- ), /* @__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(
8098
8129
  CustomPropertyDisplay,
8099
8130
  {
8100
8131
  key: prop.id,
@@ -11603,6 +11634,26 @@ function XViewScene({
11603
11634
  const handleStartVersioning = (nodeData) => {
11604
11635
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
11605
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
+ }, []);
11606
11657
  const handleSaveQuestNode = async (context, newQuestData) => {
11607
11658
  const { graphDataRef, sceneDataRef: sceneDataRef2, stateRef: stateRef2, setters, actions, sceneSaveUrl: sceneSaveUrl2, viewType, sceneConfigId: sceneConfigId2, ownerId: ownerId2 } = context;
11608
11659
  if (!graphDataRef.current || (viewType == null ? void 0 : viewType.toLowerCase()) !== "view") return;
@@ -11619,9 +11670,7 @@ function XViewScene({
11619
11670
  const sceneFileData = {
11620
11671
  parent_dbs: sceneDataRef2.current.parent_dbs,
11621
11672
  nodes: sceneDataRef2.current.nodes,
11622
- // <-- Mantém o cenário inicial inalterado
11623
11673
  links: sceneDataRef2.current.links,
11624
- // <-- Mantém o cenário inicial inalterado
11625
11674
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
11626
11675
  quest_links: graphDataRef.current[sceneConfigId2].links
11627
11676
  };
@@ -11632,9 +11681,13 @@ function XViewScene({
11632
11681
  ownerId: ownerId2,
11633
11682
  datasetName: "Quests Internas (View)"
11634
11683
  });
11635
- const basePosition = stateRef2.current.controls.target.clone();
11636
- const offset = new THREE3.Vector3((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 5, 0);
11637
- 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 };
11638
11691
  addStandaloneNodeToScene(stateRef2.current, newNode, finalPosition);
11639
11692
  context.tweenToTarget(finalPosition, 1.2);
11640
11693
  setters.setQuestMode({ isActive: false });
@@ -11672,12 +11725,9 @@ function XViewScene({
11672
11725
  const viewFilePayload = {
11673
11726
  parent_dbs: sceneDataRef2.current.parent_dbs,
11674
11727
  nodes: sceneDataRef2.current.nodes,
11675
- // <-- Usa o estado original intocado
11676
11728
  links: sceneDataRef2.current.links,
11677
- // <-- Usa o estado original intocado
11678
11729
  quest_nodes: specificParentData.nodes,
11679
11730
  quest_links: specificParentData.links
11680
- // Salva a conexão aqui!
11681
11731
  };
11682
11732
  await context.actions.save_view_data(sceneSaveUrl2, viewFilePayload);
11683
11733
  } else {
@@ -13085,7 +13135,7 @@ function XViewScene({
13085
13135
  }, [isInitialized, sceneVersion, focusAncestryId, hasOpenedInitialAncestry, handleStartReadingAncestry]);
13086
13136
  useEffect21(() => {
13087
13137
  function handleKeyDown(event) {
13088
- var _a2, _b2, _c2, _d2;
13138
+ var _a2, _b2, _c2;
13089
13139
  const context = actionHandlerContext;
13090
13140
  if (event.key === "Escape") {
13091
13141
  if (stateRef.current.connection.isActive) userActionHandlers.handleCancelConnection(context);
@@ -13096,7 +13146,9 @@ function XViewScene({
13096
13146
  setAncestryMode({ isActive: false, tree: null, selectedParentId: null, isEditMode: false, currentAncestryId: null, ancestryName: "", ancestryDescription: "", ancestryDescriptionSections: [], isAddingNodes: false });
13097
13147
  if (mountRef.current) mountRef.current.style.cursor = "grab";
13098
13148
  }
13099
- if ((_b2 = context.questMode) == null ? void 0 : _b2.isActive) context.setters.setQuestMode({ isActive: false });
13149
+ if (questMode.isActive) {
13150
+ handleCancelQuest();
13151
+ }
13100
13152
  if (stateRef.current.selectedNodes.size > 0) {
13101
13153
  stateRef.current.selectedNodes.clear();
13102
13154
  }
@@ -13105,16 +13157,42 @@ function XViewScene({
13105
13157
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
13106
13158
  }
13107
13159
  if (event.key.toLowerCase() === "q") {
13108
- 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
13109
- !detailsNode && // Condição nova
13110
- !detailsLink && // Condição nova
13111
- !ancestryLinkDetails && // Condição nova
13112
- !imageViewer.visible && // Condição nova
13113
- !editingAncestryRel.visible && // Condição nova
13114
- !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;
13115
13161
  if (isUiClear) {
13116
- 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";
13117
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
+ }
13118
13196
  setQuestMode({ isActive: true });
13119
13197
  }
13120
13198
  }
@@ -13122,7 +13200,6 @@ function XViewScene({
13122
13200
  window.addEventListener("keydown", handleKeyDown);
13123
13201
  return () => window.removeEventListener("keydown", handleKeyDown);
13124
13202
  }, [
13125
- // Dependências: sempre que um painel abrir ou fechar, o React atualiza o listener com os dados frescos
13126
13203
  contextMenu.visible,
13127
13204
  multiContextMenu.visible,
13128
13205
  relationshipMenu.visible,
@@ -13137,7 +13214,9 @@ function XViewScene({
13137
13214
  editingAncestryRel.visible,
13138
13215
  questMode.isActive,
13139
13216
  viewParams,
13140
- actionHandlerContext
13217
+ actionHandlerContext,
13218
+ handleCancelQuest
13219
+ // <-- handleCancelQuest adicionado aqui
13141
13220
  ]);
13142
13221
  if (isLoading || status === "loading" || permissionStatus === "loading") {
13143
13222
  return /* @__PURE__ */ React24.createElement(LoadingScreen, null);
@@ -13233,7 +13312,10 @@ function XViewScene({
13233
13312
  InSceneQuestForm,
13234
13313
  {
13235
13314
  onSave: (data) => handleSaveQuestNode(actionHandlerContext, data),
13236
- onCancel: () => setQuestMode({ isActive: false }),
13315
+ onCancel: handleCancelQuest,
13316
+ onNameChange: handleGhostNodeNameChange,
13317
+ onColorChange: handleGhostNodeColorChange,
13318
+ onSizeChange: handleGhostNodeSizeChange,
13237
13319
  style: { position: "absolute", left: `16px`, top: `16px`, zIndex: 20, transition: "opacity 200ms ease-out" },
13238
13320
  refEl: formRef,
13239
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.17",
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",