@lv-x-software-house/x_view 1.2.6-dev.3 → 1.2.6-dev.31

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 +1597 -866
  2. package/dist/index.mjs +1518 -806
  3. package/package.json +4 -22
package/dist/index.mjs CHANGED
@@ -1,11 +1,9 @@
1
- "use client";
2
-
3
1
  // src/XViewScene.jsx
4
2
  import React30, {
5
- useCallback as useCallback4,
3
+ useCallback as useCallback5,
6
4
  useEffect as useEffect24,
7
- useRef as useRef19,
8
- useState as useState28,
5
+ useRef as useRef21,
6
+ useState as useState29,
9
7
  useMemo as useMemo13
10
8
  } from "react";
11
9
  import { useRouter, useSearchParams } from "next/navigation";
@@ -40,8 +38,8 @@ function defineAbilityFor(role) {
40
38
  can("read", "Versioning");
41
39
  can("read", "Node");
42
40
  can("read", "Scene");
43
- can("read", "AncestryBoard");
44
- can("read", "AncestryGroup");
41
+ can("read", "Boards");
42
+ can("read", "BoardGroups");
45
43
  can("dismiss", "Node");
46
44
  cannot("update", "Scene");
47
45
  cannot("import", "File");
@@ -54,10 +52,10 @@ function defineAbilityFor(role) {
54
52
  cannot("create", "Ancestry");
55
53
  cannot("update", "Ancestry");
56
54
  cannot("delete", "Ancestry");
57
- cannot("create", "AncestryGroup");
58
- cannot("update", "AncestryGroup");
59
- cannot("delete", "AncestryGroup");
60
- cannot("manage", "AncestryBoard");
55
+ cannot("create", "BoardGroups");
56
+ cannot("update", "BoardGroups");
57
+ cannot("delete", "BoardGroups");
58
+ cannot("manage", "Boards");
61
59
  cannot("update", "Node");
62
60
  break;
63
61
  case "link_viewer":
@@ -66,8 +64,8 @@ function defineAbilityFor(role) {
66
64
  can("read", "Versioning");
67
65
  cannot("read", "Scene");
68
66
  cannot("read", "Connection");
69
- cannot("read", "AncestryBoard");
70
- cannot("read", "AncestryGroup");
67
+ cannot("read", "Boards");
68
+ cannot("read", "BoardGroups");
71
69
  cannot("dismiss", "Node");
72
70
  cannot("manage", "all");
73
71
  break;
@@ -1055,6 +1053,8 @@ var userActionHandlers = {
1055
1053
  if (isCrossDataset) {
1056
1054
  graphDataRef.current[finalTargetDatasetId] = targetDataToUpdate;
1057
1055
  }
1056
+ if (sceneDataRef.current.parent_dbs.some((db) => String(db.db_id) === String(sourceParentInfo.parentFileId))) {
1057
+ }
1058
1058
  const finalPosition = stateRef.current.ghostElements.node.position.clone();
1059
1059
  addNewNodeToScene(stateRef.current, newNode, newLink, finalPosition);
1060
1060
  stateRef.current.nodeIdToParentFileMap.set(String(newNode.id), {
@@ -1253,6 +1253,9 @@ var userActionHandlers = {
1253
1253
  );
1254
1254
  }
1255
1255
  graphDataRef.current[parentFileId] = specificParentData;
1256
+ if (isView && parentFileId === context.sceneConfigId) {
1257
+ sceneDataRef.current.quest_nodes = [...specificParentData.nodes];
1258
+ }
1256
1259
  const finalPosition = stateRef.current.ghostElements.node.position.clone();
1257
1260
  addNewNodeToScene(stateRef.current, newNode, newLink, finalPosition);
1258
1261
  setters.setSceneVersion((v) => v + 1);
@@ -2272,8 +2275,6 @@ var userActionHandlers = {
2272
2275
  milestones: sceneDataRef.current.milestones || [],
2273
2276
  quest_counter: sceneDataRef.current.quest_counter || 1
2274
2277
  };
2275
- sceneDataRef.current.quest_nodes = specificParentData.nodes;
2276
- sceneDataRef.current.quest_links = specificParentData.links;
2277
2278
  await context.actions.save_view_data(
2278
2279
  context.sceneSaveUrl,
2279
2280
  viewFilePayload
@@ -2286,6 +2287,10 @@ var userActionHandlers = {
2286
2287
  );
2287
2288
  }
2288
2289
  graphDataRef.current[parentFileId] = specificParentData;
2290
+ if (isView && parentFileId === context.sceneConfigId) {
2291
+ sceneDataRef.current.quest_nodes = [...specificParentData.nodes];
2292
+ sceneDataRef.current.quest_counter = specificParentData.quest_counter || sceneDataRef.current.quest_counter;
2293
+ }
2289
2294
  updateExistingNodeVisuals(stateRef.current, nodeToSave);
2290
2295
  setters.setSceneVersion((v) => v + 1);
2291
2296
  if (!keepOpen) {
@@ -3838,7 +3843,7 @@ function XViewSidebar({
3838
3843
  importSuccessMessage = "",
3839
3844
  onActivateTimeline = () => {
3840
3845
  },
3841
- onOpenAncestryBoard = () => {
3846
+ onOpenBoards = () => {
3842
3847
  },
3843
3848
  viewType,
3844
3849
  viewName,
@@ -4339,12 +4344,12 @@ function XViewSidebar({
4339
4344
  ), /* @__PURE__ */ React2.createElement(
4340
4345
  "button",
4341
4346
  {
4342
- onClick: onOpenAncestryBoard,
4347
+ onClick: onOpenBoards,
4343
4348
  className: buttonBaseClass,
4344
- title: "Abrir painel de Ancestralidades"
4349
+ title: "Abrir painel de Boards"
4345
4350
  },
4346
- /* @__PURE__ */ React2.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React2.createElement("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }), /* @__PURE__ */ React2.createElement("polyline", { points: "2 17 12 22 22 17" }), /* @__PURE__ */ React2.createElement("polyline", { points: "2 12 12 17 22 12" })),
4347
- "Ancestry Board"
4351
+ /* @__PURE__ */ React2.createElement(FiLayers, { size: 16 }),
4352
+ "Boards"
4348
4353
  )), /* @__PURE__ */ React2.createElement("div", { className: "h-px bg-white/10" }), /* @__PURE__ */ React2.createElement("section", { className: "space-y-3" }, /* @__PURE__ */ React2.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Milestones"), /* @__PURE__ */ React2.createElement(
4349
4354
  "button",
4350
4355
  {
@@ -12944,586 +12949,806 @@ function AncestryLinkDetailsPanel({ data, onClose, onOpenImageViewer, onOpenRefe
12944
12949
  );
12945
12950
  }
12946
12951
 
12947
- // src/components/AncestryBoard.jsx
12948
- import React28, { useState as useState26, useMemo as useMemo12, useEffect as useEffect23, useRef as useRef18 } from "react";
12952
+ // src/components/Boards.jsx
12953
+ import React28, { useState as useState27, useMemo as useMemo12, useEffect as useEffect23, useRef as useRef20 } from "react";
12949
12954
  import {
12950
- FiSearch as FiSearch9,
12955
+ FiSearch as FiSearch8,
12951
12956
  FiLayers as FiLayers7,
12952
- FiCornerUpRight as FiCornerUpRight4,
12953
12957
  FiPlay,
12954
12958
  FiPlus as FiPlus10,
12955
12959
  FiTrash2 as FiTrash23,
12956
12960
  FiArrowLeft as FiArrowLeft3,
12957
12961
  FiArrowRight as FiArrowRight2,
12962
+ FiArrowUp,
12963
+ FiArrowDown,
12958
12964
  FiCheckCircle,
12959
12965
  FiLoader as FiLoader5,
12960
12966
  FiX as FiX8,
12961
- FiAlertTriangle,
12962
- FiCheckSquare as FiCheckSquare4,
12963
12967
  FiBox as FiBox2,
12964
- FiGitBranch as FiGitBranch4
12968
+ FiCheckSquare as FiCheckSquare4,
12969
+ FiGitBranch as FiGitBranch4,
12970
+ FiFolder as FiFolder3,
12971
+ FiChevronRight as FiChevronRight7,
12972
+ FiCornerUpLeft as FiCornerUpLeft3
12965
12973
  } from "react-icons/fi";
12966
12974
  import { v4 as uuidv411 } from "uuid";
12967
12975
 
12968
- // src/components/MentionEditor.jsx
12969
- import React27, { useMemo as useMemo11, useEffect as useEffect22 } from "react";
12970
- import { useEditor, EditorContent, ReactRenderer } from "@tiptap/react";
12971
- import StarterKit from "@tiptap/starter-kit";
12972
- import Mention from "@tiptap/extension-mention";
12973
- import tippy from "tippy.js";
12976
+ // src/components/RichGroupEditor.jsx
12977
+ import React27, { useEffect as useEffect22, useRef as useRef19, useState as useState26, useCallback as useCallback4 } from "react";
12974
12978
 
12975
- // src/components/MentionList.jsx
12976
- import React26, {
12977
- forwardRef,
12978
- useEffect as useEffect21,
12979
- useImperativeHandle,
12980
- useState as useState25
12981
- } from "react";
12982
- import {
12983
- FiLayers as FiLayers6,
12984
- FiCheckSquare as FiCheckSquare3,
12985
- FiBox,
12986
- FiGitBranch as FiGitBranch3,
12987
- FiSearch as FiSearch8
12988
- } from "react-icons/fi";
12989
- var MentionList = forwardRef((props, ref) => {
12990
- const [selectedIndex, setSelectedIndex] = useState25(0);
12991
- const selectItem = (index) => {
12992
- const item = props.items[index];
12993
- if (item) {
12994
- props.command({ id: item.id, label: item.name, type: item.type });
12995
- }
12996
- };
12997
- const upHandler = () => {
12998
- setSelectedIndex(
12999
- (selectedIndex + props.items.length - 1) % props.items.length
13000
- );
13001
- };
13002
- const downHandler = () => {
13003
- setSelectedIndex((selectedIndex + 1) % props.items.length);
13004
- };
13005
- const enterHandler = () => {
13006
- selectItem(selectedIndex);
13007
- };
13008
- useEffect21(() => setSelectedIndex(0), [props.items]);
13009
- useImperativeHandle(ref, () => ({
13010
- onKeyDown: ({ event }) => {
13011
- if (event.key === "ArrowUp") {
13012
- upHandler();
13013
- return true;
13014
- }
13015
- if (event.key === "ArrowDown") {
13016
- downHandler();
13017
- return true;
13018
- }
13019
- if (event.key === "Enter") {
13020
- enterHandler();
13021
- return true;
12979
+ // src/components/MentionPopover.jsx
12980
+ import React26, { useEffect as useEffect21, useRef as useRef18, useState as useState25, useMemo as useMemo11 } from "react";
12981
+ import { FiLayers as FiLayers6, FiCheckSquare as FiCheckSquare3, FiGitBranch as FiGitBranch3, FiBox } from "react-icons/fi";
12982
+ var TYPE_CONFIG = {
12983
+ ancestry: { Icon: FiLayers6, color: "text-indigo-400", label: "Ancestralidade" },
12984
+ quest: { Icon: FiCheckSquare3, color: "text-emerald-400", label: "Quest" },
12985
+ version: { Icon: FiGitBranch3, color: "text-amber-400", label: "Vers\xE3o" },
12986
+ node: { Icon: FiBox, color: "text-slate-400", label: "Node" }
12987
+ };
12988
+ function MentionPopover({ query, items, position, onSelect, onClose }) {
12989
+ const [activeIndex, setActiveIndex] = useState25(0);
12990
+ const listRef = useRef18(null);
12991
+ const filtered = useMemo11(() => {
12992
+ const term = (query || "").toLowerCase().trim();
12993
+ if (!term) return items.slice(0, 8);
12994
+ return items.filter(
12995
+ (item) => (item.searchLabel || "").toLowerCase().includes(term) || (item.searchDesc || "").toLowerCase().includes(term)
12996
+ ).slice(0, 8);
12997
+ }, [items, query]);
12998
+ useEffect21(() => {
12999
+ setActiveIndex(0);
13000
+ }, [query]);
13001
+ useEffect21(() => {
13002
+ const handleKeyDown = (e) => {
13003
+ if (e.key === "ArrowDown") {
13004
+ e.preventDefault();
13005
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
13006
+ } else if (e.key === "ArrowUp") {
13007
+ e.preventDefault();
13008
+ setActiveIndex((i) => Math.max(i - 1, 0));
13009
+ } else if (e.key === "Enter" || e.key === "Tab") {
13010
+ e.preventDefault();
13011
+ if (filtered[activeIndex]) onSelect(filtered[activeIndex]);
13012
+ } else if (e.key === "Escape") {
13013
+ e.preventDefault();
13014
+ e.stopPropagation();
13015
+ e.stopImmediatePropagation();
13016
+ onClose();
13022
13017
  }
13023
- return false;
13024
- }
13025
- }));
13026
- return /* @__PURE__ */ React26.createElement("div", { className: "z-[3000] bg-slate-900 border border-white/10 rounded-lg shadow-2xl overflow-hidden min-w-[280px] max-w-[400px] animate-in fade-in zoom-in-95 duration-200" }, /* @__PURE__ */ React26.createElement("div", { className: "p-2 border-b border-white/5 bg-slate-950/50 flex items-center gap-2" }, /* @__PURE__ */ React26.createElement(FiSearch8, { className: "text-indigo-400", size: 14 }), /* @__PURE__ */ React26.createElement("span", { className: "text-[10px] uppercase tracking-wider font-bold text-slate-500" }, "Mencionar Recurso")), /* @__PURE__ */ React26.createElement("div", { className: "p-1 max-h-[300px] overflow-y-auto custom-scrollbar" }, props.items.length ? props.items.map((item, index) => {
13027
- const isQuest = item.type === "quest";
13028
- const isVersion = item.type === "version";
13029
- const isNode = item.type === "node";
13030
- let Icon = FiLayers6;
13031
- let colorClass = "text-indigo-400";
13032
- let badgeText = "Ancestralidade";
13033
- if (isQuest) {
13034
- Icon = FiCheckSquare3;
13035
- colorClass = "text-amber-400";
13036
- badgeText = "Quest";
13037
- } else if (isVersion) {
13038
- Icon = FiGitBranch3;
13039
- colorClass = "text-purple-400";
13040
- badgeText = "Vers\xE3o";
13041
- } else if (isNode) {
13042
- Icon = FiBox;
13043
- colorClass = "text-emerald-400";
13044
- badgeText = "Node";
13045
- }
13046
- return /* @__PURE__ */ React26.createElement(
13047
- "button",
13048
- {
13049
- className: `
13050
- w-full flex items-center gap-3 px-3 py-2 text-left rounded-md transition-all
13051
- ${index === selectedIndex ? "bg-indigo-500/20 border border-indigo-500/30 text-white" : "text-slate-400 hover:bg-white/5 border border-transparent"}
13052
- `,
13053
- key: item.id,
13054
- onClick: () => selectItem(index)
13055
- },
13056
- /* @__PURE__ */ React26.createElement("div", { className: `shrink-0 w-8 h-8 rounded border border-white/5 flex items-center justify-center bg-slate-800/50 ${colorClass}` }, /* @__PURE__ */ React26.createElement(Icon, { size: 14 })),
13057
- /* @__PURE__ */ React26.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React26.createElement("div", { className: "flex items-center justify-between gap-2" }, /* @__PURE__ */ React26.createElement("span", { className: "text-sm font-medium truncate" }, item.name), /* @__PURE__ */ React26.createElement("span", { className: `text-[9px] px-1 py-0.5 rounded border border-current opacity-60 ${colorClass}` }, badgeText)))
13058
- );
13059
- }) : /* @__PURE__ */ React26.createElement("div", { className: "p-4 text-center text-slate-500 text-sm italic" }, "Nenhum resultado encontrado...")));
13060
- });
13061
- MentionList.displayName = "MentionList";
13062
- var MentionList_default = MentionList;
13018
+ };
13019
+ window.addEventListener("keydown", handleKeyDown, true);
13020
+ return () => window.removeEventListener("keydown", handleKeyDown, true);
13021
+ }, [filtered, activeIndex, onSelect, onClose]);
13022
+ useEffect21(() => {
13023
+ if (listRef.current) {
13024
+ const el = listRef.current.children[activeIndex];
13025
+ if (el) el.scrollIntoView({ block: "nearest" });
13026
+ }
13027
+ }, [activeIndex]);
13028
+ return /* @__PURE__ */ React26.createElement(
13029
+ "div",
13030
+ {
13031
+ style: { top: position.top, left: position.left, position: "fixed", zIndex: 9999 },
13032
+ className: "bg-slate-900 border border-white/10 rounded-xl shadow-2xl py-1.5 min-w-[260px] max-h-[240px] overflow-y-auto custom-scrollbar animate-in fade-in zoom-in-95 duration-100",
13033
+ ref: listRef,
13034
+ onMouseDown: (e) => e.preventDefault()
13035
+ },
13036
+ filtered.length === 0 ? /* @__PURE__ */ React26.createElement("div", { className: "px-4 py-3 text-xs text-slate-500" }, "Nenhum resultado para", " ", /* @__PURE__ */ React26.createElement("span", { className: "text-slate-300 font-medium" }, '"', query, '"')) : filtered.map((item, idx) => {
13037
+ const config = TYPE_CONFIG[item.type] || TYPE_CONFIG.node;
13038
+ const { Icon } = config;
13039
+ return /* @__PURE__ */ React26.createElement(
13040
+ "div",
13041
+ {
13042
+ key: item.id,
13043
+ onMouseDown: (e) => {
13044
+ e.preventDefault();
13045
+ onSelect(item);
13046
+ },
13047
+ onMouseEnter: () => setActiveIndex(idx),
13048
+ className: `flex items-center gap-2.5 px-3 py-2 cursor-pointer text-sm transition-colors duration-100 ${idx === activeIndex ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5"}`
13049
+ },
13050
+ /* @__PURE__ */ React26.createElement(Icon, { size: 13, className: config.color }),
13051
+ /* @__PURE__ */ React26.createElement("span", { className: "flex-1 truncate font-medium" }, item.searchLabel || "Sem Nome"),
13052
+ /* @__PURE__ */ React26.createElement("span", { className: "text-[9px] px-1.5 py-0.5 rounded-full bg-white/5 text-slate-500 border border-white/5 uppercase tracking-wide shrink-0" }, config.label)
13053
+ );
13054
+ })
13055
+ );
13056
+ }
13063
13057
 
13064
- // src/components/MentionEditor.jsx
13065
- var CustomMention = Mention.extend({
13066
- addAttributes() {
13058
+ // src/components/RichGroupEditor.jsx
13059
+ var MENTION_BASE_STYLE = "display:inline-flex;align-items:center;padding:1px 7px;border-radius:5px;margin:0 2px;font-size:0.78em;font-weight:600;cursor:pointer;white-space:nowrap;vertical-align:middle;user-select:none;line-height:1.6;";
13060
+ var MENTION_TYPE_STYLE = {
13061
+ ancestry: "background:rgba(99,102,241,0.18);color:#a5b4fc;border:1px solid rgba(99,102,241,0.35);",
13062
+ quest: "background:rgba(16,185,129,0.18);color:#6ee7b7;border:1px solid rgba(16,185,129,0.35);",
13063
+ version: "background:rgba(245,158,11,0.18);color:#fcd34d;border:1px solid rgba(245,158,11,0.35);",
13064
+ node: "background:rgba(100,116,139,0.18);color:#94a3b8;border:1px solid rgba(100,116,139,0.35);"
13065
+ };
13066
+ function mentionStyle(type) {
13067
+ return MENTION_BASE_STYLE + (MENTION_TYPE_STYLE[type] || MENTION_TYPE_STYLE.node);
13068
+ }
13069
+ function escapeHtml(str) {
13070
+ return String(str || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13071
+ }
13072
+ function contentToHTML(content) {
13073
+ if (!content || content.length === 0) return "";
13074
+ return content.map((seg) => {
13075
+ if (seg.type === "text") {
13076
+ return escapeHtml(seg.value).replace(/\n/g, "<br>");
13077
+ }
13078
+ if (seg.type === "mention") {
13079
+ const style = mentionStyle(seg.mentionType);
13080
+ return `<span contenteditable="false" data-mention="true" data-id="${escapeHtml(String(seg.id))}" data-name="${escapeHtml(seg.name || "")}" data-mention-type="${escapeHtml(seg.mentionType || "node")}" style="${style}" title="Clique para focar na cena">@${escapeHtml(seg.name || "")}</span>`;
13081
+ }
13082
+ return "";
13083
+ }).join("");
13084
+ }
13085
+ function domToContent(el) {
13086
+ const segments = [];
13087
+ function pushText(text) {
13088
+ const clean = text.replace(/\u200B/g, "");
13089
+ if (!clean) return;
13090
+ const last = segments[segments.length - 1];
13091
+ if (last && last.type === "text") {
13092
+ last.value += clean;
13093
+ } else {
13094
+ segments.push({ type: "text", value: clean });
13095
+ }
13096
+ }
13097
+ function walk(node) {
13067
13098
  var _a;
13068
- return {
13069
- ...(_a = this.parent) == null ? void 0 : _a.call(this),
13070
- type: {
13071
- default: null,
13072
- parseHTML: (element) => element.getAttribute("data-mention-type"),
13073
- renderHTML: (attributes) => {
13074
- if (!attributes.type) {
13075
- return {};
13076
- }
13077
- return {
13078
- "data-mention-type": attributes.type
13079
- };
13080
- }
13081
- },
13082
- label: {
13083
- default: null,
13084
- parseHTML: (element) => element.getAttribute("data-label"),
13085
- renderHTML: (attributes) => {
13086
- if (!attributes.label) {
13087
- return {};
13088
- }
13089
- return {
13090
- "data-label": attributes.label
13091
- };
13099
+ if (node.nodeType === Node.TEXT_NODE) {
13100
+ pushText(node.textContent);
13101
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
13102
+ if (((_a = node.dataset) == null ? void 0 : _a.mention) === "true") {
13103
+ segments.push({
13104
+ type: "mention",
13105
+ id: node.dataset.id,
13106
+ name: node.dataset.name,
13107
+ mentionType: node.dataset.mentionType
13108
+ });
13109
+ } else if (node.tagName === "BR") {
13110
+ pushText("\n");
13111
+ } else {
13112
+ if (["DIV", "P"].includes(node.tagName) && segments.length > 0) {
13113
+ pushText("\n");
13092
13114
  }
13115
+ node.childNodes.forEach(walk);
13093
13116
  }
13094
- };
13117
+ }
13095
13118
  }
13096
- });
13097
- var MentionEditor = ({
13098
- initialValue = "",
13119
+ el.childNodes.forEach(walk);
13120
+ return segments;
13121
+ }
13122
+ function RichGroupEditor({
13123
+ content,
13099
13124
  onChange,
13100
- onPlay,
13101
- availableItems = [],
13102
- canEdit = true,
13103
- placeholder = "Escreva algo... use @ para mencionar"
13104
- }) => {
13105
- const parseInitialContent = (text) => {
13106
- if (!text) return "<p></p>";
13107
- const parts = text.split(/(\[\[MENTION:[^\]]+\]\])/g);
13108
- return parts.map((part) => {
13109
- const match = part.match(/\[\[MENTION:([^:]+):([^:]+):([^\]]+)\]\]/);
13110
- if (match) {
13111
- const [, type, id, label] = match;
13112
- return `<span data-type="mention" data-id="${id}" data-mention-type="${type}" data-label="${label}">@${label}</span>`;
13113
- }
13114
- return part;
13115
- }).join("");
13116
- };
13117
- const content = useMemo11(() => parseInitialContent(initialValue), [initialValue]);
13118
- const editor = useEditor({
13119
- extensions: [
13120
- StarterKit.configure({
13121
- heading: false,
13122
- bulletList: false,
13123
- orderedList: false,
13124
- codeBlock: false,
13125
- blockquote: false
13126
- }),
13127
- CustomMention.configure({
13128
- HTMLAttributes: {
13129
- class: "mention"
13130
- },
13131
- suggestion: {
13132
- items: ({ query }) => {
13133
- return availableItems.filter(
13134
- (item) => item.name.toLowerCase().includes(query.toLowerCase())
13135
- ).slice(0, 10);
13136
- },
13137
- render: () => {
13138
- let component;
13139
- let popup;
13140
- return {
13141
- onStart: (props) => {
13142
- component = new ReactRenderer(MentionList_default, {
13143
- props,
13144
- editor: props.editor
13145
- });
13146
- if (!props.clientRect) {
13147
- return;
13148
- }
13149
- popup = tippy("body", {
13150
- getReferenceClientRect: props.clientRect,
13151
- appendTo: () => document.body,
13152
- content: component.element,
13153
- showOnCreate: true,
13154
- interactive: true,
13155
- trigger: "manual",
13156
- placement: "bottom-start",
13157
- theme: "x-view-dark"
13158
- });
13159
- },
13160
- onUpdate(props) {
13161
- component.updateProps(props);
13162
- if (!props.clientRect) {
13163
- return;
13164
- }
13165
- popup[0].setProps({
13166
- getReferenceClientRect: props.clientRect
13167
- });
13168
- },
13169
- onKeyDown(props) {
13170
- var _a;
13171
- if (props.event.key === "Escape") {
13172
- popup[0].hide();
13173
- return true;
13174
- }
13175
- return (_a = component == null ? void 0 : component.ref) == null ? void 0 : _a.onKeyDown(props);
13176
- },
13177
- onExit() {
13178
- if (popup && popup[0]) popup[0].destroy();
13179
- if (component) component.destroy();
13180
- }
13181
- };
13182
- }
13183
- }
13184
- })
13185
- ],
13186
- content,
13187
- editable: canEdit,
13188
- onUpdate: ({ editor: editor2 }) => {
13189
- const serialize = (json) => {
13190
- if (!json.content) return "";
13191
- return json.content.map((block) => {
13192
- if (block.type === "paragraph") {
13193
- const inner = block.content ? block.content.map((node) => {
13194
- if (node.type === "text") return node.text;
13195
- if (node.type === "mention") {
13196
- return `[[MENTION:${node.attrs.type}:${node.attrs.id}:${node.attrs.label}]]`;
13197
- }
13198
- return "";
13199
- }).join("") : "";
13200
- return inner;
13201
- }
13202
- return "";
13203
- }).join("\n").trim();
13204
- };
13205
- const serialized = serialize(editor2.getJSON());
13206
- onChange(serialized);
13125
+ onMentionClick,
13126
+ searchItems,
13127
+ readOnly,
13128
+ placeholder
13129
+ }) {
13130
+ const editorRef = useRef19(null);
13131
+ const isMountedRef = useRef19(false);
13132
+ const dismissedTriggerRef = useRef19(null);
13133
+ const [mentionState, setMentionState] = useState26(null);
13134
+ const [isEmpty, setIsEmpty] = useState26(true);
13135
+ useEffect22(() => {
13136
+ if (!isMountedRef.current && editorRef.current) {
13137
+ editorRef.current.innerHTML = contentToHTML(content);
13138
+ const hasContent = !!editorRef.current.innerText.replace(/\u200B/g, "").trim();
13139
+ const hasMentions = !!editorRef.current.querySelector("[data-mention]");
13140
+ setIsEmpty(!hasContent && !hasMentions);
13141
+ isMountedRef.current = true;
13207
13142
  }
13208
- });
13209
- if (!editor) {
13210
- return null;
13211
- }
13212
- return /* @__PURE__ */ React27.createElement("div", { className: "relative group/editor min-h-[1.5rem] w-full" }, /* @__PURE__ */ React27.createElement(
13213
- EditorContent,
13214
- {
13215
- editor,
13216
- className: "text-sm text-slate-200 placeholder-slate-600 focus:outline-none"
13217
- }
13218
- ), /* @__PURE__ */ React27.createElement("style", null, `
13219
- .tiptap p {
13220
- margin: 0;
13221
- min-height: 1.25rem;
13222
- line-height: 1.5;
13223
- }
13224
- .tiptap .mention {
13225
- display: inline-flex;
13226
- align-items: center;
13227
- background: rgba(99, 102, 241, 0.12);
13228
- border: 1px solid rgba(99, 102, 241, 0.25);
13229
- border-radius: 6px;
13230
- padding: 1px 6px;
13231
- color: #a5b4fc;
13232
- font-weight: 600;
13233
- cursor: pointer;
13234
- margin: 0 1px;
13235
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
13236
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
13237
- font-size: 0.85rem;
13238
- text-decoration: none;
13239
- vertical-align: baseline;
13240
- }
13241
- .tiptap .mention:hover {
13242
- background: rgba(99, 102, 241, 0.22);
13243
- border-color: rgba(99, 102, 241, 0.5);
13244
- color: #fff;
13245
- transform: translateY(-1px);
13246
- box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
13247
- }
13248
- .tiptap .mention::before {
13249
- content: "";
13250
- display: inline-block;
13251
- width: 12px;
13252
- height: 12px;
13253
- margin-right: 4px;
13254
- background-size: contain;
13255
- background-repeat: no-repeat;
13256
- opacity: 0.8;
13257
- filter: brightness(1.2);
13258
- }
13259
-
13260
- /* Type specific styles */
13261
- .tiptap .mention[data-mention-type="ancestry"] {
13262
- background: rgba(139, 92, 246, 0.15);
13263
- border-color: rgba(139, 92, 246, 0.3);
13264
- color: #c4b5fd;
13265
- }
13266
- .tiptap .mention[data-mention-type="quest"] {
13267
- background: rgba(16, 185, 129, 0.15);
13268
- border-color: rgba(16, 185, 129, 0.3);
13269
- color: #6ee7b7;
13270
- }
13271
- .tiptap .mention[data-mention-type="node"] {
13272
- background: rgba(59, 130, 246, 0.15);
13273
- border-color: rgba(59, 130, 246, 0.3);
13274
- color: #93c5fd;
13143
+ }, []);
13144
+ const getMentionContext = useCallback4(() => {
13145
+ const selection = window.getSelection();
13146
+ if (!selection || selection.rangeCount === 0) return null;
13147
+ const range = selection.getRangeAt(0);
13148
+ const node = range.startContainer;
13149
+ if (node.nodeType !== Node.TEXT_NODE) return null;
13150
+ const textBefore = node.textContent.substring(0, range.startOffset);
13151
+ const atIndex = textBefore.lastIndexOf("@");
13152
+ if (atIndex === -1) {
13153
+ dismissedTriggerRef.current = null;
13154
+ return null;
13155
+ }
13156
+ const charBeforeAt = atIndex > 0 ? textBefore[atIndex - 1] : null;
13157
+ if (charBeforeAt && !/\s/.test(charBeforeAt)) {
13158
+ return null;
13159
+ }
13160
+ if (dismissedTriggerRef.current && dismissedTriggerRef.current.node === node && dismissedTriggerRef.current.atIndex === atIndex) {
13161
+ return null;
13162
+ }
13163
+ const query = textBefore.substring(atIndex + 1);
13164
+ if (query.includes(" ") || query.includes(".") || query.includes(",") || query.length > 40) {
13165
+ return null;
13166
+ }
13167
+ const rect = range.getBoundingClientRect();
13168
+ return {
13169
+ query,
13170
+ position: { top: rect.bottom + 6, left: rect.left }
13171
+ };
13172
+ }, []);
13173
+ const handleInput = useCallback4(() => {
13174
+ if (!editorRef.current) return;
13175
+ const serialized = domToContent(editorRef.current);
13176
+ onChange(serialized);
13177
+ const text = editorRef.current.innerText.replace(/\u200B/g, "").trim();
13178
+ const hasMentions = !!editorRef.current.querySelector("[data-mention]");
13179
+ setIsEmpty(!text && !hasMentions);
13180
+ const ctx = getMentionContext();
13181
+ setMentionState(ctx);
13182
+ }, [onChange, getMentionContext]);
13183
+ const handleKeyDown = useCallback4(
13184
+ (e) => {
13185
+ if (mentionState && ["ArrowDown", "ArrowUp", "Enter", "Tab", "Escape"].includes(e.key)) {
13186
+ return;
13187
+ }
13188
+ if (e.key === "Enter") {
13189
+ e.preventDefault();
13190
+ const sel = window.getSelection();
13191
+ const range = sel.getRangeAt(0);
13192
+ const br = document.createElement("br");
13193
+ range.deleteContents();
13194
+ range.insertNode(br);
13195
+ const afterBr = br.nextSibling || (() => {
13196
+ const zws = document.createTextNode("\u200B");
13197
+ br.parentNode.insertBefore(zws, br.nextSibling);
13198
+ return zws;
13199
+ })();
13200
+ const newRange = document.createRange();
13201
+ newRange.setStartBefore(afterBr);
13202
+ newRange.collapse(true);
13203
+ sel.removeAllRanges();
13204
+ sel.addRange(newRange);
13205
+ handleInput();
13206
+ }
13207
+ },
13208
+ [mentionState, handleInput]
13209
+ );
13210
+ const insertMention = useCallback4(
13211
+ (item) => {
13212
+ if (!editorRef.current) return;
13213
+ const sel = window.getSelection();
13214
+ if (!sel || sel.rangeCount === 0) return;
13215
+ const range = sel.getRangeAt(0);
13216
+ const textNode = range.startContainer;
13217
+ if (textNode.nodeType !== Node.TEXT_NODE) {
13218
+ setMentionState(null);
13219
+ return;
13220
+ }
13221
+ const cursorPos = range.startOffset;
13222
+ const textContent = textNode.textContent;
13223
+ const atIndex = textContent.lastIndexOf("@", cursorPos - 1);
13224
+ if (atIndex === -1) {
13225
+ setMentionState(null);
13226
+ return;
13227
+ }
13228
+ const before = textContent.substring(0, atIndex);
13229
+ const after = textContent.substring(cursorPos);
13230
+ textNode.textContent = before;
13231
+ const parent = textNode.parentNode;
13232
+ const nextSib = textNode.nextSibling;
13233
+ const span = document.createElement("span");
13234
+ span.contentEditable = "false";
13235
+ span.dataset.mention = "true";
13236
+ span.dataset.id = item.id;
13237
+ span.dataset.name = item.searchLabel || item.name || "";
13238
+ span.dataset.mentionType = item.type;
13239
+ span.style.cssText = mentionStyle(item.type);
13240
+ span.title = "Clique para focar na cena";
13241
+ span.textContent = `@${item.searchLabel || item.name || ""}`;
13242
+ parent.insertBefore(span, nextSib);
13243
+ const trailing = document.createTextNode(after || "\u200B");
13244
+ parent.insertBefore(trailing, span.nextSibling);
13245
+ const newRange = document.createRange();
13246
+ newRange.setStart(trailing, after ? 0 : 1);
13247
+ newRange.collapse(true);
13248
+ sel.removeAllRanges();
13249
+ sel.addRange(newRange);
13250
+ const serialized = domToContent(editorRef.current);
13251
+ onChange(serialized);
13252
+ setMentionState(null);
13253
+ setIsEmpty(false);
13254
+ },
13255
+ [onChange]
13256
+ );
13257
+ const handlePaste = useCallback4(
13258
+ (e) => {
13259
+ e.preventDefault();
13260
+ const text = e.clipboardData.getData("text/plain");
13261
+ if (text) {
13262
+ document.execCommand("insertText", false, text);
13263
+ handleInput();
13264
+ }
13265
+ },
13266
+ [handleInput]
13267
+ );
13268
+ const handleClick = useCallback4(
13269
+ (e) => {
13270
+ const chip = e.target.closest("[data-mention='true']");
13271
+ if (chip && onMentionClick) {
13272
+ e.stopPropagation();
13273
+ const id = chip.dataset.id;
13274
+ const full = searchItems == null ? void 0 : searchItems.find((i) => String(i.id) === String(id));
13275
+ if (full) {
13276
+ onMentionClick(full);
13277
+ } else {
13278
+ onMentionClick({
13279
+ id,
13280
+ name: chip.dataset.name,
13281
+ type: chip.dataset.mentionType,
13282
+ searchLabel: chip.dataset.name
13283
+ });
13275
13284
  }
13276
- .tiptap .mention[data-mention-type="version"] {
13277
- background: rgba(245, 158, 11, 0.15);
13278
- border-color: rgba(245, 158, 11, 0.3);
13279
- color: #fcd34d;
13285
+ return;
13286
+ }
13287
+ },
13288
+ [onMentionClick, getMentionContext, searchItems]
13289
+ );
13290
+ const handleBlur = useCallback4(() => {
13291
+ setTimeout(() => setMentionState(null), 200);
13292
+ }, []);
13293
+ const handleMentionDismiss = useCallback4(() => {
13294
+ const selection = window.getSelection();
13295
+ if (selection && selection.rangeCount > 0) {
13296
+ const range = selection.getRangeAt(0);
13297
+ const node = range.startContainer;
13298
+ if (node.nodeType === Node.TEXT_NODE) {
13299
+ const textBefore = node.textContent.substring(0, range.startOffset);
13300
+ const atIndex = textBefore.lastIndexOf("@");
13301
+ if (atIndex !== -1) {
13302
+ dismissedTriggerRef.current = { node, atIndex };
13280
13303
  }
13304
+ }
13305
+ }
13306
+ setMentionState(null);
13307
+ }, []);
13308
+ return /* @__PURE__ */ React27.createElement("div", { className: "relative" }, isEmpty && !readOnly && /* @__PURE__ */ React27.createElement("div", { className: "absolute top-0 left-0 text-sm text-slate-600 pointer-events-none select-none" }, placeholder || "Escreva sobre este grupo\u2026 ou digite @ para mencionar"), /* @__PURE__ */ React27.createElement(
13309
+ "div",
13310
+ {
13311
+ ref: editorRef,
13312
+ contentEditable: readOnly ? "false" : "true",
13313
+ suppressContentEditableWarning: true,
13314
+ onInput: handleInput,
13315
+ onKeyDown: handleKeyDown,
13316
+ onPaste: handlePaste,
13317
+ onClick: handleClick,
13318
+ onBlur: handleBlur,
13319
+ className: `w-full min-h-[1.5rem] max-h-[250px] overflow-y-auto text-sm text-slate-200 focus:outline-none leading-relaxed ${!readOnly ? "cursor-text" : "cursor-default"}`,
13320
+ style: { overflowWrap: "break-word", wordBreak: "break-word" }
13321
+ }
13322
+ ), mentionState && !readOnly && /* @__PURE__ */ React27.createElement(
13323
+ MentionPopover,
13324
+ {
13325
+ query: mentionState.query,
13326
+ items: searchItems || [],
13327
+ position: mentionState.position,
13328
+ onSelect: insertMention,
13329
+ onClose: handleMentionDismiss
13330
+ }
13331
+ ));
13332
+ }
13281
13333
 
13282
- .tiptap:focus {
13283
- outline: none;
13284
- }
13285
-
13286
- .ProseMirror-placeholder:first-child::before {
13287
- content: attr(data-placeholder);
13288
- float: left;
13289
- color: #475569;
13290
- pointer-events: none;
13291
- height: 0;
13292
- font-style: italic;
13293
- }
13294
- `));
13334
+ // src/components/Boards.jsx
13335
+ var Tooltip = ({ text, children }) => {
13336
+ if (!text) return children;
13337
+ return /* @__PURE__ */ React28.createElement("div", { className: "relative group/tooltip" }, children, /* @__PURE__ */ React28.createElement("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-slate-900 border border-white/10 text-white text-[10px] rounded opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-[3000] shadow-2xl" }, text));
13295
13338
  };
13296
- var MentionEditor_default = MentionEditor;
13297
-
13298
- // src/components/AncestryBoard.jsx
13299
- var GroupItem = ({
13300
- group,
13339
+ var BoardItem = ({
13340
+ item,
13301
13341
  index,
13342
+ prevItem,
13343
+ nextItem,
13302
13344
  onUpdate,
13303
13345
  onDelete,
13304
13346
  onAddSubgroup,
13305
13347
  onIndent,
13348
+ onIndentDown,
13306
13349
  onOutdent,
13307
- onPlayAncestry,
13308
- availableMentionItems,
13309
- canEdit
13350
+ onMoveUp,
13351
+ onMoveDown,
13352
+ searchItems,
13353
+ onMentionClick,
13354
+ onEnterFolder,
13355
+ canEdit,
13356
+ isAtRoot = false
13310
13357
  }) => {
13311
- const canIndent = index > 0;
13312
- return /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col gap-2 mb-3 pl-3 border-l border-white/10 relative group/item animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React28.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ React28.createElement(
13313
- "div",
13314
- {
13315
- className: "flex flex-col gap-2 py-2 px-3 transition-all duration-200 hover:bg-white/5 border-l-2 border-transparent hover:border-white/20"
13316
- },
13317
- /* @__PURE__ */ React28.createElement(
13318
- MentionEditor_default,
13319
- {
13320
- initialValue: group.text,
13321
- onChange: (newText) => {
13322
- if (canEdit) onUpdate(group.id, { ...group, text: newText });
13323
- },
13324
- onPlay: onPlayAncestry,
13325
- availableItems: availableMentionItems,
13326
- canEdit,
13327
- placeholder: "Escreva sobre este grupo... use @ para mencionar"
13328
- }
13329
- ),
13330
- canEdit && /* @__PURE__ */ React28.createElement("div", { className: "flex items-center justify-between pt-2 mt-1 border-t border-white/5 opacity-0 group-hover/item:opacity-100 transition-opacity" }, /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React28.createElement(
13331
- "button",
13332
- {
13333
- onClick: () => onAddSubgroup(group.id),
13334
- className: "flex items-center gap-1.5 px-3 py-1.5 text-xs text-slate-500 hover:text-white hover:bg-white/10 rounded-md transition-colors",
13335
- title: "Criar Subgrupo"
13336
- },
13337
- /* @__PURE__ */ React28.createElement(FiPlus10, { size: 14 }),
13338
- /* @__PURE__ */ React28.createElement("span", null, "Adicionar Subgrupo")
13339
- )), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React28.createElement(
13340
- "button",
13341
- {
13342
- onClick: () => onIndent(group.id),
13343
- disabled: !canIndent,
13344
- className: `p-1.5 rounded transition-colors ${!canIndent ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`,
13345
- title: "Aninhar no grupo acima"
13346
- },
13347
- /* @__PURE__ */ React28.createElement(FiArrowRight2, { size: 14 })
13348
- ), /* @__PURE__ */ React28.createElement(
13349
- "button",
13350
- {
13351
- onClick: () => onOutdent(group.id),
13352
- className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
13353
- title: "Desaninhar"
13354
- },
13355
- /* @__PURE__ */ React28.createElement(FiArrowLeft3, { size: 14 })
13356
- ), /* @__PURE__ */ React28.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ React28.createElement(
13357
- "button",
13358
+ var _a;
13359
+ const isFolder = item.type === "folder";
13360
+ const canMoveUp = index > 0;
13361
+ const canMoveDown = nextItem !== void 0;
13362
+ const isNested = onOutdent !== void 0;
13363
+ const getIndentUpTooltip = () => {
13364
+ if (!prevItem) return null;
13365
+ const typeLabel = prevItem.type === "folder" ? "pasta" : "grupo de texto";
13366
+ return isFolder ? prevItem.type === "folder" ? "Aninhar na pasta acima" : null : `Mover para dentro do ${typeLabel} acima`;
13367
+ };
13368
+ const getIndentDownTooltip = () => {
13369
+ if (!nextItem) return null;
13370
+ const typeLabel = nextItem.type === "folder" ? "pasta" : "grupo de texto";
13371
+ return isFolder ? nextItem.type === "folder" ? "Aninhar na pasta abaixo" : null : `Mover para dentro do ${typeLabel} abaixo`;
13372
+ };
13373
+ const showIndentUp = !!getIndentUpTooltip();
13374
+ const showIndentDown = !!getIndentDownTooltip();
13375
+ const handleContentChange = (content) => {
13376
+ onUpdate(item.id, { ...item, content });
13377
+ };
13378
+ const handleNameChange = (e) => {
13379
+ onUpdate(item.id, { ...item, name: e.target.value });
13380
+ };
13381
+ if (isFolder) {
13382
+ return /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col gap-2 mb-3 relative group/item animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React28.createElement(
13383
+ "div",
13358
13384
  {
13359
- onClick: () => onDelete(group.id),
13360
- className: "p-1.5 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors",
13361
- title: "Remover Grupo"
13385
+ onClick: () => onEnterFolder(item.id),
13386
+ className: "flex items-center gap-3 py-3 px-4 bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/20 rounded-xl cursor-pointer transition-all duration-200 group/folder"
13362
13387
  },
13363
- /* @__PURE__ */ React28.createElement(FiTrash23, { size: 14 })
13364
- )))
13365
- ), group.children && group.children.length > 0 && /* @__PURE__ */ React28.createElement("div", { className: "ml-2" }, group.children.map((childGroup, idx) => /* @__PURE__ */ React28.createElement(
13366
- GroupItem,
13367
- {
13368
- key: childGroup.id,
13369
- index: idx,
13370
- group: childGroup,
13371
- siblings: group.children,
13372
- onUpdate,
13373
- onDelete,
13374
- onAddSubgroup,
13375
- onIndent,
13376
- onOutdent,
13377
- onPlayAncestry,
13378
- availableMentionItems,
13379
- canEdit
13380
- }
13381
- ))));
13382
- };
13383
- function AncestryBoard({
13384
- isOpen,
13385
- onClose,
13386
- onSelect,
13387
- availableAncestries = [],
13388
- availableNodes = [],
13389
- initialGroups = [],
13390
- onSave,
13391
- userRole
13392
- }) {
13393
- const [groups, setGroups] = useState26([]);
13394
- const [isLoaded, setIsLoaded] = useState26(false);
13395
- const [saveStatus, setSaveStatus] = useState26("idle");
13396
- const canEdit = useMemo12(() => {
13397
- return userRole !== "viewer";
13398
- }, [userRole]);
13399
- useEffect23(() => {
13400
- if (initialGroups && !isLoaded) {
13401
- setGroups(initialGroups);
13402
- setIsLoaded(true);
13403
- }
13404
- }, [initialGroups, isLoaded]);
13405
- const nodeNamesMap = useMemo12(() => {
13406
- const map = /* @__PURE__ */ new Map();
13407
- if (availableNodes && Array.isArray(availableNodes)) {
13408
- availableNodes.forEach((node) => {
13409
- if (node && node.id) {
13410
- map.set(String(node.id), node.name);
13388
+ /* @__PURE__ */ React28.createElement("div", { className: "w-10 h-10 rounded-lg bg-indigo-500/10 flex items-center justify-center text-indigo-400 group-hover/folder:scale-110 transition-transform" }, /* @__PURE__ */ React28.createElement(FiFolder3, { size: 20 })),
13389
+ /* @__PURE__ */ React28.createElement("div", { className: "flex-1 min-w-0" }, canEdit ? /* @__PURE__ */ React28.createElement(
13390
+ "input",
13391
+ {
13392
+ type: "text",
13393
+ value: item.name,
13394
+ onChange: handleNameChange,
13395
+ onClick: (e) => e.stopPropagation(),
13396
+ className: "bg-transparent border-none outline-none text-sm font-medium text-slate-200 w-full focus:text-white",
13397
+ placeholder: "Nome da pasta..."
13411
13398
  }
13412
- });
13413
- }
13414
- return map;
13415
- }, [availableNodes]);
13416
- const availableIds = useMemo12(() => {
13417
- return new Set(availableAncestries.map((a) => String(a.ancestry_id)));
13418
- }, [availableAncestries]);
13419
- const availableMentionItems = useMemo12(() => {
13420
- const ancestries = availableAncestries.map((a) => ({
13421
- id: String(a.ancestry_id),
13422
- ancestry_id: a.ancestry_id,
13423
- name: a.name,
13424
- description: a.description,
13425
- type: "ancestry",
13426
- ancestral_node: a.ancestral_node,
13427
- is_private: a.is_private
13428
- }));
13429
- const nodes = availableNodes.map((n) => {
13430
- let type = "node";
13431
- if (n.is_quest) type = "quest";
13432
- else if (n.is_version) type = "version";
13433
- return {
13434
- id: String(n.id),
13435
- name: n.name,
13436
- description: n.description,
13437
- type,
13438
- raw_node: n
13439
- };
13440
- });
13441
- return [...ancestries, ...nodes];
13442
- }, [availableAncestries, availableNodes]);
13443
- const sanitizeGroups = (groupList) => {
13444
- return groupList.map((g) => ({
13445
- id: g.id,
13446
- text: g.text || "",
13447
- children: sanitizeGroups(g.children || [])
13448
- }));
13449
- };
13450
- useEffect23(() => {
13451
- if (!isLoaded || !onSave) return;
13452
- const timeoutId = setTimeout(async () => {
13453
- setSaveStatus("saving");
13454
- const cleanData = sanitizeGroups(groups);
13455
- try {
13456
- await onSave(cleanData);
13457
- setSaveStatus("saved");
13458
- setTimeout(() => {
13459
- setSaveStatus("idle");
13460
- }, 3e3);
13461
- } catch (error) {
13462
- console.error("Erro ao salvar board:", error);
13463
- setSaveStatus("error");
13464
- }
13465
- }, 3e3);
13466
- return () => clearTimeout(timeoutId);
13467
- }, [groups, isLoaded, onSave]);
13468
- useEffect23(() => {
13469
- if (!isOpen) return;
13470
- const handleKeyDown = (e) => {
13471
- if (e.key === "Escape") {
13472
- onClose();
13473
- e.preventDefault();
13474
- e.stopPropagation();
13399
+ ) : /* @__PURE__ */ React28.createElement("span", { className: "text-sm font-medium text-slate-200" }, item.name), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-2 mt-0.5" }, /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] text-slate-500 uppercase tracking-wider font-semibold" }, ((_a = item.children) == null ? void 0 : _a.length) || 0, " itens"))),
13400
+ /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1.5 opacity-0 group-item/item:opacity-100 group-hover/folder:opacity-100 transition-opacity" }, canEdit && /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-0.5 bg-slate-900/80 backdrop-blur-sm p-1 rounded-lg border border-white/5 shadow-xl" }, /* @__PURE__ */ React28.createElement(Tooltip, { text: "Subir" }, /* @__PURE__ */ React28.createElement(
13401
+ "button",
13402
+ {
13403
+ onClick: (e) => {
13404
+ e.stopPropagation();
13405
+ onMoveUp(item.id);
13406
+ },
13407
+ disabled: !canMoveUp,
13408
+ className: `p-1.5 rounded transition-colors ${!canMoveUp ? "text-slate-800 cursor-not-allowed" : "text-slate-400 hover:text-white hover:bg-white/10"}`
13409
+ },
13410
+ /* @__PURE__ */ React28.createElement(FiArrowUp, { size: 14 })
13411
+ )), /* @__PURE__ */ React28.createElement(Tooltip, { text: "Descer" }, /* @__PURE__ */ React28.createElement(
13412
+ "button",
13413
+ {
13414
+ onClick: (e) => {
13415
+ e.stopPropagation();
13416
+ onMoveDown(item.id);
13417
+ },
13418
+ disabled: !canMoveDown,
13419
+ className: `p-1.5 rounded transition-colors ${!canMoveDown ? "text-slate-800 cursor-not-allowed" : "text-slate-400 hover:text-white hover:bg-white/10"}`
13420
+ },
13421
+ /* @__PURE__ */ React28.createElement(FiArrowDown, { size: 14 })
13422
+ )), /* @__PURE__ */ React28.createElement("div", { className: "w-px h-3 bg-white/10 mx-0.5" }), showIndentUp && /* @__PURE__ */ React28.createElement(Tooltip, { text: getIndentUpTooltip() }, /* @__PURE__ */ React28.createElement(
13423
+ "button",
13424
+ {
13425
+ onClick: (e) => {
13426
+ e.stopPropagation();
13427
+ onIndent(item.id);
13428
+ },
13429
+ className: "p-1.5 text-slate-400 hover:text-white hover:bg-white/10 rounded transition-colors"
13430
+ },
13431
+ /* @__PURE__ */ React28.createElement(FiArrowLeft3, { size: 14 })
13432
+ )), showIndentDown && /* @__PURE__ */ React28.createElement(Tooltip, { text: getIndentDownTooltip() }, /* @__PURE__ */ React28.createElement(
13433
+ "button",
13434
+ {
13435
+ onClick: (e) => {
13436
+ e.stopPropagation();
13437
+ onIndentDown(item.id);
13438
+ },
13439
+ className: "p-1.5 text-slate-400 hover:text-white hover:bg-white/10 rounded transition-colors"
13440
+ },
13441
+ /* @__PURE__ */ React28.createElement(FiArrowRight2, { size: 14 })
13442
+ )), !isAtRoot && /* @__PURE__ */ React28.createElement(Tooltip, { text: "Desaninhar / Recuar n\xEDvel" }, /* @__PURE__ */ React28.createElement(
13443
+ "button",
13444
+ {
13445
+ onClick: (e) => {
13446
+ e.stopPropagation();
13447
+ onOutdent(item.id);
13448
+ },
13449
+ disabled: !onOutdent,
13450
+ className: `p-1.5 rounded transition-colors ${!onOutdent ? "text-slate-800 cursor-not-allowed" : "text-slate-400 hover:text-white hover:bg-white/10"}`
13451
+ },
13452
+ /* @__PURE__ */ React28.createElement(FiCornerUpLeft3, { size: 14 })
13453
+ )), /* @__PURE__ */ React28.createElement("div", { className: "w-px h-3 bg-white/10 mx-0.5" }), /* @__PURE__ */ React28.createElement(Tooltip, { text: "Remover Pasta" }, /* @__PURE__ */ React28.createElement(
13454
+ "button",
13455
+ {
13456
+ onClick: (e) => {
13457
+ e.stopPropagation();
13458
+ onDelete(item.id);
13459
+ },
13460
+ className: "p-1.5 text-slate-500 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
13461
+ },
13462
+ /* @__PURE__ */ React28.createElement(FiTrash23, { size: 14 })
13463
+ ))), /* @__PURE__ */ React28.createElement(FiChevronRight7, { className: "text-slate-500 group-hover/folder:translate-x-1 transition-transform" }))
13464
+ ));
13465
+ }
13466
+ return /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col gap-2 mb-3 pl-3 border-l border-white/10 relative group/item animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React28.createElement("div", { className: "absolute -left-[1px] top-4 w-2 h-px bg-white/20" }), /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col gap-2 py-2 px-3 transition-all duration-200 hover:bg-white/5 border-l-2 border-transparent hover:border-white/20" }, /* @__PURE__ */ React28.createElement(
13467
+ RichGroupEditor,
13468
+ {
13469
+ content: item.content,
13470
+ onChange: handleContentChange,
13471
+ onMentionClick,
13472
+ searchItems,
13473
+ readOnly: !canEdit,
13474
+ placeholder: "Escreva sobre este grupo de texto\u2026 ou @ para mencionar"
13475
+ }
13476
+ ), canEdit && /* @__PURE__ */ React28.createElement("div", { className: "flex items-center justify-between pt-2 mt-1 border-t border-white/5 opacity-40 group-hover/item:opacity-100 transition-opacity" }, /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React28.createElement(
13477
+ "button",
13478
+ {
13479
+ onClick: () => onAddSubgroup(item.id, "text_group"),
13480
+ className: "flex items-center gap-1 px-1.5 py-1 text-[10px] uppercase font-bold tracking-wider text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors",
13481
+ title: "Criar Subgrupo de Texto"
13482
+ },
13483
+ /* @__PURE__ */ React28.createElement(FiPlus10, { size: 12 }),
13484
+ /* @__PURE__ */ React28.createElement("span", null, "Subgrupo")
13485
+ )), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React28.createElement(Tooltip, { text: "Subir" }, /* @__PURE__ */ React28.createElement(
13486
+ "button",
13487
+ {
13488
+ onClick: () => onMoveUp(item.id),
13489
+ disabled: !canMoveUp,
13490
+ className: `p-1.5 rounded transition-colors ${!canMoveUp ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`
13491
+ },
13492
+ /* @__PURE__ */ React28.createElement(FiArrowUp, { size: 14 })
13493
+ )), /* @__PURE__ */ React28.createElement(Tooltip, { text: "Descer" }, /* @__PURE__ */ React28.createElement(
13494
+ "button",
13495
+ {
13496
+ onClick: () => onMoveDown(item.id),
13497
+ disabled: !canMoveDown,
13498
+ className: `p-1.5 rounded transition-colors ${!canMoveDown ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`
13499
+ },
13500
+ /* @__PURE__ */ React28.createElement(FiArrowDown, { size: 14 })
13501
+ )), /* @__PURE__ */ React28.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), showIndentUp && /* @__PURE__ */ React28.createElement(Tooltip, { text: getIndentUpTooltip() }, /* @__PURE__ */ React28.createElement(
13502
+ "button",
13503
+ {
13504
+ onClick: () => onIndent(item.id),
13505
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors"
13506
+ },
13507
+ /* @__PURE__ */ React28.createElement(FiArrowLeft3, { size: 14 })
13508
+ )), showIndentDown && /* @__PURE__ */ React28.createElement(Tooltip, { text: getIndentDownTooltip() }, /* @__PURE__ */ React28.createElement(
13509
+ "button",
13510
+ {
13511
+ onClick: () => onIndentDown(item.id),
13512
+ className: "p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-colors"
13513
+ },
13514
+ /* @__PURE__ */ React28.createElement(FiArrowRight2, { size: 14 })
13515
+ )), !isAtRoot && /* @__PURE__ */ React28.createElement(Tooltip, { text: "Desaninhar / Recuar n\xEDvel" }, /* @__PURE__ */ React28.createElement(
13516
+ "button",
13517
+ {
13518
+ onClick: () => onOutdent(item.id),
13519
+ disabled: !onOutdent,
13520
+ className: `p-1.5 rounded transition-colors ${!onOutdent ? "text-slate-800 cursor-not-allowed" : "text-slate-500 hover:text-white hover:bg-white/10"}`
13521
+ },
13522
+ /* @__PURE__ */ React28.createElement(FiCornerUpLeft3, { size: 14 })
13523
+ )), /* @__PURE__ */ React28.createElement("div", { className: "w-px h-3 bg-white/10 mx-1" }), /* @__PURE__ */ React28.createElement(Tooltip, { text: "Remover Grupo de Texto" }, /* @__PURE__ */ React28.createElement(
13524
+ "button",
13525
+ {
13526
+ onClick: () => onDelete(item.id),
13527
+ className: "p-1.5 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
13528
+ },
13529
+ /* @__PURE__ */ React28.createElement(FiTrash23, { size: 14 })
13530
+ ))))), item.children && item.children.length > 0 && /* @__PURE__ */ React28.createElement("div", { className: "ml-2" }, item.children.map((child, idx) => /* @__PURE__ */ React28.createElement(
13531
+ BoardItem,
13532
+ {
13533
+ key: child.id,
13534
+ index: idx,
13535
+ prevItem: item.children[idx - 1],
13536
+ nextItem: item.children[idx + 1],
13537
+ item: child,
13538
+ onUpdate,
13539
+ onDelete,
13540
+ onAddSubgroup,
13541
+ onIndent,
13542
+ onIndentDown,
13543
+ onOutdent,
13544
+ onMoveUp,
13545
+ onMoveDown,
13546
+ searchItems,
13547
+ onMentionClick,
13548
+ onEnterFolder,
13549
+ canEdit,
13550
+ isAtRoot: false
13551
+ }
13552
+ ))));
13553
+ };
13554
+ function Boards({
13555
+ isOpen,
13556
+ onClose,
13557
+ onSelect,
13558
+ availableAncestries = [],
13559
+ availableNodes = [],
13560
+ initialBoardItems = [],
13561
+ onSave,
13562
+ userRole
13563
+ }) {
13564
+ const [boardItems, setBoardItems] = useState27([]);
13565
+ const [isLoaded, setIsLoaded] = useState27(false);
13566
+ const [saveStatus, setSaveStatus] = useState27("idle");
13567
+ const [navigationStack, setNavigationStack] = useState27([]);
13568
+ const lastSavedBoardItemsRef = useRef20(null);
13569
+ const canEdit = useMemo12(() => userRole !== "viewer", [userRole]);
13570
+ const sanitizeBoardItems = (list) => list.map((g) => ({
13571
+ id: g.id,
13572
+ type: g.type || "text_group",
13573
+ name: g.name || "",
13574
+ content: g.content || [],
13575
+ children: sanitizeBoardItems(g.children || [])
13576
+ }));
13577
+ useEffect23(() => {
13578
+ if (initialBoardItems && !isLoaded) {
13579
+ const hasLegacyTypes = (list) => list.some(
13580
+ (g) => g.type === "group" || hasLegacyTypes(g.children || [])
13581
+ );
13582
+ let preparedItems = [];
13583
+ if (hasLegacyTypes(initialBoardItems)) {
13584
+ console.warn("Legacy Boards detected. Resetting board to empty list.");
13585
+ preparedItems = [];
13586
+ } else {
13587
+ const prepare = (list) => list.map((g) => ({
13588
+ id: g.id || uuidv411(),
13589
+ type: g.type || "text_group",
13590
+ name: g.name || "",
13591
+ content: Array.isArray(g.content) ? g.content : [],
13592
+ children: prepare(g.children || [])
13593
+ }));
13594
+ preparedItems = prepare(initialBoardItems);
13595
+ }
13596
+ setBoardItems(preparedItems);
13597
+ lastSavedBoardItemsRef.current = JSON.stringify(
13598
+ sanitizeBoardItems(preparedItems)
13599
+ );
13600
+ setIsLoaded(true);
13601
+ }
13602
+ }, [initialBoardItems, isLoaded]);
13603
+ const allSearchableItems = useMemo12(() => {
13604
+ const items = [];
13605
+ availableAncestries.forEach((anc) => {
13606
+ items.push({
13607
+ ...anc,
13608
+ id: String(anc.ancestry_id),
13609
+ type: "ancestry",
13610
+ searchLabel: anc.name || "",
13611
+ searchDesc: anc.description || ""
13612
+ });
13613
+ });
13614
+ availableNodes.forEach((node) => {
13615
+ var _a;
13616
+ let type = "node";
13617
+ if (node.is_quest) type = "quest";
13618
+ else if ((_a = node.version_node) == null ? void 0 : _a.is_version) type = "version";
13619
+ items.push({
13620
+ ...node,
13621
+ id: String(node.id),
13622
+ type,
13623
+ searchLabel: node.name || node.raw_title || "",
13624
+ searchDesc: node.description || ""
13625
+ });
13626
+ });
13627
+ return items;
13628
+ }, [availableAncestries, availableNodes]);
13629
+ useEffect23(() => {
13630
+ if (!isLoaded || !onSave) return;
13631
+ const id = setTimeout(async () => {
13632
+ const currentSanitized = sanitizeBoardItems(boardItems);
13633
+ const currentJson = JSON.stringify(currentSanitized);
13634
+ if (currentJson === lastSavedBoardItemsRef.current) return;
13635
+ setSaveStatus("saving");
13636
+ try {
13637
+ await onSave(currentSanitized);
13638
+ lastSavedBoardItemsRef.current = currentJson;
13639
+ setSaveStatus("saved");
13640
+ setTimeout(() => setSaveStatus("idle"), 3e3);
13641
+ } catch (err) {
13642
+ console.error("Erro ao salvar board:", err);
13643
+ setSaveStatus("error");
13644
+ }
13645
+ }, 3e3);
13646
+ return () => clearTimeout(id);
13647
+ }, [boardItems, isLoaded, onSave]);
13648
+ const triggerInstantSave = async (updatedBoardItems) => {
13649
+ if (!onSave) return;
13650
+ const sanitized = sanitizeBoardItems(updatedBoardItems);
13651
+ const sanitizedJson = JSON.stringify(sanitized);
13652
+ lastSavedBoardItemsRef.current = sanitizedJson;
13653
+ setSaveStatus("saving");
13654
+ try {
13655
+ await onSave(sanitized);
13656
+ setSaveStatus("saved");
13657
+ setTimeout(() => setSaveStatus("idle"), 3e3);
13658
+ } catch (err) {
13659
+ console.error("Erro ao salvar board:", err);
13660
+ setSaveStatus("error");
13661
+ }
13662
+ };
13663
+ useEffect23(() => {
13664
+ if (!isOpen) return;
13665
+ const handler = (e) => {
13666
+ if (e.key === "Escape") {
13667
+ onClose();
13668
+ e.preventDefault();
13669
+ e.stopPropagation();
13475
13670
  }
13476
13671
  };
13477
- window.addEventListener("keydown", handleKeyDown);
13478
- return () => window.removeEventListener("keydown", handleKeyDown);
13672
+ window.addEventListener("keydown", handler);
13673
+ return () => window.removeEventListener("keydown", handler);
13479
13674
  }, [isOpen, onClose]);
13480
- const handleAddRootGroup = () => {
13481
- const newGroup = {
13482
- id: uuidv411(),
13483
- text: "",
13484
- children: []
13485
- };
13486
- setGroups((prev) => [newGroup, ...prev]);
13675
+ const currentPath = useMemo12(() => {
13676
+ const path = [];
13677
+ let currentLevel = boardItems;
13678
+ for (const folderId of navigationStack) {
13679
+ const folder = currentLevel.find((f) => f.id === folderId);
13680
+ if (folder) {
13681
+ path.push(folder);
13682
+ currentLevel = folder.children;
13683
+ }
13684
+ }
13685
+ return path;
13686
+ }, [boardItems, navigationStack]);
13687
+ const currentBoardItems = useMemo12(() => {
13688
+ const lastFolder = currentPath[currentPath.length - 1];
13689
+ return lastFolder ? lastFolder.children : boardItems;
13690
+ }, [boardItems, currentPath]);
13691
+ const navigateUp = () => {
13692
+ setNavigationStack((prev) => prev.slice(0, -1));
13693
+ };
13694
+ const navigateTo = (folderId) => {
13695
+ setNavigationStack((prev) => [...prev, folderId]);
13696
+ };
13697
+ const navigateToBreadcrumb = (index) => {
13698
+ setNavigationStack((prev) => prev.slice(0, index + 1));
13699
+ };
13700
+ const navigateHome = () => {
13701
+ setNavigationStack([]);
13487
13702
  };
13488
- const handleAddSubgroup = (parentId) => {
13489
- const newGroup = {
13703
+ const handleAddItem = (type = "text_group") => {
13704
+ const newItem = {
13490
13705
  id: uuidv411(),
13491
- text: "",
13706
+ type,
13707
+ name: type === "folder" ? "Nova Pasta" : "",
13708
+ content: [],
13492
13709
  children: []
13493
13710
  };
13494
- const addRecursive = (list) => {
13495
- return list.map((g) => {
13496
- if (g.id === parentId) {
13497
- return { ...g, children: [newGroup, ...g.children] };
13498
- }
13499
- return { ...g, children: addRecursive(g.children) };
13500
- });
13501
- };
13502
- setGroups((prev) => addRecursive(prev));
13711
+ if (navigationStack.length === 0) {
13712
+ setBoardItems((prev) => [...prev, newItem]);
13713
+ } else {
13714
+ const parentId = navigationStack[navigationStack.length - 1];
13715
+ const add = (list) => list.map(
13716
+ (g) => g.id === parentId ? { ...g, children: [...g.children, newItem] } : { ...g, children: add(g.children) }
13717
+ );
13718
+ setBoardItems((prev) => add(prev));
13719
+ }
13503
13720
  };
13504
- const handleUpdateGroup = (id, newData) => {
13505
- const updateRecursive = (list) => {
13506
- return list.map((g) => {
13507
- if (g.id === id) return newData;
13508
- return { ...g, children: updateRecursive(g.children) };
13509
- });
13510
- };
13511
- setGroups((prev) => updateRecursive(prev));
13721
+ const handleAddSubgroup = (parentId, type = "text_group") => {
13722
+ const add = (list) => list.map((g) => {
13723
+ if (g.id === parentId) {
13724
+ if (g.type === "text_group" && type === "folder") return g;
13725
+ const newItem = {
13726
+ id: uuidv411(),
13727
+ type,
13728
+ name: type === "folder" ? "Nova Pasta" : "",
13729
+ content: [],
13730
+ children: []
13731
+ };
13732
+ return { ...g, children: [...g.children, newItem] };
13733
+ }
13734
+ return { ...g, children: add(g.children) };
13735
+ });
13736
+ setBoardItems((prev) => add(prev));
13512
13737
  };
13513
- const handleDeleteGroup = (id) => {
13514
- const deleteRecursive = (list) => {
13515
- return list.filter((g) => g.id !== id).map((g) => ({
13516
- ...g,
13517
- children: deleteRecursive(g.children)
13518
- }));
13519
- };
13520
- setGroups((prev) => deleteRecursive(prev));
13738
+ const handleUpdateItem = (id, newData) => {
13739
+ const update = (list) => list.map(
13740
+ (g) => g.id === id ? newData : { ...g, children: update(g.children) }
13741
+ );
13742
+ setBoardItems((prev) => update(prev));
13743
+ };
13744
+ const handleDeleteItem = (id) => {
13745
+ const del = (list) => list.filter((g) => g.id !== id).map((g) => ({ ...g, children: del(g.children) }));
13746
+ setBoardItems((prev) => del(prev));
13521
13747
  };
13522
13748
  const findGroupAndParent = (list, targetId, parent = null) => {
13523
13749
  for (let i = 0; i < list.length; i++) {
13524
- if (list[i].id === targetId) {
13750
+ if (list[i].id === targetId)
13525
13751
  return { group: list[i], parent, index: i, siblings: list };
13526
- }
13527
13752
  if (list[i].children.length > 0) {
13528
13753
  const found = findGroupAndParent(list[i].children, targetId, list[i]);
13529
13754
  if (found) return found;
@@ -13532,47 +13757,72 @@ function AncestryBoard({
13532
13757
  return null;
13533
13758
  };
13534
13759
  const handleIndent = (id) => {
13535
- setGroups((prev) => {
13536
- const newList = JSON.parse(JSON.stringify(prev));
13537
- const found = findGroupAndParent(newList, id);
13538
- if (!found || found.index === 0) return prev;
13539
- const { group, siblings, index } = found;
13540
- const prevSibling = siblings[index - 1];
13541
- siblings.splice(index, 1);
13542
- prevSibling.children.push(group);
13543
- return newList;
13544
- });
13760
+ const next = JSON.parse(JSON.stringify(boardItems));
13761
+ const found = findGroupAndParent(next, id);
13762
+ if (!found || found.index === 0) return;
13763
+ const { group, siblings, index } = found;
13764
+ const targetParent = siblings[index - 1];
13765
+ if (group.type === "folder" && targetParent.type === "text_group") {
13766
+ return;
13767
+ }
13768
+ siblings.splice(index, 1);
13769
+ targetParent.children.push(group);
13770
+ setBoardItems(next);
13771
+ triggerInstantSave(next);
13545
13772
  };
13546
- const handleOutdent = (id) => {
13547
- setGroups((prev) => {
13548
- const newList = JSON.parse(JSON.stringify(prev));
13549
- const found = findGroupAndParent(newList, id);
13550
- if (!found || !found.parent) return prev;
13551
- const { group, siblings, index, parent } = found;
13552
- siblings.splice(index, 1);
13553
- const parentFound = findGroupAndParent(newList, parent.id);
13554
- if (parentFound) {
13555
- parentFound.siblings.splice(parentFound.index + 1, 0, group);
13556
- } else {
13557
- newList.push(group);
13558
- }
13559
- return newList;
13560
- });
13773
+ const handleIndentDown = (id) => {
13774
+ const next = JSON.parse(JSON.stringify(boardItems));
13775
+ const found = findGroupAndParent(next, id);
13776
+ if (!found || found.index === found.siblings.length - 1) return;
13777
+ const { group, siblings, index } = found;
13778
+ const targetParent = siblings[index + 1];
13779
+ if (group.type === "folder" && targetParent.type === "text_group") {
13780
+ return;
13781
+ }
13782
+ siblings.splice(index, 1);
13783
+ targetParent.children.push(group);
13784
+ setBoardItems(next);
13785
+ triggerInstantSave(next);
13561
13786
  };
13562
- const handlePlayFromGroup = (item) => {
13563
- let fullItem = item;
13564
- if (item.type === "ancestry" || !item.type) {
13565
- fullItem = availableAncestries.find(
13566
- (a) => String(a.ancestry_id) === String(item.id || item.ancestry_id)
13567
- ) || item;
13787
+ const handleOutdent = (id) => {
13788
+ const next = JSON.parse(JSON.stringify(boardItems));
13789
+ const found = findGroupAndParent(next, id);
13790
+ if (!found || !found.parent) return;
13791
+ const { group, siblings, index, parent } = found;
13792
+ siblings.splice(index, 1);
13793
+ const parentFound = findGroupAndParent(next, parent.id);
13794
+ if (parentFound) {
13795
+ parentFound.siblings.splice(parentFound.index + 1, 0, group);
13568
13796
  } else {
13569
- fullItem = availableNodes.find(
13570
- (n) => String(n.id) === String(item.id)
13571
- ) || item;
13572
- }
13573
- if (fullItem && onSelect) {
13574
- onSelect(fullItem);
13797
+ next.push(group);
13575
13798
  }
13799
+ setBoardItems(next);
13800
+ triggerInstantSave(next);
13801
+ };
13802
+ const handleMoveUp = (id) => {
13803
+ const next = JSON.parse(JSON.stringify(boardItems));
13804
+ const found = findGroupAndParent(next, id);
13805
+ if (!found || found.index === 0) return;
13806
+ const { siblings, index } = found;
13807
+ const item = siblings[index];
13808
+ siblings.splice(index, 1);
13809
+ siblings.splice(index - 1, 0, item);
13810
+ setBoardItems(next);
13811
+ triggerInstantSave(next);
13812
+ };
13813
+ const handleMoveDown = (id) => {
13814
+ const next = JSON.parse(JSON.stringify(boardItems));
13815
+ const found = findGroupAndParent(next, id);
13816
+ if (!found || found.index === found.siblings.length - 1) return;
13817
+ const { siblings, index } = found;
13818
+ const item = siblings[index];
13819
+ siblings.splice(index, 1);
13820
+ siblings.splice(index + 1, 0, item);
13821
+ setBoardItems(next);
13822
+ triggerInstantSave(next);
13823
+ };
13824
+ const handleMentionClick = (item) => {
13825
+ if (onSelect) onSelect(item);
13576
13826
  };
13577
13827
  if (!isOpen) return null;
13578
13828
  return /* @__PURE__ */ React28.createElement(
@@ -13584,61 +13834,92 @@ function AncestryBoard({
13584
13834
  /* @__PURE__ */ React28.createElement(
13585
13835
  "div",
13586
13836
  {
13587
- className: "bg-slate-950 border border-white/10 rounded-xl w-[90vw] max-w-[1200px] h-[90vh] flex flex-col shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200",
13837
+ className: "bg-slate-950 border border-white/10 rounded-xl w-full max-w-[calc(100vw-32px)] h-[97vh] flex flex-col shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200",
13588
13838
  onClick: (e) => e.stopPropagation()
13589
13839
  },
13590
- /* @__PURE__ */ React28.createElement("div", { className: "h-14 px-6 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React28.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ React28.createElement(FiLayers7, { className: "text-indigo-400" }), "Ancestry Board"), saveStatus !== "idle" && /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React28.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-900/50 border border-white/5" }, saveStatus === "saving" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement(
13840
+ /* @__PURE__ */ React28.createElement("div", { className: "h-14 px-4 border-b border-white/10 bg-slate-900/90 flex items-center justify-between shrink-0" }, /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React28.createElement("h3", { className: "text-base font-semibold text-white flex items-center gap-2 whitespace-nowrap" }, /* @__PURE__ */ React28.createElement(FiLayers7, { className: "text-indigo-400" }), "Boards"), saveStatus !== "idle" && /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-2 animate-in fade-in slide-in-from-left-2 duration-300" }, /* @__PURE__ */ React28.createElement("div", { className: "w-px h-4 bg-white/10 mx-1" }), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-900/50 border border-white/5" }, saveStatus === "saving" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement(
13591
13841
  FiLoader5,
13592
13842
  {
13593
13843
  className: "animate-spin text-indigo-400",
13594
13844
  size: 12
13595
13845
  }
13596
- ), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement(FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-3" }, canEdit && /* @__PURE__ */ React28.createElement(
13846
+ ), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-indigo-300" }, "Salvando")), saveStatus === "saved" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement(FiCheckCircle, { className: "text-emerald-400", size: 12 }), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-slate-400" }, "Salvo")), saveStatus === "error" && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement("span", { className: "w-2 h-2 rounded-full bg-red-500" }), /* @__PURE__ */ React28.createElement("span", { className: "text-[10px] uppercase tracking-wide font-medium text-red-400" }, "Erro"))))), /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-3" }, canEdit && /* @__PURE__ */ React28.createElement(React28.Fragment, null, /* @__PURE__ */ React28.createElement(
13597
13847
  "button",
13598
13848
  {
13599
- onClick: handleAddRootGroup,
13600
- className: "\n flex items-center gap-2 px-3 py-1.5 \n bg-white/5 hover:bg-white/10 \n border border-white/10 hover:border-white/20\n backdrop-blur-sm\n text-slate-200 hover:text-white\n rounded-md transition-all duration-200\n text-xs font-medium shadow-sm\n "
13849
+ onClick: () => handleAddItem("folder"),
13850
+ className: "flex items-center gap-2 px-3 py-1.5 bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/20 backdrop-blur-sm text-slate-200 hover:text-white rounded-md transition-all duration-200 text-xs font-medium shadow-sm"
13601
13851
  },
13602
- /* @__PURE__ */ React28.createElement(FiPlus10, { size: 14, className: "text-indigo-400" }),
13603
- /* @__PURE__ */ React28.createElement("span", { className: "hidden sm:inline" }, "Novo Grupo")
13852
+ /* @__PURE__ */ React28.createElement(FiPlus10, { size: 14, className: "text-amber-400" }),
13853
+ /* @__PURE__ */ React28.createElement("span", { className: "hidden sm:inline" }, "Pasta")
13604
13854
  ), /* @__PURE__ */ React28.createElement(
13855
+ "button",
13856
+ {
13857
+ onClick: () => handleAddItem("text_group"),
13858
+ className: "flex items-center gap-2 px-3 py-1.5 bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/20 backdrop-blur-sm text-slate-200 hover:text-white rounded-md transition-all duration-200 text-xs font-medium shadow-sm"
13859
+ },
13860
+ /* @__PURE__ */ React28.createElement(FiPlus10, { size: 14, className: "text-indigo-400" }),
13861
+ /* @__PURE__ */ React28.createElement("span", { className: "hidden sm:inline" }, "Texto")
13862
+ )), /* @__PURE__ */ React28.createElement(
13605
13863
  "button",
13606
13864
  {
13607
13865
  onClick: onClose,
13608
13866
  className: "w-8 h-8 rounded-lg flex items-center justify-center text-slate-400 hover:text-white hover:bg-white/10 transition-colors"
13609
13867
  },
13610
- "\xD7"
13868
+ /* @__PURE__ */ React28.createElement(FiX8, { size: 16 })
13611
13869
  ))),
13612
- /* @__PURE__ */ React28.createElement("div", { className: "flex flex-1 overflow-hidden bg-slate-900/20" }, /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col flex-1" }, /* @__PURE__ */ React28.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-8 max-w-[900px] mx-auto w-full space-y-6" }, groups.length === 0 ? /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col items-center justify-center h-full text-slate-500 gap-4 border-2 border-dashed border-white/5 rounded-2xl bg-slate-900/40 p-12" }, /* @__PURE__ */ React28.createElement(FiLayers7, { size: 40, className: "opacity-10 text-indigo-400" }), /* @__PURE__ */ React28.createElement("div", { className: "text-center space-y-1" }, /* @__PURE__ */ React28.createElement("p", { className: "text-sm font-medium text-slate-300" }, "Nenhum grupo criado"), /* @__PURE__ */ React28.createElement("p", { className: "text-xs text-slate-500 max-w-[200px]" }, canEdit ? "Comece organizando sua narrativa criando o primeiro grupo." : "Este board ainda n\xE3o possui conte\xFAdo.")), canEdit && /* @__PURE__ */ React28.createElement(
13870
+ /* @__PURE__ */ React28.createElement("div", { className: "h-10 px-6 bg-slate-900/40 border-b border-white/5 flex items-center gap-2 text-[10px] uppercase font-bold tracking-widest text-slate-500 overflow-x-auto no-scrollbar whitespace-nowrap" }, /* @__PURE__ */ React28.createElement(
13871
+ "button",
13872
+ {
13873
+ onClick: navigateHome,
13874
+ className: `hover:text-white transition-colors flex items-center gap-1 ${navigationStack.length === 0 ? "text-indigo-400" : ""}`
13875
+ },
13876
+ "Home"
13877
+ ), currentPath.map((folder, idx) => /* @__PURE__ */ React28.createElement(React28.Fragment, { key: folder.id }, /* @__PURE__ */ React28.createElement(FiChevronRight7, { size: 10, className: "shrink-0" }), /* @__PURE__ */ React28.createElement(
13878
+ "button",
13879
+ {
13880
+ onClick: () => navigateToBreadcrumb(idx),
13881
+ className: `hover:text-white transition-colors ${idx === currentPath.length - 1 ? "text-indigo-400" : ""}`
13882
+ },
13883
+ folder.name || "Sem Nome"
13884
+ ))), navigationStack.length > 0 && /* @__PURE__ */ React28.createElement("div", { className: "ml-auto" }, /* @__PURE__ */ React28.createElement(
13613
13885
  "button",
13614
13886
  {
13615
- onClick: handleAddRootGroup,
13616
- className: "mt-2 px-4 py-2 bg-indigo-500/10 hover:bg-indigo-500/20 text-indigo-400 rounded-lg border border-indigo-500/20 transition-all text-xs font-bold uppercase tracking-wider"
13887
+ onClick: navigateUp,
13888
+ className: "flex items-center gap-1 px-2 py-1 rounded bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white transition-all"
13617
13889
  },
13618
- "Criar Primeiro Grupo"
13619
- )) : groups.map((group, index) => /* @__PURE__ */ React28.createElement(
13620
- GroupItem,
13890
+ /* @__PURE__ */ React28.createElement(FiArrowLeft3, { size: 10 }),
13891
+ /* @__PURE__ */ React28.createElement("span", null, "Voltar")
13892
+ ))),
13893
+ /* @__PURE__ */ React28.createElement("div", { className: "flex-1 overflow-y-auto overflow-x-hidden custom-scrollbar bg-slate-950/30 p-6 space-y-4" }, currentBoardItems.length === 0 ? /* @__PURE__ */ React28.createElement("div", { className: "flex flex-col items-center justify-center h-full text-slate-500 gap-3 border-2 border-dashed border-white/5 rounded-xl bg-slate-900/20" }, /* @__PURE__ */ React28.createElement(FiLayers7, { size: 24, className: "opacity-20" }), /* @__PURE__ */ React28.createElement("p", { className: "text-xs text-center px-4" }, canEdit ? /* @__PURE__ */ React28.createElement(React28.Fragment, null, "Esta pasta est\xE1 vazia.", /* @__PURE__ */ React28.createElement("br", null), "Use os bot\xF5es acima para criar conte\xFAdo.") : /* @__PURE__ */ React28.createElement(React28.Fragment, null, "Nenhum conte\xFAdo dispon\xEDvel nesta pasta."))) : currentBoardItems.map((item, index) => /* @__PURE__ */ React28.createElement(
13894
+ BoardItem,
13621
13895
  {
13622
- key: group.id,
13896
+ key: item.id,
13623
13897
  index,
13624
- group,
13625
- onUpdate: handleUpdateGroup,
13626
- onDelete: handleDeleteGroup,
13898
+ prevItem: currentBoardItems[index - 1],
13899
+ nextItem: currentBoardItems[index + 1],
13900
+ item,
13901
+ onUpdate: handleUpdateItem,
13902
+ onDelete: handleDeleteItem,
13627
13903
  onAddSubgroup: handleAddSubgroup,
13628
13904
  onIndent: handleIndent,
13905
+ onIndentDown: handleIndentDown,
13629
13906
  onOutdent: handleOutdent,
13630
- onPlayAncestry: handlePlayFromGroup,
13631
- availableMentionItems,
13632
- canEdit
13907
+ onMoveUp: handleMoveUp,
13908
+ onMoveDown: handleMoveDown,
13909
+ searchItems: allSearchableItems,
13910
+ onMentionClick: handleMentionClick,
13911
+ onEnterFolder: navigateTo,
13912
+ canEdit,
13913
+ isAtRoot: navigationStack.length === 0
13633
13914
  }
13634
- ))))),
13635
- /* @__PURE__ */ React28.createElement("div", { className: "px-6 py-3 border-t border-white/10 bg-slate-950/50 text-[10px] uppercase tracking-widest font-bold text-slate-600 flex justify-between items-center flex-shrink-0" }, /* @__PURE__ */ React28.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React28.createElement("span", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React28.createElement("div", { className: "w-1.5 h-1.5 rounded-full bg-indigo-500" }), " ", availableMentionItems.length, " RECURSOS DISPON\xCDVEIS"), /* @__PURE__ */ React28.createElement("span", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React28.createElement("div", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500" }), " ", groups.length, " GRUPOS PRINCIPAIS")), /* @__PURE__ */ React28.createElement("span", { className: "text-slate-700" }, "X.VIEW :: ANCESTRY BOARD ENGINE"))
13915
+ ))),
13916
+ /* @__PURE__ */ React28.createElement("div", { className: "px-5 py-2 border-t border-white/10 bg-slate-950/50 text-xs text-slate-500 flex justify-end flex-shrink-0" }, /* @__PURE__ */ React28.createElement("span", null, currentBoardItems.length, " itens nesta visualiza\xE7\xE3o"))
13636
13917
  )
13637
13918
  );
13638
13919
  }
13639
13920
 
13640
13921
  // src/components/MilestoneManager.jsx
13641
- import React29, { useState as useState27 } from "react";
13922
+ import React29, { useState as useState28 } from "react";
13642
13923
  import { FiTarget as FiTarget4, FiPlus as FiPlus11, FiTrash2 as FiTrash24, FiX as FiX9, FiCheck as FiCheck12, FiPauseCircle, FiPlayCircle } from "react-icons/fi";
13643
13924
  function MilestoneManager({
13644
13925
  isOpen,
@@ -13647,7 +13928,7 @@ function MilestoneManager({
13647
13928
  onUpdateMilestones = () => {
13648
13929
  }
13649
13930
  }) {
13650
- const [newMilestoneName, setNewMilestoneName] = useState27("");
13931
+ const [newMilestoneName, setNewMilestoneName] = useState28("");
13651
13932
  const handleAddMilestone = () => {
13652
13933
  if (!newMilestoneName.trim()) return;
13653
13934
  const newMilestone = {
@@ -13821,14 +14102,14 @@ function XViewScene({
13821
14102
  get_ancestry_file,
13822
14103
  import_parent_file_modal_get,
13823
14104
  add_new_parent_file_to_scene_at_firebase_action,
13824
- get_ancestry_board_action,
13825
- save_ancestry_board_action,
14105
+ get_boards_action,
14106
+ save_boards_action,
13826
14107
  upload_file_action,
13827
14108
  delete_file_action,
13828
14109
  check_user_permission,
13829
14110
  get_view_members
13830
14111
  }) {
13831
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
14112
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
13832
14113
  const { data: session, status } = useSession();
13833
14114
  const router = useRouter();
13834
14115
  const searchParams = useSearchParams();
@@ -13900,45 +14181,45 @@ function XViewScene({
13900
14181
  }
13901
14182
  return null;
13902
14183
  }, [ownerId, sceneConfigId]);
13903
- const sceneDataRef = useRef19(null);
13904
- const parentDataRef = useRef19(null);
13905
- const ancestryDataRef = useRef19(null);
13906
- const [isLoading, setIsLoading] = useState28(true);
13907
- const [permissionStatus, setPermissionStatus] = useState28("loading");
13908
- const [userPermissionRole, setUserPermissionRole] = useState28(null);
13909
- const [viewMembers, setViewMembers] = useState28([]);
13910
- const [isInitialized, setIsInitialized] = useState28(false);
13911
- const [sceneVersion, setSceneVersion] = useState28(0);
13912
- const [contextMenu, setContextMenu] = useState28({
14184
+ const sceneDataRef = useRef21(null);
14185
+ const parentDataRef = useRef21(null);
14186
+ const ancestryDataRef = useRef21(null);
14187
+ const [isLoading, setIsLoading] = useState29(true);
14188
+ const [permissionStatus, setPermissionStatus] = useState29("loading");
14189
+ const [userPermissionRole, setUserPermissionRole] = useState29(null);
14190
+ const [viewMembers, setViewMembers] = useState29([]);
14191
+ const [isInitialized, setIsInitialized] = useState29(false);
14192
+ const [sceneVersion, setSceneVersion] = useState29(0);
14193
+ const [contextMenu, setContextMenu] = useState29({
13913
14194
  visible: false,
13914
14195
  x: 0,
13915
14196
  y: 0,
13916
14197
  nodeData: null
13917
14198
  });
13918
- const [multiContextMenu, setMultiContextMenu] = useState28({
14199
+ const [multiContextMenu, setMultiContextMenu] = useState29({
13919
14200
  visible: false,
13920
14201
  x: 0,
13921
14202
  y: 0,
13922
14203
  nodeIds: null
13923
14204
  });
13924
- const [relationshipMenu, setRelationshipMenu] = useState28({
14205
+ const [relationshipMenu, setRelationshipMenu] = useState29({
13925
14206
  visible: false,
13926
14207
  x: 0,
13927
14208
  y: 0,
13928
14209
  linkObject: null
13929
14210
  });
13930
- const [creationMode, setCreationMode] = useState28({
14211
+ const [creationMode, setCreationMode] = useState29({
13931
14212
  isActive: false,
13932
14213
  sourceNodeData: null
13933
14214
  });
13934
- const [versionMode, setVersionMode] = useState28({
14215
+ const [versionMode, setVersionMode] = useState29({
13935
14216
  isActive: false,
13936
14217
  sourceNodeData: null
13937
14218
  });
13938
- const [questMode, setQuestMode] = useState28({ isActive: false });
13939
- const [hasFocusedInitial, setHasFocusedInitial] = useState28(false);
13940
- const [hasOpenedInitialAncestry, setHasOpenedInitialAncestry] = useState28(false);
13941
- const [ancestryMode, setAncestryMode] = useState28({
14219
+ const [questMode, setQuestMode] = useState29({ isActive: false });
14220
+ const [hasFocusedInitial, setHasFocusedInitial] = useState29(false);
14221
+ const [hasOpenedInitialAncestry, setHasOpenedInitialAncestry] = useState29(false);
14222
+ const [ancestryMode, setAncestryMode] = useState29({
13942
14223
  isActive: false,
13943
14224
  tree: null,
13944
14225
  selectedParentId: null,
@@ -13949,42 +14230,42 @@ function XViewScene({
13949
14230
  ancestryDescriptionSections: [],
13950
14231
  isAddingNodes: false
13951
14232
  });
13952
- const [readingMode, setReadingMode] = useState28({
14233
+ const [readingMode, setReadingMode] = useState29({
13953
14234
  isActive: false,
13954
14235
  ancestry: null,
13955
14236
  branchStack: [],
13956
14237
  autoAbstraction: false
13957
14238
  });
13958
- const [formPosition, setFormPosition] = useState28({
14239
+ const [formPosition, setFormPosition] = useState29({
13959
14240
  left: 16,
13960
14241
  top: 16,
13961
14242
  opacity: 0
13962
14243
  });
13963
- const [detailsNode, setDetailsNode] = useState28(null);
13964
- const [detailsLink, setDetailsLink] = useState28(null);
13965
- const [ancestryLinkDetails, setAncestryLinkDetails] = useState28(null);
13966
- const [imageViewer, setImageViewer] = useState28({
14244
+ const [detailsNode, setDetailsNode] = useState29(null);
14245
+ const [detailsLink, setDetailsLink] = useState29(null);
14246
+ const [ancestryLinkDetails, setAncestryLinkDetails] = useState29(null);
14247
+ const [imageViewer, setImageViewer] = useState29({
13967
14248
  visible: false,
13968
14249
  images: [],
13969
14250
  startIndex: 0
13970
14251
  });
13971
- const [editingAncestryRel, setEditingAncestryRel] = useState28({
14252
+ const [editingAncestryRel, setEditingAncestryRel] = useState29({
13972
14253
  visible: false,
13973
14254
  data: null,
13974
14255
  path: null
13975
14256
  });
13976
- const [isImportModalOpen, setIsImportModalOpen] = useState28(false);
13977
- const [importSuccessMessage, setImportSuccessMessage] = useState28("");
13978
- const [invalidTargetError, setInvalidTargetError] = useState28(null);
13979
- const [highlightedNodeId, setHighlightedNodeId] = useState28(null);
13980
- const [isAncestryBoardOpen, setIsAncestryBoardOpen] = useState28(false);
13981
- const [ancestryBoardData, setAncestryBoardData] = useState28([]);
13982
- const [isSidebarOpen, setIsSidebarOpen] = useState28(false);
13983
- const [isMilestoneManagerOpen, setIsMilestoneManagerOpen] = useState28(false);
13984
- const mountRef = useRef19(null);
13985
- const tooltipRef = useRef19(null);
13986
- const formRef = useRef19(null);
13987
- const stateRef = useRef19({
14257
+ const [isImportModalOpen, setIsImportModalOpen] = useState29(false);
14258
+ const [importSuccessMessage, setImportSuccessMessage] = useState29("");
14259
+ const [invalidTargetError, setInvalidTargetError] = useState29(null);
14260
+ const [highlightedNodeId, setHighlightedNodeId] = useState29(null);
14261
+ const [isBoardsOpen, setIsBoardsOpen] = useState29(false);
14262
+ const [boardItems, setBoardItems] = useState29([]);
14263
+ const [isSidebarOpen, setIsSidebarOpen] = useState29(false);
14264
+ const [isMilestoneManagerOpen, setIsMilestoneManagerOpen] = useState29(false);
14265
+ const mountRef = useRef21(null);
14266
+ const tooltipRef = useRef21(null);
14267
+ const formRef = useRef21(null);
14268
+ const stateRef = useRef21({
13988
14269
  readMode: {
13989
14270
  currentMaxIndex: 0,
13990
14271
  progressMap: {}
@@ -14074,10 +14355,10 @@ function XViewScene({
14074
14355
  }
14075
14356
  stateRef.current.nodeIdToParentFileMap = map;
14076
14357
  }, [isInitialized, sceneVersion]);
14077
- const handleNavigateBack = useCallback4(() => {
14358
+ const handleNavigateBack = useCallback5(() => {
14078
14359
  router.push("/dashboard/scenes");
14079
14360
  }, [router]);
14080
- const handleConfirmImport = useCallback4(
14361
+ const handleConfirmImport = useCallback5(
14081
14362
  async (importPayload) => {
14082
14363
  var _a2, _b2;
14083
14364
  let files = [];
@@ -14205,7 +14486,7 @@ function XViewScene({
14205
14486
  const handleOpenImageViewer = (images, startIndex) => {
14206
14487
  setImageViewer({ visible: true, images, startIndex });
14207
14488
  };
14208
- const tweenToTarget = useCallback4(
14489
+ const tweenToTarget = useCallback5(
14209
14490
  (target, zoomFactor = 1, forcedDirection = null) => {
14210
14491
  const { camera, controls, tweenGroup } = stateRef.current;
14211
14492
  if (!camera || !controls || !tweenGroup) return;
@@ -14231,7 +14512,7 @@ function XViewScene({
14231
14512
  if (!t || typeof t.closest !== "function") return false;
14232
14513
  return !!t.closest(".ui-overlay");
14233
14514
  };
14234
- const buildFullAncestryTree = useCallback4(
14515
+ const buildFullAncestryTree = useCallback5(
14235
14516
  (idTree, nodes, ancestries = []) => {
14236
14517
  if (!idTree) return null;
14237
14518
  const nodeMap = new Map(nodes.map((n) => [String(n.id), n]));
@@ -14316,7 +14597,7 @@ function XViewScene({
14316
14597
  },
14317
14598
  []
14318
14599
  );
14319
- const handleActivateTimeline = useCallback4(() => {
14600
+ const handleActivateTimeline = useCallback5(() => {
14320
14601
  const { nodeObjects, tweenGroup, timelineIntervalsGroup } = stateRef.current;
14321
14602
  if (!nodeObjects || !tweenGroup || !timelineIntervalsGroup) return;
14322
14603
  while (timelineIntervalsGroup.children.length > 0) {
@@ -14476,7 +14757,7 @@ function XViewScene({
14476
14757
  }
14477
14758
  });
14478
14759
  }, []);
14479
- const handleVersionTimeline = useCallback4((sourceMesh, versionMeshes) => {
14760
+ const handleVersionTimeline = useCallback5((sourceMesh, versionMeshes) => {
14480
14761
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
14481
14762
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
14482
14763
  return;
@@ -14632,7 +14913,7 @@ function XViewScene({
14632
14913
  focusNodeId,
14633
14914
  focusAncestryId
14634
14915
  );
14635
- const boardPromise = get_ancestry_board_action && session ? get_ancestry_board_action(configPath, sceneType, session, ownerId2) : Promise.resolve({ success: false, data: [] });
14916
+ const boardPromise = get_boards_action && session ? get_boards_action(configPath, sceneType, session, ownerId2) : Promise.resolve({ success: false, data: [] });
14636
14917
  const [sceneResponse, boardResponse] = await Promise.all([
14637
14918
  scenePromise,
14638
14919
  boardPromise
@@ -14683,10 +14964,10 @@ function XViewScene({
14683
14964
  (sceneResponse == null ? void 0 : sceneResponse.error) || "Resposta inv\xE1lida."
14684
14965
  );
14685
14966
  }
14686
- if (boardResponse == null ? void 0 : boardResponse.success) {
14687
- setAncestryBoardData(boardResponse.data);
14967
+ if (boardResponse.success && boardResponse.data) {
14968
+ setBoardItems(boardResponse.data);
14688
14969
  } else {
14689
- setAncestryBoardData([]);
14970
+ setBoardItems([]);
14690
14971
  }
14691
14972
  } catch (error) {
14692
14973
  console.error("Erro cr\xEDtico no carregamento inicial:", error);
@@ -14712,18 +14993,18 @@ function XViewScene({
14712
14993
  ownerId,
14713
14994
  viewParams,
14714
14995
  get_scene_view_data,
14715
- get_ancestry_board_action,
14996
+ get_boards_action,
14716
14997
  isInitialized,
14717
14998
  permissionStatus,
14718
14999
  focusNodeId,
14719
15000
  focusAncestryId
14720
15001
  ]);
14721
- const isNodeInView = useCallback4((id) => {
15002
+ const isNodeInView = useCallback5((id) => {
14722
15003
  const key = String(id);
14723
15004
  const objs = stateRef.current.nodeObjects || {};
14724
15005
  return !!objs[key];
14725
15006
  }, []);
14726
- const addOrUpdateNodeMesh = useCallback4(
15007
+ const addOrUpdateNodeMesh = useCallback5(
14727
15008
  (nodeData, position, suppressVersionUpdate = false, forceShowQuestDate = false) => {
14728
15009
  const {
14729
15010
  graphGroup,
@@ -14764,7 +15045,7 @@ function XViewScene({
14764
15045
  },
14765
15046
  []
14766
15047
  );
14767
- const handleActivateQuestLens = useCallback4(({ assigneeId, status: status2, milestoneId }) => {
15048
+ const handleActivateQuestLens = useCallback5(({ assigneeId, status: status2, milestoneId }) => {
14768
15049
  var _a2;
14769
15050
  const { nodeObjects, tweenGroup, graphGroup } = stateRef.current;
14770
15051
  if (!nodeObjects || !tweenGroup || !graphGroup) return;
@@ -15734,7 +16015,7 @@ function XViewScene({
15734
16015
  get_scene_view_data,
15735
16016
  save_view_data
15736
16017
  ]);
15737
- const handleGhostNodeImageChange = useCallback4((useImage, imageUrl) => {
16018
+ const handleGhostNodeImageChange = useCallback5((useImage, imageUrl) => {
15738
16019
  const {
15739
16020
  node: ghostNode,
15740
16021
  line: ghostLine,
@@ -15782,7 +16063,7 @@ function XViewScene({
15782
16063
  aura: newGhostNode.getObjectByName("aura")
15783
16064
  };
15784
16065
  }, []);
15785
- const handleGhostNodeIntensityChange = useCallback4((newIntensity) => {
16066
+ const handleGhostNodeIntensityChange = useCallback5((newIntensity) => {
15786
16067
  const { node: ghostNode, aura: ghostAura } = stateRef.current.ghostElements;
15787
16068
  if (!ghostNode) return;
15788
16069
  const adjustedIntensity = newIntensity + MIN_VISIBILITY_INTENSITY;
@@ -15803,7 +16084,7 @@ function XViewScene({
15803
16084
  ghostAura.material.opacity = Math.min(0.8, newIntensity * 0.15);
15804
16085
  }
15805
16086
  }, []);
15806
- const handleDetailNodeIntensityChange = useCallback4(
16087
+ const handleDetailNodeIntensityChange = useCallback5(
15807
16088
  (nodeId, newIntensity) => {
15808
16089
  const mesh = stateRef.current.nodeObjects[String(nodeId)];
15809
16090
  if (!mesh) return;
@@ -15959,7 +16240,7 @@ function XViewScene({
15959
16240
  mountRef.current.style.cursor = "default";
15960
16241
  }
15961
16242
  };
15962
- const handleAncestryTreeUpdate = useCallback4((newTree, extraData = null) => {
16243
+ const handleAncestryTreeUpdate = useCallback5((newTree, extraData = null) => {
15963
16244
  setAncestryMode((prev) => {
15964
16245
  const prevTreeStr = JSON.stringify(prev.tree);
15965
16246
  const newTreeStr = JSON.stringify(newTree);
@@ -16043,7 +16324,7 @@ function XViewScene({
16043
16324
  const handleStartVersioning = (nodeData) => {
16044
16325
  userActionHandlers.handleStartVersioning(actionHandlerContext, nodeData);
16045
16326
  };
16046
- const handleStartQuestQuick = useCallback4((questNode) => {
16327
+ const handleStartQuestQuick = useCallback5((questNode) => {
16047
16328
  var _a2, _b2, _c2;
16048
16329
  if (!questNode || !actionHandlerContext) return;
16049
16330
  const allParentNodes = Object.values(parentDataRef.current).flatMap((fileData) => fileData.nodes || []);
@@ -16078,7 +16359,7 @@ function XViewScene({
16078
16359
  }
16079
16360
  setDetailsNode(updatedNode);
16080
16361
  }, [session, actionHandlerContext]);
16081
- const handleFinishQuestQuick = useCallback4((questNode) => {
16362
+ const handleFinishQuestQuick = useCallback5((questNode) => {
16082
16363
  var _a2, _b2, _c2, _d2;
16083
16364
  if (!questNode || !actionHandlerContext) return;
16084
16365
  const {
@@ -16152,7 +16433,7 @@ function XViewScene({
16152
16433
  setDetailsNode(null);
16153
16434
  }
16154
16435
  }, [session, actionHandlerContext, viewMembers, detailsNode]);
16155
- const handleCancelQuest = useCallback4(() => {
16436
+ const handleCancelQuest = useCallback5(() => {
16156
16437
  const { graphGroup, ghostElements } = stateRef.current;
16157
16438
  if (ghostElements.node && graphGroup) {
16158
16439
  if (ghostElements.node.userData.labelObject) {
@@ -16211,13 +16492,15 @@ function XViewScene({
16211
16492
  try {
16212
16493
  await actions.save_view_data(sceneSaveUrl2, sceneFileData);
16213
16494
  sceneDataRef2.current.quest_counter = currentCounter + 1;
16214
- sceneDataRef2.current.quest_nodes = sceneFileData.quest_nodes;
16215
- sceneDataRef2.current.quest_links = sceneFileData.quest_links;
16216
16495
  stateRef2.current.nodeIdToParentFileMap.set(String(newNode.id), {
16217
16496
  parentFileId: sceneConfigId2,
16218
16497
  ownerId: ownerId2,
16219
16498
  datasetName: "Quests Internas (View)"
16220
16499
  });
16500
+ sceneDataRef2.current.quest_nodes = [...graphDataRef.current[sceneConfigId2].nodes];
16501
+ if (parentDataRef.current[sceneConfigId2]) {
16502
+ parentDataRef.current[sceneConfigId2].nodes = [...graphDataRef.current[sceneConfigId2].nodes];
16503
+ }
16221
16504
  const finalPosition = stateRef2.current.ghostElements.node ? stateRef2.current.ghostElements.node.position.clone() : stateRef2.current.controls.target.clone();
16222
16505
  const { graphGroup, ghostElements } = stateRef2.current;
16223
16506
  if (ghostElements.node && graphGroup) {
@@ -16307,7 +16590,7 @@ function XViewScene({
16307
16590
  }
16308
16591
  userActionHandlers.handleCancelConnection(context);
16309
16592
  };
16310
- const handleClearAncestryVisuals = useCallback4((ancestryId) => {
16593
+ const handleClearAncestryVisuals = useCallback5((ancestryId) => {
16311
16594
  const { renderedAncestries, ancestryGroup } = stateRef.current;
16312
16595
  const renderIndex = renderedAncestries.findIndex(
16313
16596
  (a) => String(a.id) === String(ancestryId)
@@ -16325,7 +16608,7 @@ function XViewScene({
16325
16608
  );
16326
16609
  }
16327
16610
  }, []);
16328
- const handleRenderAncestry = useCallback4(
16611
+ const handleRenderAncestry = useCallback5(
16329
16612
  async (ancestryObject, allowedSectionIds = null, activeSectionIdForFocus = null, baseRotation = 0, forceReprocess = true) => {
16330
16613
  setContextMenu(
16331
16614
  (prev) => prev.visible ? { ...prev, visible: false } : prev
@@ -16819,7 +17102,7 @@ function XViewScene({
16819
17102
  ancestryMode.isActive
16820
17103
  ]
16821
17104
  );
16822
- const handleRenderAbstractionTree = useCallback4(
17105
+ const handleRenderAbstractionTree = useCallback5(
16823
17106
  (ancestryObject, targetNodeId = null) => {
16824
17107
  setContextMenu(
16825
17108
  (prev) => prev.visible ? { ...prev, visible: false } : prev
@@ -16907,7 +17190,7 @@ function XViewScene({
16907
17190
  handleClearAncestryVisuals
16908
17191
  ]
16909
17192
  );
16910
- const handleReadModeBranchNav = useCallback4(
17193
+ const handleReadModeBranchNav = useCallback5(
16911
17194
  (nodeId, action, direction = "right") => {
16912
17195
  const { ancestry, branchStack } = readingMode;
16913
17196
  if (!ancestry || !ancestry.tree) return;
@@ -17085,7 +17368,7 @@ function XViewScene({
17085
17368
  },
17086
17369
  [readingMode, handleRenderAncestry, buildFullAncestryTree, tweenToTarget]
17087
17370
  );
17088
- const handleReadModeHighlight = useCallback4((nodeId) => {
17371
+ const handleReadModeHighlight = useCallback5((nodeId) => {
17089
17372
  if (stateRef.current.highlightedNodeId !== nodeId) {
17090
17373
  stateRef.current.highlightedNodeId = nodeId;
17091
17374
  }
@@ -17208,7 +17491,7 @@ function XViewScene({
17208
17491
  buildFullAncestryTree,
17209
17492
  sceneVersion
17210
17493
  ]);
17211
- const handleStartReadingAncestry = useCallback4(
17494
+ const handleStartReadingAncestry = useCallback5(
17212
17495
  async (ancestryObject, renderMode = "full") => {
17213
17496
  setContextMenu(
17214
17497
  (prev) => prev.visible ? { ...prev, visible: false } : prev
@@ -17251,7 +17534,7 @@ function XViewScene({
17251
17534
  },
17252
17535
  [handleRenderAncestry, handleRenderAbstractionTree]
17253
17536
  );
17254
- const handleReadModeSectionChange = useCallback4(
17537
+ const handleReadModeSectionChange = useCallback5(
17255
17538
  (activeSectionId) => {
17256
17539
  const { ancestry, branchStack } = readingMode;
17257
17540
  if (!ancestry || !readingMode.isActive) return;
@@ -17336,10 +17619,10 @@ function XViewScene({
17336
17619
  ancestryDataRef.current
17337
17620
  ]
17338
17621
  );
17339
- const handleCloseReadMode = useCallback4(() => {
17622
+ const handleCloseReadMode = useCallback5(() => {
17340
17623
  setReadingMode({ isActive: false, ancestry: null, branchStack: [] });
17341
17624
  }, []);
17342
- const handleAncestrySectionChange = useCallback4(
17625
+ const handleAncestrySectionChange = useCallback5(
17343
17626
  (activeSectionId, ancestryOverride = null, rotation = 0) => {
17344
17627
  var _a2, _b2;
17345
17628
  const currentMode = stateRef.current.ancestry;
@@ -17401,7 +17684,7 @@ function XViewScene({
17401
17684
  },
17402
17685
  [handleRenderAncestry]
17403
17686
  );
17404
- const handleEditAncestry = useCallback4(
17687
+ const handleEditAncestry = useCallback5(
17405
17688
  async (ancestryObject) => {
17406
17689
  setContextMenu(
17407
17690
  (prev) => prev.visible ? { ...prev, visible: false } : prev
@@ -17456,7 +17739,7 @@ function XViewScene({
17456
17739
  const handleSelectAncestryParent = (nodeId) => {
17457
17740
  setAncestryMode((prev) => ({ ...prev, selectedParentId: nodeId }));
17458
17741
  };
17459
- const handleRemoveFromAncestry = useCallback4((pathToRemove) => {
17742
+ const handleRemoveFromAncestry = useCallback5((pathToRemove) => {
17460
17743
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) {
17461
17744
  console.warn("Tentativa de remover a raiz ou caminho inv\xE1lido.");
17462
17745
  return;
@@ -17481,7 +17764,7 @@ function XViewScene({
17481
17764
  return { ...prev, tree: newTree };
17482
17765
  });
17483
17766
  }, []);
17484
- const handleSaveAncestry = useCallback4(
17767
+ const handleSaveAncestry = useCallback5(
17485
17768
  async (ancestryName, ancestryDescription, ancestrySections, keepOpen = false, treeOverride = null, ancestryCustomProps = {}) => {
17486
17769
  const treeToUse = treeOverride || ancestryMode.tree;
17487
17770
  const { isEditMode, currentAncestryId } = ancestryMode;
@@ -17717,7 +18000,7 @@ function XViewScene({
17717
18000
  });
17718
18001
  setEditingAncestryRel({ visible: false, data: null, path: null });
17719
18002
  };
17720
- const handleDeleteAncestry = useCallback4(
18003
+ const handleDeleteAncestry = useCallback5(
17721
18004
  async (ancestryIdToDelete) => {
17722
18005
  if (!ancestryIdToDelete) {
17723
18006
  alert("ID da ancestralidade n\xE3o encontrado.");
@@ -17799,40 +18082,95 @@ function XViewScene({
17799
18082
  },
17800
18083
  [save_view_data, delete_file_action]
17801
18084
  );
17802
- const handleOpenAncestryBoard = useCallback4(() => {
17803
- setIsAncestryBoardOpen(true);
18085
+ const handleOpenBoards = useCallback5(() => {
18086
+ setIsBoardsOpen(true);
17804
18087
  }, []);
17805
- const handleSelectAncestryFromBoard = useCallback4(
18088
+ const allAvailableNodes = useMemo13(() => {
18089
+ if (!parentDataRef.current) return [];
18090
+ return Object.values(parentDataRef.current).flatMap(
18091
+ (fileData) => fileData.nodes || []
18092
+ );
18093
+ }, [sceneVersion, isInitialized]);
18094
+ const allAvailableAncestries = useMemo13(() => {
18095
+ return ancestryDataRef.current || [];
18096
+ }, [sceneVersion, isInitialized]);
18097
+ const handleFocusNode = useCallback5(
18098
+ (nodeData) => {
18099
+ var _a2;
18100
+ if (!nodeData) return;
18101
+ let nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
18102
+ if (!nodeMesh) {
18103
+ const spawnPos = ((_a2 = stateRef.current.controls) == null ? void 0 : _a2.target.clone()) || new THREE3.Vector3();
18104
+ addStandaloneNodeToScene(stateRef.current, nodeData, spawnPos);
18105
+ nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
18106
+ }
18107
+ if (nodeMesh) {
18108
+ tweenToTarget(nodeMesh, 1.2);
18109
+ }
18110
+ },
18111
+ [tweenToTarget]
18112
+ );
18113
+ const handleOpenReference = useCallback5(
18114
+ (referenceData) => {
18115
+ const { type, id } = referenceData;
18116
+ if (type === "node") {
18117
+ const targetNode = allAvailableNodes.find(
18118
+ (n) => String(n.id) === String(id)
18119
+ );
18120
+ if (targetNode) {
18121
+ setAncestryLinkDetails(null);
18122
+ setDetailsLink(null);
18123
+ setDetailsNode(targetNode);
18124
+ handleFocusNode(targetNode);
18125
+ } else {
18126
+ alert("Node original n\xE3o encontrado neste contexto.");
18127
+ }
18128
+ } else if (type === "ancestry") {
18129
+ const targetAncestry = allAvailableAncestries.find(
18130
+ (a) => String(a.ancestry_id) === String(id)
18131
+ );
18132
+ if (targetAncestry) {
18133
+ setDetailsNode(null);
18134
+ setDetailsLink(null);
18135
+ setAncestryLinkDetails(null);
18136
+ handleEditAncestry(targetAncestry);
18137
+ } else {
18138
+ alert("Ancestralidade original n\xE3o encontrada neste contexto.");
18139
+ }
18140
+ }
18141
+ },
18142
+ [
18143
+ allAvailableNodes,
18144
+ allAvailableAncestries,
18145
+ handleEditAncestry,
18146
+ handleFocusNode
18147
+ ]
18148
+ );
18149
+ const handleSelectAncestryFromBoard = useCallback5(
17806
18150
  (item) => {
17807
- setIsAncestryBoardOpen(false);
18151
+ setIsBoardsOpen(false);
17808
18152
  setIsSidebarOpen(false);
17809
- if (item.type === "ancestry" || !item.type) {
18153
+ if (item.type === "ancestry") {
17810
18154
  handleStartReadingAncestry(item);
17811
18155
  } else {
17812
- const nodeId = String(item.id);
17813
- const sceneMesh = stateRef.current.nodeObjects[nodeId];
17814
- if (sceneMesh) {
17815
- tweenToTarget(sceneMesh);
17816
- } else {
17817
- console.warn("Objeto n\xE3o encontrado na cena para foco:", nodeId);
17818
- }
18156
+ handleOpenReference({ type: "node", id: item.id });
17819
18157
  }
17820
18158
  },
17821
- [handleStartReadingAncestry, tweenToTarget]
18159
+ [handleStartReadingAncestry, handleOpenReference]
17822
18160
  );
17823
- const handleSaveAncestryBoard = useCallback4(
17824
- async (groups) => {
18161
+ const handleSaveBoards = useCallback5(
18162
+ async (boardItems2) => {
17825
18163
  if (!sceneConfigId || !viewParams || !session) return;
17826
18164
  const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
17827
- await save_ancestry_board_action(
18165
+ await save_boards_action(
17828
18166
  sceneConfigId,
17829
18167
  sceneType,
17830
- groups,
18168
+ boardItems2,
17831
18169
  session,
17832
18170
  ownerId
17833
18171
  );
17834
18172
  },
17835
- [sceneConfigId, viewParams, session, save_ancestry_board_action, ownerId]
18173
+ [sceneConfigId, viewParams, session, save_boards_action, ownerId]
17836
18174
  );
17837
18175
  const existingNodeTypes = useMemo13(() => {
17838
18176
  if (!parentDataRef.current) {
@@ -17847,27 +18185,21 @@ function XViewScene({
17847
18185
  return [...new Set(allTypes)];
17848
18186
  }, [parentDataRef.current, sceneVersion]);
17849
18187
  const searchableDbNodes = useMemo13(() => {
17850
- var _a2;
17851
18188
  if (!parentDataRef.current) {
17852
18189
  return [];
17853
18190
  }
17854
- const datasetsPool = Object.entries(parentDataRef.current).flatMap(([dbId, fileData]) => {
18191
+ return Object.entries(parentDataRef.current).flatMap(([dbId, fileData]) => {
17855
18192
  const datasetName = fileData.dataset_name || `Dataset #${dbId.substring(0, 6)}`;
17856
18193
  return (fileData.nodes || []).map((node) => ({
17857
18194
  ...node,
17858
18195
  dataset_name: datasetName
17859
18196
  }));
18197
+ }).filter((node) => {
18198
+ var _a2;
18199
+ return !((_a2 = node.version_node) == null ? void 0 : _a2.is_version);
17860
18200
  });
17861
- const questsPool = (((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || []).map((node) => ({
17862
- ...node,
17863
- dataset_name: "Quests Internas (View)"
17864
- }));
17865
- return [...datasetsPool, ...questsPool].filter((node) => {
17866
- var _a3;
17867
- return !((_a3 = node.version_node) == null ? void 0 : _a3.is_version);
17868
- });
17869
- }, [parentDataRef.current, (_b = sceneDataRef.current) == null ? void 0 : _b.quest_nodes, sceneVersion]);
17870
- const handleAddExistingNode = useCallback4(
18201
+ }, [parentDataRef.current, sceneVersion]);
18202
+ const handleAddExistingNode = useCallback5(
17871
18203
  (nodeId) => {
17872
18204
  return userActionHandlers.handleAddExistingNodeById(
17873
18205
  actionHandlerContext,
@@ -17876,7 +18208,7 @@ function XViewScene({
17876
18208
  },
17877
18209
  [actionHandlerContext]
17878
18210
  );
17879
- const handleUpdateMilestones = useCallback4(
18211
+ const handleUpdateMilestones = useCallback5(
17880
18212
  async (newMilestones) => {
17881
18213
  var _a2, _b2, _c2;
17882
18214
  if (!sceneDataRef.current) return;
@@ -17922,7 +18254,7 @@ function XViewScene({
17922
18254
  viewParams == null ? void 0 : viewParams.type
17923
18255
  ]
17924
18256
  );
17925
- const handleSaveCurrentView = useCallback4(async () => {
18257
+ const handleSaveCurrentView = useCallback5(async () => {
17926
18258
  var _a2, _b2, _c2;
17927
18259
  const { nodeObjects, allLinks } = stateRef.current;
17928
18260
  if (!nodeObjects || !allLinks || !sceneSaveUrl || !parentDataRef.current) {
@@ -17965,76 +18297,12 @@ function XViewScene({
17965
18297
  console.error("Erro na chamada de save_view_data:", error);
17966
18298
  }
17967
18299
  }, [sceneSaveUrl, save_view_data, sceneConfigId, viewParams == null ? void 0 : viewParams.type]);
17968
- const allAvailableNodes = useMemo13(() => {
17969
- if (!parentDataRef.current) return [];
17970
- return Object.values(parentDataRef.current).flatMap(
17971
- (fileData) => fileData.nodes || []
17972
- );
17973
- }, [sceneVersion, isInitialized]);
17974
- const allAvailableAncestries = useMemo13(() => {
17975
- return ancestryDataRef.current || [];
17976
- }, [sceneVersion, isInitialized]);
17977
- const handleOpenReference = useCallback4(
17978
- (referenceData) => {
17979
- const { type, id } = referenceData;
17980
- if (type === "node") {
17981
- const targetNode = allAvailableNodes.find(
17982
- (n) => String(n.id) === String(id)
17983
- );
17984
- if (targetNode) {
17985
- setAncestryLinkDetails(null);
17986
- setDetailsLink(null);
17987
- setDetailsNode(targetNode);
17988
- const sceneMesh = stateRef.current.nodeObjects[String(id)];
17989
- if (sceneMesh) {
17990
- tweenToTarget(sceneMesh);
17991
- }
17992
- } else {
17993
- alert("Node original n\xE3o encontrado neste contexto.");
17994
- }
17995
- } else if (type === "ancestry") {
17996
- const targetAncestry = allAvailableAncestries.find(
17997
- (a) => String(a.ancestry_id) === String(id)
17998
- );
17999
- if (targetAncestry) {
18000
- setDetailsNode(null);
18001
- setDetailsLink(null);
18002
- setAncestryLinkDetails(null);
18003
- handleEditAncestry(targetAncestry);
18004
- } else {
18005
- alert("Ancestralidade original n\xE3o encontrada neste contexto.");
18006
- }
18007
- }
18008
- },
18009
- [
18010
- allAvailableNodes,
18011
- allAvailableAncestries,
18012
- handleEditAncestry,
18013
- tweenToTarget
18014
- ]
18015
- );
18016
- const handleToggleAncestryAddMode = useCallback4(() => {
18300
+ const handleToggleAncestryAddMode = useCallback5(() => {
18017
18301
  setAncestryMode((prev) => ({
18018
18302
  ...prev,
18019
18303
  isAddingNodes: !prev.isAddingNodes
18020
18304
  }));
18021
18305
  }, []);
18022
- const handleFocusNode = useCallback4(
18023
- (nodeData) => {
18024
- var _a2;
18025
- if (!nodeData) return;
18026
- let nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
18027
- if (!nodeMesh) {
18028
- const spawnPos = ((_a2 = stateRef.current.controls) == null ? void 0 : _a2.target.clone()) || new THREE3.Vector3();
18029
- addStandaloneNodeToScene(stateRef.current, nodeData, spawnPos);
18030
- nodeMesh = stateRef.current.nodeObjects[String(nodeData.id)];
18031
- }
18032
- if (nodeMesh) {
18033
- tweenToTarget(nodeMesh, 1.2);
18034
- }
18035
- },
18036
- [tweenToTarget]
18037
- );
18038
18306
  const availableDatasets = useMemo13(() => {
18039
18307
  if (!sceneDataRef.current || !parentDataRef.current) return [];
18040
18308
  return sceneDataRef.current.parent_dbs.map((db) => {
@@ -18045,9 +18313,9 @@ function XViewScene({
18045
18313
  };
18046
18314
  });
18047
18315
  }, [sceneVersion, isInitialized]);
18048
- const sourceNodeDatasetId = creationMode.sourceNodeData ? (_c = stateRef.current.nodeIdToParentFileMap.get(
18316
+ const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(
18049
18317
  String(creationMode.sourceNodeData.id)
18050
- )) == null ? void 0 : _c.parentFileId : null;
18318
+ )) == null ? void 0 : _b.parentFileId : null;
18051
18319
  const detailsNodeDatasetInfo = detailsNode ? stateRef.current.nodeIdToParentFileMap.get(String(detailsNode.id)) : null;
18052
18320
  useEffect24(() => {
18053
18321
  if (isInitialized && focusNodeId && !hasFocusedInitial) {
@@ -18137,7 +18405,7 @@ function XViewScene({
18137
18405
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
18138
18406
  }
18139
18407
  if (event.key.toLowerCase() === "q") {
18140
- 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;
18408
+ 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 && !isBoardsOpen && !isSidebarOpen && !detailsNode && !detailsLink && !ancestryLinkDetails && !imageViewer.visible && !editingAncestryRel.visible && !questMode.isActive;
18141
18409
  if (isUiClear) {
18142
18410
  const isView = ((_c2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _c2.toLowerCase()) === "view";
18143
18411
  if (!isView) return;
@@ -18206,7 +18474,7 @@ function XViewScene({
18206
18474
  relationshipMenu.visible,
18207
18475
  readingMode.isActive,
18208
18476
  isImportModalOpen,
18209
- isAncestryBoardOpen,
18477
+ isBoardsOpen,
18210
18478
  isSidebarOpen,
18211
18479
  detailsNode,
18212
18480
  detailsLink,
@@ -18293,7 +18561,7 @@ function XViewScene({
18293
18561
  importSuccessMessage,
18294
18562
  onActivateTimeline: handleActivateTimeline,
18295
18563
  onActivateQuestLens: handleActivateQuestLens,
18296
- onOpenAncestryBoard: handleOpenAncestryBoard,
18564
+ onOpenBoards: handleOpenBoards,
18297
18565
  viewType: viewParams == null ? void 0 : viewParams.type,
18298
18566
  viewName: viewParams == null ? void 0 : viewParams.name,
18299
18567
  onNavigateBack: handleNavigateBack,
@@ -18301,7 +18569,7 @@ function XViewScene({
18301
18569
  onToggle: setIsSidebarOpen,
18302
18570
  userRole: userPermissionRole,
18303
18571
  viewMembers,
18304
- milestones: ((_d = sceneDataRef.current) == null ? void 0 : _d.milestones) || [],
18572
+ milestones: ((_c = sceneDataRef.current) == null ? void 0 : _c.milestones) || [],
18305
18573
  onOpenMilestoneManager: () => setIsMilestoneManagerOpen(true)
18306
18574
  }
18307
18575
  ),
@@ -18326,8 +18594,8 @@ function XViewScene({
18326
18594
  },
18327
18595
  refEl: formRef,
18328
18596
  existingTypes: existingNodeTypes,
18329
- initialColor: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.color,
18330
- sourceTypes: (_f = creationMode.sourceNodeData) == null ? void 0 : _f.type,
18597
+ initialColor: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.color,
18598
+ sourceTypes: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.type,
18331
18599
  onIntensityChange: handleGhostNodeIntensityChange,
18332
18600
  onUploadFile: upload_file_action,
18333
18601
  availableDatasets,
@@ -18356,8 +18624,8 @@ function XViewScene({
18356
18624
  transition: "opacity 200ms ease-out"
18357
18625
  },
18358
18626
  refEl: formRef,
18359
- fixedType: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.type,
18360
- fixedColor: (_h = versionMode.sourceNodeData) == null ? void 0 : _h.color,
18627
+ fixedType: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.type,
18628
+ fixedColor: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.color,
18361
18629
  onUploadFile: upload_file_action,
18362
18630
  availableNodes: allAvailableNodes,
18363
18631
  availableAncestries: allAvailableAncestries
@@ -18385,9 +18653,9 @@ function XViewScene({
18385
18653
  availableNodes: allAvailableNodes,
18386
18654
  availableAncestries: allAvailableAncestries,
18387
18655
  viewName: viewParams == null ? void 0 : viewParams.name,
18388
- questCounter: ((_i = sceneDataRef.current) == null ? void 0 : _i.quest_counter) || 1,
18656
+ questCounter: ((_h = sceneDataRef.current) == null ? void 0 : _h.quest_counter) || 1,
18389
18657
  viewMembers,
18390
- milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
18658
+ milestones: ((_i = sceneDataRef.current) == null ? void 0 : _i.milestones) || [],
18391
18659
  onUpdateMilestones: handleUpdateMilestones
18392
18660
  }
18393
18661
  ),
@@ -18522,7 +18790,7 @@ function XViewScene({
18522
18790
  currentDatasetName: detailsNodeDatasetInfo == null ? void 0 : detailsNodeDatasetInfo.datasetName,
18523
18791
  viewMembers,
18524
18792
  currentUser: session == null ? void 0 : session.user,
18525
- milestones: ((_k = sceneDataRef.current) == null ? void 0 : _k.milestones) || [],
18793
+ milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
18526
18794
  onUpdateMilestones: handleUpdateMilestones
18527
18795
  }
18528
18796
  ),
@@ -18689,15 +18957,15 @@ function XViewScene({
18689
18957
  }
18690
18958
  ),
18691
18959
  /* @__PURE__ */ React30.createElement(
18692
- AncestryBoard,
18960
+ Boards,
18693
18961
  {
18694
- isOpen: isAncestryBoardOpen,
18695
- onClose: () => setIsAncestryBoardOpen(false),
18962
+ isOpen: isBoardsOpen,
18963
+ onClose: () => setIsBoardsOpen(false),
18696
18964
  onSelect: handleSelectAncestryFromBoard,
18697
18965
  availableAncestries: allAvailableAncestries,
18698
18966
  availableNodes: allAvailableNodes,
18699
- initialGroups: ancestryBoardData,
18700
- onSave: handleSaveAncestryBoard,
18967
+ initialBoardItems: boardItems,
18968
+ onSave: handleSaveBoards,
18701
18969
  userRole: userPermissionRole
18702
18970
  }
18703
18971
  ),
@@ -18706,7 +18974,7 @@ function XViewScene({
18706
18974
  {
18707
18975
  isOpen: isMilestoneManagerOpen,
18708
18976
  onClose: () => setIsMilestoneManagerOpen(false),
18709
- milestones: ((_l = sceneDataRef.current) == null ? void 0 : _l.milestones) || [],
18977
+ milestones: ((_k = sceneDataRef.current) == null ? void 0 : _k.milestones) || [],
18710
18978
  onUpdateMilestones: handleUpdateMilestones
18711
18979
  }
18712
18980
  ),
@@ -18717,7 +18985,7 @@ function XViewScene({
18717
18985
  onClose: () => setIsImportModalOpen(false),
18718
18986
  onConfirm: handleConfirmImport,
18719
18987
  session,
18720
- parentDbs: ((_m = sceneDataRef.current) == null ? void 0 : _m.parent_dbs) || [],
18988
+ parentDbs: ((_l = sceneDataRef.current) == null ? void 0 : _l.parent_dbs) || [],
18721
18989
  onFetchAvailableFiles: import_parent_file_modal_get,
18722
18990
  currentViewName: viewParams == null ? void 0 : viewParams.name,
18723
18991
  currentAncestries: ancestryDataRef.current || []
@@ -18801,6 +19069,450 @@ function XViewScene({
18801
19069
  )
18802
19070
  );
18803
19071
  }
19072
+
19073
+ // src/x_view_server_logic.js
19074
+ async function save_view_data_logic(db_services, filename, data) {
19075
+ try {
19076
+ const saveResult = await db_services.save_file(filename, data);
19077
+ return saveResult;
19078
+ } catch (error) {
19079
+ console.error("catch (error) em save_view_data_logic:", error);
19080
+ return { success: false, error: error.message };
19081
+ }
19082
+ }
19083
+ async function get_scene_view_data_logic(db_services, scene_config, owner_id, type, session, focusNodeId = null, focusAncestryId = null) {
19084
+ var _a, _b;
19085
+ try {
19086
+ if (!session || !session.user) {
19087
+ return { success: false, error: "Usu\xE1rio n\xE3o autenticado." };
19088
+ }
19089
+ const membershipCheck = await db_services.check_membership(owner_id, type, scene_config, session.user.id);
19090
+ const isFullMember = membershipCheck.success && membershipCheck.isValidMember;
19091
+ if (!isFullMember && !focusNodeId && !focusAncestryId) {
19092
+ return { success: false, error: "Acesso Negado: Voc\xEA n\xE3o possui permiss\xE3o para acessar este cen\xE1rio." };
19093
+ }
19094
+ const sceneResponse = await db_services.get_file(`x_view_scenes/${owner_id}/${scene_config}`);
19095
+ if (!sceneResponse || !sceneResponse.success) {
19096
+ console.error("Falha ao buscar dados da cena:", sceneResponse == null ? void 0 : sceneResponse.error);
19097
+ return { success: false, error: `N\xE3o foi poss\xEDvel carregar o arquivo da cena: ${scene_config}` };
19098
+ }
19099
+ const sceneData = sceneResponse.data;
19100
+ const parentDbObjects = sceneData.parent_dbs || [];
19101
+ if (type && type.toLowerCase().includes("database")) {
19102
+ const selfExists = parentDbObjects.some((db) => String(db.db_id) === String(scene_config));
19103
+ if (!selfExists) {
19104
+ parentDbObjects.push({ db_id: scene_config, owner_id });
19105
+ }
19106
+ }
19107
+ const parentResponsesPromises = parentDbObjects.map(
19108
+ (db_info) => db_services.get_file(`x_view_dbs/${db_info.owner_id}/${db_info.db_id}`)
19109
+ );
19110
+ const parentResponsesResults = await Promise.allSettled(parentResponsesPromises);
19111
+ const parentData = {};
19112
+ for (let i = 0; i < parentResponsesResults.length; i++) {
19113
+ const result = parentResponsesResults[i];
19114
+ const db_info = parentDbObjects[i];
19115
+ if (result.status === "fulfilled" && ((_a = result.value) == null ? void 0 : _a.success)) {
19116
+ parentData[db_info.db_id] = result.value.data;
19117
+ } else {
19118
+ console.warn(
19119
+ `Arquivo pai n\xE3o carregado: ${db_info.db_id}. Pode ter sido deletado.`,
19120
+ result.status === "rejected" ? result.reason : (_b = result.value) == null ? void 0 : _b.error
19121
+ );
19122
+ }
19123
+ }
19124
+ if (type && type.toLowerCase() === "view") {
19125
+ parentData[scene_config] = {
19126
+ dataset_name: "Quests Internas (View)",
19127
+ nodes: sceneData.quest_nodes || [],
19128
+ links: sceneData.quest_links || []
19129
+ };
19130
+ }
19131
+ const allNodes = Object.values(parentData).flatMap((db) => db.nodes || []);
19132
+ const allLinks = Object.values(parentData).flatMap((db) => db.links || []);
19133
+ const parentNodeMap = new Map(allNodes.map((node) => [String(node.id), node]));
19134
+ const parentLinkMap = new Map(allLinks.map((link) => [`${link.source}-${link.target}`, link]));
19135
+ const validatedNodes = (sceneData.nodes || []).map((sceneNode) => {
19136
+ var _a2, _b2;
19137
+ const nodeTypes = Array.isArray(sceneNode.type) ? sceneNode.type : [sceneNode.type];
19138
+ if (nodeTypes.includes("quest")) {
19139
+ const dbQuestNode = (_b2 = (_a2 = parentData[scene_config]) == null ? void 0 : _a2.nodes) == null ? void 0 : _b2.find((qn) => String(qn.id) === String(sceneNode.id));
19140
+ if (dbQuestNode) {
19141
+ return { ...sceneNode, ...dbQuestNode };
19142
+ }
19143
+ return sceneNode;
19144
+ }
19145
+ const dbNode = parentNodeMap.get(String(sceneNode.id));
19146
+ if (dbNode) {
19147
+ return { ...sceneNode, ...dbNode };
19148
+ }
19149
+ return null;
19150
+ }).filter(Boolean);
19151
+ const validNodeIdsInScene = new Set(validatedNodes.map((node) => String(node.id)));
19152
+ const validatedLinks = (sceneData.links || []).filter((sceneLink) => {
19153
+ const linkExistsInParent = parentLinkMap.has(`${sceneLink.source}-${sceneLink.target}`);
19154
+ const nodesAreValidForScene = validNodeIdsInScene.has(String(sceneLink.source)) && validNodeIdsInScene.has(String(sceneLink.target));
19155
+ return linkExistsInParent && nodesAreValidForScene;
19156
+ }).map((sceneLink) => parentLinkMap.get(`${sceneLink.source}-${sceneLink.target}`));
19157
+ const validatedSceneData = {
19158
+ ...sceneData,
19159
+ nodes: validatedNodes,
19160
+ links: validatedLinks
19161
+ };
19162
+ const ancestryFilePaths = [];
19163
+ ancestryFilePaths.push({
19164
+ path: `x_view_ancestry/${owner_id}/${scene_config}`,
19165
+ source_id: scene_config,
19166
+ owner_id,
19167
+ isSceneFile: true
19168
+ });
19169
+ parentDbObjects.forEach((db_info) => {
19170
+ ancestryFilePaths.push({
19171
+ path: `x_view_ancestry/${db_info.owner_id}/${db_info.db_id}`,
19172
+ source_id: db_info.db_id,
19173
+ owner_id: db_info.owner_id,
19174
+ isSceneFile: false
19175
+ });
19176
+ });
19177
+ const ancestryPromises = ancestryFilePaths.map((file) => db_services.get_file(file.path));
19178
+ const ancestryResults = await Promise.allSettled(ancestryPromises);
19179
+ const ancestriesFromSceneFile = [];
19180
+ const ancestriesFromDbFiles = [];
19181
+ ancestryResults.forEach((result, index) => {
19182
+ var _a2;
19183
+ const fileInfo = ancestryFilePaths[index];
19184
+ if (result.status === "fulfilled" && ((_a2 = result.value) == null ? void 0 : _a2.success) && Array.isArray(result.value.data)) {
19185
+ const ancestriesFromFile = result.value.data.map((ancestry) => {
19186
+ const existingSource = ancestry._source_file_id;
19187
+ const existingOwner = ancestry._source_owner_id;
19188
+ const isImportedItem = existingSource && existingSource !== fileInfo.source_id;
19189
+ return {
19190
+ ...ancestry,
19191
+ _source_file_id: isImportedItem ? existingSource : fileInfo.source_id,
19192
+ _source_owner_id: isImportedItem ? existingOwner : fileInfo.owner_id
19193
+ };
19194
+ });
19195
+ if (fileInfo.isSceneFile) {
19196
+ ancestriesFromSceneFile.push(...ancestriesFromFile);
19197
+ } else {
19198
+ ancestriesFromDbFiles.push(...ancestriesFromFile);
19199
+ }
19200
+ }
19201
+ });
19202
+ const ancestryMap = /* @__PURE__ */ new Map();
19203
+ ancestriesFromDbFiles.forEach((anc) => ancestryMap.set(anc.ancestry_id, anc));
19204
+ ancestriesFromSceneFile.forEach((anc) => ancestryMap.set(anc.ancestry_id, anc));
19205
+ let ancestryData = Array.from(ancestryMap.values());
19206
+ const externalSourcesToRefresh = /* @__PURE__ */ new Map();
19207
+ ancestryData.forEach((anc) => {
19208
+ const targetSourceId = anc._source_file_id || anc._imported_from_view_id;
19209
+ const targetOwnerId = anc._source_owner_id || anc._imported_from_view_owner_id;
19210
+ if (targetSourceId && targetSourceId !== scene_config && targetOwnerId) {
19211
+ const isAlreadyLoadedAsParent = parentDbObjects.some((db) => String(db.db_id) === String(targetSourceId));
19212
+ if (!isAlreadyLoadedAsParent) {
19213
+ const key = `${targetOwnerId}/${targetSourceId}`;
19214
+ externalSourcesToRefresh.set(key, {
19215
+ owner_id: targetOwnerId,
19216
+ source_id: targetSourceId,
19217
+ key
19218
+ });
19219
+ }
19220
+ }
19221
+ });
19222
+ const successfullyFetchedSources = /* @__PURE__ */ new Set();
19223
+ const failedSources = /* @__PURE__ */ new Set();
19224
+ if (externalSourcesToRefresh.size > 0) {
19225
+ const refreshPromises = Array.from(externalSourcesToRefresh.values()).map(
19226
+ (src) => db_services.get_file(`x_view_ancestry/${src.owner_id}/${src.source_id}`).then((res) => ({ ...res, _meta_key: src.key }))
19227
+ );
19228
+ const refreshResults = await Promise.allSettled(refreshPromises);
19229
+ const freshAncestryMap = /* @__PURE__ */ new Map();
19230
+ refreshResults.forEach((result) => {
19231
+ var _a2, _b2;
19232
+ if (result.status === "fulfilled" && ((_a2 = result.value) == null ? void 0 : _a2.success) && Array.isArray(result.value.data)) {
19233
+ successfullyFetchedSources.add(result.value._meta_key);
19234
+ result.value.data.forEach((freshAnc) => {
19235
+ if (!freshAnc.is_private) {
19236
+ freshAncestryMap.set(String(freshAnc.ancestry_id), freshAnc);
19237
+ }
19238
+ });
19239
+ } else if (result.status === "fulfilled" && !((_b2 = result.value) == null ? void 0 : _b2.success)) {
19240
+ failedSources.add(result.value._meta_key);
19241
+ }
19242
+ });
19243
+ ancestryData = ancestryData.filter((localAnc) => {
19244
+ const targetSourceId = localAnc._source_file_id || localAnc._imported_from_view_id;
19245
+ const targetOwnerId = localAnc._source_owner_id || localAnc._imported_from_view_owner_id;
19246
+ if (!targetSourceId || targetSourceId === scene_config) return true;
19247
+ const key = `${targetOwnerId}/${targetSourceId}`;
19248
+ if (failedSources.has(key)) {
19249
+ return false;
19250
+ }
19251
+ if (externalSourcesToRefresh.has(key) && successfullyFetchedSources.has(key)) {
19252
+ const stillExists = freshAncestryMap.has(String(localAnc.ancestry_id));
19253
+ return stillExists;
19254
+ }
19255
+ return true;
19256
+ }).map((localAnc) => {
19257
+ const freshVersion = freshAncestryMap.get(String(localAnc.ancestry_id));
19258
+ if (freshVersion) {
19259
+ return {
19260
+ ...freshVersion,
19261
+ _imported_from_view_id: localAnc._imported_from_view_id,
19262
+ _imported_from_view_owner_id: localAnc._imported_from_view_owner_id,
19263
+ _origin_db_ids: localAnc._origin_db_ids,
19264
+ _source_file_id: localAnc._source_file_id,
19265
+ _source_owner_id: localAnc._source_owner_id
19266
+ };
19267
+ }
19268
+ return localAnc;
19269
+ });
19270
+ }
19271
+ const ancestryFilePath = `x_view_ancestry/${owner_id}/${scene_config}`;
19272
+ const parentNodeIds = new Set(allNodes.map((node) => String(node.id)));
19273
+ const pruneTree = (node) => {
19274
+ if (!node) return null;
19275
+ if (node.is_section) {
19276
+ const validChildren2 = (node.children || []).map((child) => pruneTree(child)).filter(Boolean);
19277
+ return { ...node, children: validChildren2 };
19278
+ }
19279
+ if (!parentNodeIds.has(String(node.node_id))) {
19280
+ return null;
19281
+ }
19282
+ const validChildren = (node.children || []).map((child) => pruneTree(child)).filter(Boolean);
19283
+ let validBranches = [];
19284
+ if (node.parallel_branches) {
19285
+ validBranches = node.parallel_branches.map((branch) => ({
19286
+ ...branch,
19287
+ tree: pruneTree(branch.tree)
19288
+ })).filter((b) => b.tree !== null);
19289
+ }
19290
+ return {
19291
+ ...node,
19292
+ children: validChildren,
19293
+ parallel_branches: validBranches
19294
+ };
19295
+ };
19296
+ const originalAncestryDataString = JSON.stringify(Array.from(ancestryMap.values()));
19297
+ const cleanedAncestryData = ancestryData.map((ancestry) => {
19298
+ if (!parentNodeIds.has(String(ancestry.ancestral_node))) return null;
19299
+ const cleanedTree = pruneTree(ancestry.tree);
19300
+ if (!cleanedTree) return null;
19301
+ let cleanedAbstraction = null;
19302
+ if (ancestry.abstraction_tree) {
19303
+ cleanedAbstraction = pruneTree(ancestry.abstraction_tree);
19304
+ }
19305
+ return {
19306
+ ...ancestry,
19307
+ tree: cleanedTree,
19308
+ abstraction_tree: cleanedAbstraction
19309
+ };
19310
+ }).filter(Boolean);
19311
+ if (JSON.stringify(cleanedAncestryData) !== originalAncestryDataString) {
19312
+ ancestryData = cleanedAncestryData;
19313
+ try {
19314
+ await db_services.save_file(ancestryFilePath, ancestryData);
19315
+ } catch (saveError) {
19316
+ console.error("Falha ao salvar o arquivo de ancestralidade da cena atualizado:", saveError);
19317
+ }
19318
+ }
19319
+ if (!isFullMember) {
19320
+ const requiredNodeIds = /* @__PURE__ */ new Set();
19321
+ const requiredAncestryIds = /* @__PURE__ */ new Set();
19322
+ if (focusNodeId) requiredNodeIds.add(String(focusNodeId));
19323
+ if (focusAncestryId) requiredAncestryIds.add(String(focusAncestryId));
19324
+ const allNodesFromDb = Object.values(parentData).flatMap((db) => db.nodes || []);
19325
+ const extractDependenciesFromText = (text) => {
19326
+ if (!text) return;
19327
+ const mentionRegex = /\[\[MENTION:node:([a-zA-Z0-9\-_]+)\]\]/g;
19328
+ const refRegex = /\[\[REF:(node|ancestry):([a-zA-Z0-9\-_]+):([a-zA-Z0-9\-_]+)\]\]/g;
19329
+ let match;
19330
+ while ((match = mentionRegex.exec(text)) !== null) {
19331
+ requiredNodeIds.add(match[1]);
19332
+ }
19333
+ while ((match = refRegex.exec(text)) !== null) {
19334
+ if (match[1] === "node") requiredNodeIds.add(match[2]);
19335
+ if (match[1] === "ancestry") requiredAncestryIds.add(match[2]);
19336
+ }
19337
+ };
19338
+ const extractTreeDependencies = (treeItem) => {
19339
+ var _a2;
19340
+ if (!treeItem) return;
19341
+ if (treeItem.node_id) requiredNodeIds.add(String(treeItem.node_id));
19342
+ if ((_a2 = treeItem.node) == null ? void 0 : _a2.id) requiredNodeIds.add(String(treeItem.node.id));
19343
+ if (treeItem.children) {
19344
+ treeItem.children.forEach(extractTreeDependencies);
19345
+ }
19346
+ if (treeItem.parallel_branches) {
19347
+ treeItem.parallel_branches.forEach((branch) => {
19348
+ if (branch.linked_ancestry_id) {
19349
+ requiredAncestryIds.add(String(branch.linked_ancestry_id));
19350
+ }
19351
+ if (branch.description) extractDependenciesFromText(branch.description);
19352
+ if (branch.description_sections) {
19353
+ branch.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19354
+ }
19355
+ extractTreeDependencies(branch.tree);
19356
+ });
19357
+ }
19358
+ };
19359
+ let previousSize;
19360
+ do {
19361
+ previousSize = requiredNodeIds.size + requiredAncestryIds.size;
19362
+ requiredAncestryIds.forEach((ancId) => {
19363
+ const anc = ancestryData.find((a) => String(a.ancestry_id) === ancId);
19364
+ if (anc && !anc.is_private) {
19365
+ if (anc.description) extractDependenciesFromText(anc.description);
19366
+ if (anc.description_sections) {
19367
+ anc.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19368
+ }
19369
+ extractTreeDependencies(anc.tree);
19370
+ extractTreeDependencies(anc.abstraction_tree);
19371
+ }
19372
+ });
19373
+ requiredNodeIds.forEach((nodeId) => {
19374
+ const node = allNodesFromDb.find((n) => String(n.id) === nodeId);
19375
+ if (node) {
19376
+ if (node.description) extractDependenciesFromText(node.description);
19377
+ if (node.description_sections) {
19378
+ node.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19379
+ }
19380
+ }
19381
+ });
19382
+ } while (requiredNodeIds.size + requiredAncestryIds.size > previousSize);
19383
+ const restrictedSceneData = { ...validatedSceneData, nodes: [], links: [] };
19384
+ const restrictedParentData = {};
19385
+ const restrictedAncestryData = ancestryData.filter((a) => requiredAncestryIds.has(String(a.ancestry_id)) && !a.is_private);
19386
+ for (const [dbId, dbContent] of Object.entries(parentData)) {
19387
+ const filteredNodes = (dbContent.nodes || []).filter((n) => requiredNodeIds.has(String(n.id)));
19388
+ if (filteredNodes.length > 0) {
19389
+ restrictedParentData[dbId] = { ...dbContent, nodes: filteredNodes, links: [] };
19390
+ }
19391
+ }
19392
+ if (focusNodeId) {
19393
+ const mainNode = allNodesFromDb.find((n) => String(n.id) === String(focusNodeId));
19394
+ if (mainNode) {
19395
+ restrictedSceneData.nodes = [mainNode];
19396
+ }
19397
+ }
19398
+ return {
19399
+ success: true,
19400
+ data: {
19401
+ scene: restrictedSceneData,
19402
+ parent: restrictedParentData,
19403
+ ancestry: restrictedAncestryData
19404
+ }
19405
+ };
19406
+ }
19407
+ return {
19408
+ success: true,
19409
+ data: { scene: validatedSceneData, parent: parentData, ancestry: ancestryData }
19410
+ };
19411
+ } catch (error) {
19412
+ console.error("Erro em get_scene_view_data_logic:", error);
19413
+ return { success: false, error: error.message };
19414
+ }
19415
+ }
19416
+ async function import_parent_file_modal_get_logic(db_services, session) {
19417
+ try {
19418
+ const call_firestore_get_x_view_data = await db_services.get_firestore_data(session);
19419
+ const x_view_databases = call_firestore_get_x_view_data.data.sub_collections.x_view_databases || [];
19420
+ const x_views = call_firestore_get_x_view_data.data.sub_collections.x_views || [];
19421
+ return {
19422
+ success: true,
19423
+ data: {
19424
+ databases: x_view_databases,
19425
+ views: x_views
19426
+ }
19427
+ };
19428
+ } catch (error) {
19429
+ console.error("catch (error) em import_parent_file_modal_get_logic:", error);
19430
+ return { success: false, error: error.message };
19431
+ }
19432
+ }
19433
+ async function get_single_parent_file_logic(db_services, file_id, session) {
19434
+ try {
19435
+ const call_GCP_cloud_storage_get_file = await db_services.get_file(`x_view_dbs/${session.user.id}/${file_id}`);
19436
+ return call_GCP_cloud_storage_get_file;
19437
+ } catch (error) {
19438
+ console.error("catch (error) em get_single_parent_file_logic:", error);
19439
+ return { success: false, error: error.message };
19440
+ }
19441
+ }
19442
+ async function x_view_add_new_parent_file_to_scene_at_firebase(file_id, session, new_parent, db_services) {
19443
+ try {
19444
+ const update_data = {
19445
+ scene_id: file_id,
19446
+ new_parent,
19447
+ session
19448
+ };
19449
+ const call_firestore_add_new_parent_file_to_scene_at_firebase = await db_services.add_new_parent_file_to_scene_at_firebase(update_data);
19450
+ return call_firestore_add_new_parent_file_to_scene_at_firebase;
19451
+ } catch (error) {
19452
+ console.error("catch (error) em get_single_parent_file_logic:", error);
19453
+ return { success: false, error: error.message };
19454
+ }
19455
+ }
19456
+ async function get_ancestry_file_logic(db_services, file_id, owner_id) {
19457
+ try {
19458
+ const path = `x_view_ancestry/${owner_id}/${file_id}`;
19459
+ const call_GCP_cloud_storage_get_file = await db_services.get_file(path);
19460
+ return call_GCP_cloud_storage_get_file;
19461
+ } catch (error) {
19462
+ console.error("catch (error) em get_ancestry_file_logic:", error);
19463
+ return { success: false, error: error.message };
19464
+ }
19465
+ }
19466
+ async function save_boards_logic(db_services, scene_id, type, boardItems, session, owner_id) {
19467
+ try {
19468
+ const targetOwnerId = owner_id || session.user.id;
19469
+ return await db_services.save_boards(session, scene_id, type, boardItems, targetOwnerId);
19470
+ } catch (error) {
19471
+ console.error("Erro em save_boards_logic:", error);
19472
+ return { success: false, error: error.message };
19473
+ }
19474
+ }
19475
+ async function get_boards_logic(db_services, scene_id, type, session, owner_id) {
19476
+ try {
19477
+ const targetOwnerId = owner_id || session.user.id;
19478
+ return await db_services.get_boards(session, scene_id, type, targetOwnerId);
19479
+ } catch (error) {
19480
+ console.error("Erro em get_boards_logic:", error);
19481
+ return { success: false, error: error.message };
19482
+ }
19483
+ }
19484
+ async function delete_uploaded_file_logic(db_services, fileUrl) {
19485
+ try {
19486
+ if (db_services.delete_public_file) {
19487
+ return await db_services.delete_public_file(fileUrl);
19488
+ }
19489
+ return { success: false, error: "Servi\xE7o de dele\xE7\xE3o n\xE3o configurado." };
19490
+ } catch (error) {
19491
+ console.error("Erro em delete_uploaded_file_logic:", error);
19492
+ return { success: false, error: error.message };
19493
+ }
19494
+ }
19495
+ async function get_view_members_logic(db_services, ownerId, type, itemId) {
19496
+ try {
19497
+ if (!db_services.get_view_members) {
19498
+ return { success: false, error: "Servi\xE7o de busca de membros n\xE3o configurado." };
19499
+ }
19500
+ return await db_services.get_view_members(ownerId, type, itemId);
19501
+ } catch (error) {
19502
+ console.error("Erro em get_view_members_logic:", error);
19503
+ return { success: false, error: error.message };
19504
+ }
19505
+ }
18804
19506
  export {
18805
- XViewScene
19507
+ XViewScene,
19508
+ delete_uploaded_file_logic,
19509
+ get_ancestry_file_logic,
19510
+ get_boards_logic,
19511
+ get_scene_view_data_logic,
19512
+ get_single_parent_file_logic,
19513
+ get_view_members_logic,
19514
+ import_parent_file_modal_get_logic,
19515
+ save_boards_logic,
19516
+ save_view_data_logic,
19517
+ x_view_add_new_parent_file_to_scene_at_firebase
18806
19518
  };