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

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 +1585 -851
  2. package/dist/index.mjs +1506 -791
  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(
13613
13871
  "button",
13614
13872
  {
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"
13873
+ onClick: navigateHome,
13874
+ className: `hover:text-white transition-colors flex items-center gap-1 ${navigationStack.length === 0 ? "text-indigo-400" : ""}`
13617
13875
  },
13618
- "Criar Primeiro Grupo"
13619
- )) : groups.map((group, index) => /* @__PURE__ */ React28.createElement(
13620
- GroupItem,
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",
13621
13879
  {
13622
- key: group.id,
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(
13885
+ "button",
13886
+ {
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"
13889
+ },
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,
13895
+ {
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,82 @@ 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 handleOpenReference = useCallback5(
18098
+ (referenceData) => {
18099
+ const { type, id } = referenceData;
18100
+ if (type === "node") {
18101
+ const targetNode = allAvailableNodes.find(
18102
+ (n) => String(n.id) === String(id)
18103
+ );
18104
+ if (targetNode) {
18105
+ setAncestryLinkDetails(null);
18106
+ setDetailsLink(null);
18107
+ setDetailsNode(targetNode);
18108
+ const sceneMesh = stateRef.current.nodeObjects[String(id)];
18109
+ if (sceneMesh) {
18110
+ tweenToTarget(sceneMesh);
18111
+ }
18112
+ } else {
18113
+ alert("Node original n\xE3o encontrado neste contexto.");
18114
+ }
18115
+ } else if (type === "ancestry") {
18116
+ const targetAncestry = allAvailableAncestries.find(
18117
+ (a) => String(a.ancestry_id) === String(id)
18118
+ );
18119
+ if (targetAncestry) {
18120
+ setDetailsNode(null);
18121
+ setDetailsLink(null);
18122
+ setAncestryLinkDetails(null);
18123
+ handleEditAncestry(targetAncestry);
18124
+ } else {
18125
+ alert("Ancestralidade original n\xE3o encontrada neste contexto.");
18126
+ }
18127
+ }
18128
+ },
18129
+ [
18130
+ allAvailableNodes,
18131
+ allAvailableAncestries,
18132
+ handleEditAncestry,
18133
+ tweenToTarget
18134
+ ]
18135
+ );
18136
+ const handleSelectAncestryFromBoard = useCallback5(
17806
18137
  (item) => {
17807
- setIsAncestryBoardOpen(false);
18138
+ setIsBoardsOpen(false);
17808
18139
  setIsSidebarOpen(false);
17809
- if (item.type === "ancestry" || !item.type) {
18140
+ if (item.type === "ancestry") {
17810
18141
  handleStartReadingAncestry(item);
17811
18142
  } 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
- }
18143
+ handleOpenReference({ type: "node", id: item.id });
17819
18144
  }
17820
18145
  },
17821
- [handleStartReadingAncestry, tweenToTarget]
18146
+ [handleStartReadingAncestry, handleOpenReference]
17822
18147
  );
17823
- const handleSaveAncestryBoard = useCallback4(
17824
- async (groups) => {
18148
+ const handleSaveBoards = useCallback5(
18149
+ async (boardItems2) => {
17825
18150
  if (!sceneConfigId || !viewParams || !session) return;
17826
18151
  const sceneType = (viewParams.type || "").toLowerCase().includes("database") ? "database" : "view";
17827
- await save_ancestry_board_action(
18152
+ await save_boards_action(
17828
18153
  sceneConfigId,
17829
18154
  sceneType,
17830
- groups,
18155
+ boardItems2,
17831
18156
  session,
17832
18157
  ownerId
17833
18158
  );
17834
18159
  },
17835
- [sceneConfigId, viewParams, session, save_ancestry_board_action, ownerId]
18160
+ [sceneConfigId, viewParams, session, save_boards_action, ownerId]
17836
18161
  );
17837
18162
  const existingNodeTypes = useMemo13(() => {
17838
18163
  if (!parentDataRef.current) {
@@ -17847,27 +18172,21 @@ function XViewScene({
17847
18172
  return [...new Set(allTypes)];
17848
18173
  }, [parentDataRef.current, sceneVersion]);
17849
18174
  const searchableDbNodes = useMemo13(() => {
17850
- var _a2;
17851
18175
  if (!parentDataRef.current) {
17852
18176
  return [];
17853
18177
  }
17854
- const datasetsPool = Object.entries(parentDataRef.current).flatMap(([dbId, fileData]) => {
18178
+ return Object.entries(parentDataRef.current).flatMap(([dbId, fileData]) => {
17855
18179
  const datasetName = fileData.dataset_name || `Dataset #${dbId.substring(0, 6)}`;
17856
18180
  return (fileData.nodes || []).map((node) => ({
17857
18181
  ...node,
17858
18182
  dataset_name: datasetName
17859
18183
  }));
18184
+ }).filter((node) => {
18185
+ var _a2;
18186
+ return !((_a2 = node.version_node) == null ? void 0 : _a2.is_version);
17860
18187
  });
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(
18188
+ }, [parentDataRef.current, sceneVersion]);
18189
+ const handleAddExistingNode = useCallback5(
17871
18190
  (nodeId) => {
17872
18191
  return userActionHandlers.handleAddExistingNodeById(
17873
18192
  actionHandlerContext,
@@ -17876,7 +18195,7 @@ function XViewScene({
17876
18195
  },
17877
18196
  [actionHandlerContext]
17878
18197
  );
17879
- const handleUpdateMilestones = useCallback4(
18198
+ const handleUpdateMilestones = useCallback5(
17880
18199
  async (newMilestones) => {
17881
18200
  var _a2, _b2, _c2;
17882
18201
  if (!sceneDataRef.current) return;
@@ -17922,7 +18241,7 @@ function XViewScene({
17922
18241
  viewParams == null ? void 0 : viewParams.type
17923
18242
  ]
17924
18243
  );
17925
- const handleSaveCurrentView = useCallback4(async () => {
18244
+ const handleSaveCurrentView = useCallback5(async () => {
17926
18245
  var _a2, _b2, _c2;
17927
18246
  const { nodeObjects, allLinks } = stateRef.current;
17928
18247
  if (!nodeObjects || !allLinks || !sceneSaveUrl || !parentDataRef.current) {
@@ -17965,61 +18284,13 @@ function XViewScene({
17965
18284
  console.error("Erro na chamada de save_view_data:", error);
17966
18285
  }
17967
18286
  }, [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(() => {
18287
+ const handleToggleAncestryAddMode = useCallback5(() => {
18017
18288
  setAncestryMode((prev) => ({
18018
18289
  ...prev,
18019
18290
  isAddingNodes: !prev.isAddingNodes
18020
18291
  }));
18021
18292
  }, []);
18022
- const handleFocusNode = useCallback4(
18293
+ const handleFocusNode = useCallback5(
18023
18294
  (nodeData) => {
18024
18295
  var _a2;
18025
18296
  if (!nodeData) return;
@@ -18045,9 +18316,9 @@ function XViewScene({
18045
18316
  };
18046
18317
  });
18047
18318
  }, [sceneVersion, isInitialized]);
18048
- const sourceNodeDatasetId = creationMode.sourceNodeData ? (_c = stateRef.current.nodeIdToParentFileMap.get(
18319
+ const sourceNodeDatasetId = creationMode.sourceNodeData ? (_b = stateRef.current.nodeIdToParentFileMap.get(
18049
18320
  String(creationMode.sourceNodeData.id)
18050
- )) == null ? void 0 : _c.parentFileId : null;
18321
+ )) == null ? void 0 : _b.parentFileId : null;
18051
18322
  const detailsNodeDatasetInfo = detailsNode ? stateRef.current.nodeIdToParentFileMap.get(String(detailsNode.id)) : null;
18052
18323
  useEffect24(() => {
18053
18324
  if (isInitialized && focusNodeId && !hasFocusedInitial) {
@@ -18137,7 +18408,7 @@ function XViewScene({
18137
18408
  setRelationshipMenu((prev) => ({ ...prev, visible: false }));
18138
18409
  }
18139
18410
  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;
18411
+ 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
18412
  if (isUiClear) {
18142
18413
  const isView = ((_c2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _c2.toLowerCase()) === "view";
18143
18414
  if (!isView) return;
@@ -18206,7 +18477,7 @@ function XViewScene({
18206
18477
  relationshipMenu.visible,
18207
18478
  readingMode.isActive,
18208
18479
  isImportModalOpen,
18209
- isAncestryBoardOpen,
18480
+ isBoardsOpen,
18210
18481
  isSidebarOpen,
18211
18482
  detailsNode,
18212
18483
  detailsLink,
@@ -18293,7 +18564,7 @@ function XViewScene({
18293
18564
  importSuccessMessage,
18294
18565
  onActivateTimeline: handleActivateTimeline,
18295
18566
  onActivateQuestLens: handleActivateQuestLens,
18296
- onOpenAncestryBoard: handleOpenAncestryBoard,
18567
+ onOpenBoards: handleOpenBoards,
18297
18568
  viewType: viewParams == null ? void 0 : viewParams.type,
18298
18569
  viewName: viewParams == null ? void 0 : viewParams.name,
18299
18570
  onNavigateBack: handleNavigateBack,
@@ -18301,7 +18572,7 @@ function XViewScene({
18301
18572
  onToggle: setIsSidebarOpen,
18302
18573
  userRole: userPermissionRole,
18303
18574
  viewMembers,
18304
- milestones: ((_d = sceneDataRef.current) == null ? void 0 : _d.milestones) || [],
18575
+ milestones: ((_c = sceneDataRef.current) == null ? void 0 : _c.milestones) || [],
18305
18576
  onOpenMilestoneManager: () => setIsMilestoneManagerOpen(true)
18306
18577
  }
18307
18578
  ),
@@ -18326,8 +18597,8 @@ function XViewScene({
18326
18597
  },
18327
18598
  refEl: formRef,
18328
18599
  existingTypes: existingNodeTypes,
18329
- initialColor: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.color,
18330
- sourceTypes: (_f = creationMode.sourceNodeData) == null ? void 0 : _f.type,
18600
+ initialColor: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.color,
18601
+ sourceTypes: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.type,
18331
18602
  onIntensityChange: handleGhostNodeIntensityChange,
18332
18603
  onUploadFile: upload_file_action,
18333
18604
  availableDatasets,
@@ -18356,8 +18627,8 @@ function XViewScene({
18356
18627
  transition: "opacity 200ms ease-out"
18357
18628
  },
18358
18629
  refEl: formRef,
18359
- fixedType: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.type,
18360
- fixedColor: (_h = versionMode.sourceNodeData) == null ? void 0 : _h.color,
18630
+ fixedType: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.type,
18631
+ fixedColor: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.color,
18361
18632
  onUploadFile: upload_file_action,
18362
18633
  availableNodes: allAvailableNodes,
18363
18634
  availableAncestries: allAvailableAncestries
@@ -18385,9 +18656,9 @@ function XViewScene({
18385
18656
  availableNodes: allAvailableNodes,
18386
18657
  availableAncestries: allAvailableAncestries,
18387
18658
  viewName: viewParams == null ? void 0 : viewParams.name,
18388
- questCounter: ((_i = sceneDataRef.current) == null ? void 0 : _i.quest_counter) || 1,
18659
+ questCounter: ((_h = sceneDataRef.current) == null ? void 0 : _h.quest_counter) || 1,
18389
18660
  viewMembers,
18390
- milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
18661
+ milestones: ((_i = sceneDataRef.current) == null ? void 0 : _i.milestones) || [],
18391
18662
  onUpdateMilestones: handleUpdateMilestones
18392
18663
  }
18393
18664
  ),
@@ -18522,7 +18793,7 @@ function XViewScene({
18522
18793
  currentDatasetName: detailsNodeDatasetInfo == null ? void 0 : detailsNodeDatasetInfo.datasetName,
18523
18794
  viewMembers,
18524
18795
  currentUser: session == null ? void 0 : session.user,
18525
- milestones: ((_k = sceneDataRef.current) == null ? void 0 : _k.milestones) || [],
18796
+ milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
18526
18797
  onUpdateMilestones: handleUpdateMilestones
18527
18798
  }
18528
18799
  ),
@@ -18689,15 +18960,15 @@ function XViewScene({
18689
18960
  }
18690
18961
  ),
18691
18962
  /* @__PURE__ */ React30.createElement(
18692
- AncestryBoard,
18963
+ Boards,
18693
18964
  {
18694
- isOpen: isAncestryBoardOpen,
18695
- onClose: () => setIsAncestryBoardOpen(false),
18965
+ isOpen: isBoardsOpen,
18966
+ onClose: () => setIsBoardsOpen(false),
18696
18967
  onSelect: handleSelectAncestryFromBoard,
18697
18968
  availableAncestries: allAvailableAncestries,
18698
18969
  availableNodes: allAvailableNodes,
18699
- initialGroups: ancestryBoardData,
18700
- onSave: handleSaveAncestryBoard,
18970
+ initialBoardItems: boardItems,
18971
+ onSave: handleSaveBoards,
18701
18972
  userRole: userPermissionRole
18702
18973
  }
18703
18974
  ),
@@ -18706,7 +18977,7 @@ function XViewScene({
18706
18977
  {
18707
18978
  isOpen: isMilestoneManagerOpen,
18708
18979
  onClose: () => setIsMilestoneManagerOpen(false),
18709
- milestones: ((_l = sceneDataRef.current) == null ? void 0 : _l.milestones) || [],
18980
+ milestones: ((_k = sceneDataRef.current) == null ? void 0 : _k.milestones) || [],
18710
18981
  onUpdateMilestones: handleUpdateMilestones
18711
18982
  }
18712
18983
  ),
@@ -18717,7 +18988,7 @@ function XViewScene({
18717
18988
  onClose: () => setIsImportModalOpen(false),
18718
18989
  onConfirm: handleConfirmImport,
18719
18990
  session,
18720
- parentDbs: ((_m = sceneDataRef.current) == null ? void 0 : _m.parent_dbs) || [],
18991
+ parentDbs: ((_l = sceneDataRef.current) == null ? void 0 : _l.parent_dbs) || [],
18721
18992
  onFetchAvailableFiles: import_parent_file_modal_get,
18722
18993
  currentViewName: viewParams == null ? void 0 : viewParams.name,
18723
18994
  currentAncestries: ancestryDataRef.current || []
@@ -18801,6 +19072,450 @@ function XViewScene({
18801
19072
  )
18802
19073
  );
18803
19074
  }
19075
+
19076
+ // src/x_view_server_logic.js
19077
+ async function save_view_data_logic(db_services, filename, data) {
19078
+ try {
19079
+ const saveResult = await db_services.save_file(filename, data);
19080
+ return saveResult;
19081
+ } catch (error) {
19082
+ console.error("catch (error) em save_view_data_logic:", error);
19083
+ return { success: false, error: error.message };
19084
+ }
19085
+ }
19086
+ async function get_scene_view_data_logic(db_services, scene_config, owner_id, type, session, focusNodeId = null, focusAncestryId = null) {
19087
+ var _a, _b;
19088
+ try {
19089
+ if (!session || !session.user) {
19090
+ return { success: false, error: "Usu\xE1rio n\xE3o autenticado." };
19091
+ }
19092
+ const membershipCheck = await db_services.check_membership(owner_id, type, scene_config, session.user.id);
19093
+ const isFullMember = membershipCheck.success && membershipCheck.isValidMember;
19094
+ if (!isFullMember && !focusNodeId && !focusAncestryId) {
19095
+ return { success: false, error: "Acesso Negado: Voc\xEA n\xE3o possui permiss\xE3o para acessar este cen\xE1rio." };
19096
+ }
19097
+ const sceneResponse = await db_services.get_file(`x_view_scenes/${owner_id}/${scene_config}`);
19098
+ if (!sceneResponse || !sceneResponse.success) {
19099
+ console.error("Falha ao buscar dados da cena:", sceneResponse == null ? void 0 : sceneResponse.error);
19100
+ return { success: false, error: `N\xE3o foi poss\xEDvel carregar o arquivo da cena: ${scene_config}` };
19101
+ }
19102
+ const sceneData = sceneResponse.data;
19103
+ const parentDbObjects = sceneData.parent_dbs || [];
19104
+ if (type && type.toLowerCase().includes("database")) {
19105
+ const selfExists = parentDbObjects.some((db) => String(db.db_id) === String(scene_config));
19106
+ if (!selfExists) {
19107
+ parentDbObjects.push({ db_id: scene_config, owner_id });
19108
+ }
19109
+ }
19110
+ const parentResponsesPromises = parentDbObjects.map(
19111
+ (db_info) => db_services.get_file(`x_view_dbs/${db_info.owner_id}/${db_info.db_id}`)
19112
+ );
19113
+ const parentResponsesResults = await Promise.allSettled(parentResponsesPromises);
19114
+ const parentData = {};
19115
+ for (let i = 0; i < parentResponsesResults.length; i++) {
19116
+ const result = parentResponsesResults[i];
19117
+ const db_info = parentDbObjects[i];
19118
+ if (result.status === "fulfilled" && ((_a = result.value) == null ? void 0 : _a.success)) {
19119
+ parentData[db_info.db_id] = result.value.data;
19120
+ } else {
19121
+ console.warn(
19122
+ `Arquivo pai n\xE3o carregado: ${db_info.db_id}. Pode ter sido deletado.`,
19123
+ result.status === "rejected" ? result.reason : (_b = result.value) == null ? void 0 : _b.error
19124
+ );
19125
+ }
19126
+ }
19127
+ if (type && type.toLowerCase() === "view") {
19128
+ parentData[scene_config] = {
19129
+ dataset_name: "Quests Internas (View)",
19130
+ nodes: sceneData.quest_nodes || [],
19131
+ links: sceneData.quest_links || []
19132
+ };
19133
+ }
19134
+ const allNodes = Object.values(parentData).flatMap((db) => db.nodes || []);
19135
+ const allLinks = Object.values(parentData).flatMap((db) => db.links || []);
19136
+ const parentNodeMap = new Map(allNodes.map((node) => [String(node.id), node]));
19137
+ const parentLinkMap = new Map(allLinks.map((link) => [`${link.source}-${link.target}`, link]));
19138
+ const validatedNodes = (sceneData.nodes || []).map((sceneNode) => {
19139
+ var _a2, _b2;
19140
+ const nodeTypes = Array.isArray(sceneNode.type) ? sceneNode.type : [sceneNode.type];
19141
+ if (nodeTypes.includes("quest")) {
19142
+ const dbQuestNode = (_b2 = (_a2 = parentData[scene_config]) == null ? void 0 : _a2.nodes) == null ? void 0 : _b2.find((qn) => String(qn.id) === String(sceneNode.id));
19143
+ if (dbQuestNode) {
19144
+ return { ...sceneNode, ...dbQuestNode };
19145
+ }
19146
+ return sceneNode;
19147
+ }
19148
+ const dbNode = parentNodeMap.get(String(sceneNode.id));
19149
+ if (dbNode) {
19150
+ return { ...sceneNode, ...dbNode };
19151
+ }
19152
+ return null;
19153
+ }).filter(Boolean);
19154
+ const validNodeIdsInScene = new Set(validatedNodes.map((node) => String(node.id)));
19155
+ const validatedLinks = (sceneData.links || []).filter((sceneLink) => {
19156
+ const linkExistsInParent = parentLinkMap.has(`${sceneLink.source}-${sceneLink.target}`);
19157
+ const nodesAreValidForScene = validNodeIdsInScene.has(String(sceneLink.source)) && validNodeIdsInScene.has(String(sceneLink.target));
19158
+ return linkExistsInParent && nodesAreValidForScene;
19159
+ }).map((sceneLink) => parentLinkMap.get(`${sceneLink.source}-${sceneLink.target}`));
19160
+ const validatedSceneData = {
19161
+ ...sceneData,
19162
+ nodes: validatedNodes,
19163
+ links: validatedLinks
19164
+ };
19165
+ const ancestryFilePaths = [];
19166
+ ancestryFilePaths.push({
19167
+ path: `x_view_ancestry/${owner_id}/${scene_config}`,
19168
+ source_id: scene_config,
19169
+ owner_id,
19170
+ isSceneFile: true
19171
+ });
19172
+ parentDbObjects.forEach((db_info) => {
19173
+ ancestryFilePaths.push({
19174
+ path: `x_view_ancestry/${db_info.owner_id}/${db_info.db_id}`,
19175
+ source_id: db_info.db_id,
19176
+ owner_id: db_info.owner_id,
19177
+ isSceneFile: false
19178
+ });
19179
+ });
19180
+ const ancestryPromises = ancestryFilePaths.map((file) => db_services.get_file(file.path));
19181
+ const ancestryResults = await Promise.allSettled(ancestryPromises);
19182
+ const ancestriesFromSceneFile = [];
19183
+ const ancestriesFromDbFiles = [];
19184
+ ancestryResults.forEach((result, index) => {
19185
+ var _a2;
19186
+ const fileInfo = ancestryFilePaths[index];
19187
+ if (result.status === "fulfilled" && ((_a2 = result.value) == null ? void 0 : _a2.success) && Array.isArray(result.value.data)) {
19188
+ const ancestriesFromFile = result.value.data.map((ancestry) => {
19189
+ const existingSource = ancestry._source_file_id;
19190
+ const existingOwner = ancestry._source_owner_id;
19191
+ const isImportedItem = existingSource && existingSource !== fileInfo.source_id;
19192
+ return {
19193
+ ...ancestry,
19194
+ _source_file_id: isImportedItem ? existingSource : fileInfo.source_id,
19195
+ _source_owner_id: isImportedItem ? existingOwner : fileInfo.owner_id
19196
+ };
19197
+ });
19198
+ if (fileInfo.isSceneFile) {
19199
+ ancestriesFromSceneFile.push(...ancestriesFromFile);
19200
+ } else {
19201
+ ancestriesFromDbFiles.push(...ancestriesFromFile);
19202
+ }
19203
+ }
19204
+ });
19205
+ const ancestryMap = /* @__PURE__ */ new Map();
19206
+ ancestriesFromDbFiles.forEach((anc) => ancestryMap.set(anc.ancestry_id, anc));
19207
+ ancestriesFromSceneFile.forEach((anc) => ancestryMap.set(anc.ancestry_id, anc));
19208
+ let ancestryData = Array.from(ancestryMap.values());
19209
+ const externalSourcesToRefresh = /* @__PURE__ */ new Map();
19210
+ ancestryData.forEach((anc) => {
19211
+ const targetSourceId = anc._source_file_id || anc._imported_from_view_id;
19212
+ const targetOwnerId = anc._source_owner_id || anc._imported_from_view_owner_id;
19213
+ if (targetSourceId && targetSourceId !== scene_config && targetOwnerId) {
19214
+ const isAlreadyLoadedAsParent = parentDbObjects.some((db) => String(db.db_id) === String(targetSourceId));
19215
+ if (!isAlreadyLoadedAsParent) {
19216
+ const key = `${targetOwnerId}/${targetSourceId}`;
19217
+ externalSourcesToRefresh.set(key, {
19218
+ owner_id: targetOwnerId,
19219
+ source_id: targetSourceId,
19220
+ key
19221
+ });
19222
+ }
19223
+ }
19224
+ });
19225
+ const successfullyFetchedSources = /* @__PURE__ */ new Set();
19226
+ const failedSources = /* @__PURE__ */ new Set();
19227
+ if (externalSourcesToRefresh.size > 0) {
19228
+ const refreshPromises = Array.from(externalSourcesToRefresh.values()).map(
19229
+ (src) => db_services.get_file(`x_view_ancestry/${src.owner_id}/${src.source_id}`).then((res) => ({ ...res, _meta_key: src.key }))
19230
+ );
19231
+ const refreshResults = await Promise.allSettled(refreshPromises);
19232
+ const freshAncestryMap = /* @__PURE__ */ new Map();
19233
+ refreshResults.forEach((result) => {
19234
+ var _a2, _b2;
19235
+ if (result.status === "fulfilled" && ((_a2 = result.value) == null ? void 0 : _a2.success) && Array.isArray(result.value.data)) {
19236
+ successfullyFetchedSources.add(result.value._meta_key);
19237
+ result.value.data.forEach((freshAnc) => {
19238
+ if (!freshAnc.is_private) {
19239
+ freshAncestryMap.set(String(freshAnc.ancestry_id), freshAnc);
19240
+ }
19241
+ });
19242
+ } else if (result.status === "fulfilled" && !((_b2 = result.value) == null ? void 0 : _b2.success)) {
19243
+ failedSources.add(result.value._meta_key);
19244
+ }
19245
+ });
19246
+ ancestryData = ancestryData.filter((localAnc) => {
19247
+ const targetSourceId = localAnc._source_file_id || localAnc._imported_from_view_id;
19248
+ const targetOwnerId = localAnc._source_owner_id || localAnc._imported_from_view_owner_id;
19249
+ if (!targetSourceId || targetSourceId === scene_config) return true;
19250
+ const key = `${targetOwnerId}/${targetSourceId}`;
19251
+ if (failedSources.has(key)) {
19252
+ return false;
19253
+ }
19254
+ if (externalSourcesToRefresh.has(key) && successfullyFetchedSources.has(key)) {
19255
+ const stillExists = freshAncestryMap.has(String(localAnc.ancestry_id));
19256
+ return stillExists;
19257
+ }
19258
+ return true;
19259
+ }).map((localAnc) => {
19260
+ const freshVersion = freshAncestryMap.get(String(localAnc.ancestry_id));
19261
+ if (freshVersion) {
19262
+ return {
19263
+ ...freshVersion,
19264
+ _imported_from_view_id: localAnc._imported_from_view_id,
19265
+ _imported_from_view_owner_id: localAnc._imported_from_view_owner_id,
19266
+ _origin_db_ids: localAnc._origin_db_ids,
19267
+ _source_file_id: localAnc._source_file_id,
19268
+ _source_owner_id: localAnc._source_owner_id
19269
+ };
19270
+ }
19271
+ return localAnc;
19272
+ });
19273
+ }
19274
+ const ancestryFilePath = `x_view_ancestry/${owner_id}/${scene_config}`;
19275
+ const parentNodeIds = new Set(allNodes.map((node) => String(node.id)));
19276
+ const pruneTree = (node) => {
19277
+ if (!node) return null;
19278
+ if (node.is_section) {
19279
+ const validChildren2 = (node.children || []).map((child) => pruneTree(child)).filter(Boolean);
19280
+ return { ...node, children: validChildren2 };
19281
+ }
19282
+ if (!parentNodeIds.has(String(node.node_id))) {
19283
+ return null;
19284
+ }
19285
+ const validChildren = (node.children || []).map((child) => pruneTree(child)).filter(Boolean);
19286
+ let validBranches = [];
19287
+ if (node.parallel_branches) {
19288
+ validBranches = node.parallel_branches.map((branch) => ({
19289
+ ...branch,
19290
+ tree: pruneTree(branch.tree)
19291
+ })).filter((b) => b.tree !== null);
19292
+ }
19293
+ return {
19294
+ ...node,
19295
+ children: validChildren,
19296
+ parallel_branches: validBranches
19297
+ };
19298
+ };
19299
+ const originalAncestryDataString = JSON.stringify(Array.from(ancestryMap.values()));
19300
+ const cleanedAncestryData = ancestryData.map((ancestry) => {
19301
+ if (!parentNodeIds.has(String(ancestry.ancestral_node))) return null;
19302
+ const cleanedTree = pruneTree(ancestry.tree);
19303
+ if (!cleanedTree) return null;
19304
+ let cleanedAbstraction = null;
19305
+ if (ancestry.abstraction_tree) {
19306
+ cleanedAbstraction = pruneTree(ancestry.abstraction_tree);
19307
+ }
19308
+ return {
19309
+ ...ancestry,
19310
+ tree: cleanedTree,
19311
+ abstraction_tree: cleanedAbstraction
19312
+ };
19313
+ }).filter(Boolean);
19314
+ if (JSON.stringify(cleanedAncestryData) !== originalAncestryDataString) {
19315
+ ancestryData = cleanedAncestryData;
19316
+ try {
19317
+ await db_services.save_file(ancestryFilePath, ancestryData);
19318
+ } catch (saveError) {
19319
+ console.error("Falha ao salvar o arquivo de ancestralidade da cena atualizado:", saveError);
19320
+ }
19321
+ }
19322
+ if (!isFullMember) {
19323
+ const requiredNodeIds = /* @__PURE__ */ new Set();
19324
+ const requiredAncestryIds = /* @__PURE__ */ new Set();
19325
+ if (focusNodeId) requiredNodeIds.add(String(focusNodeId));
19326
+ if (focusAncestryId) requiredAncestryIds.add(String(focusAncestryId));
19327
+ const allNodesFromDb = Object.values(parentData).flatMap((db) => db.nodes || []);
19328
+ const extractDependenciesFromText = (text) => {
19329
+ if (!text) return;
19330
+ const mentionRegex = /\[\[MENTION:node:([a-zA-Z0-9\-_]+)\]\]/g;
19331
+ const refRegex = /\[\[REF:(node|ancestry):([a-zA-Z0-9\-_]+):([a-zA-Z0-9\-_]+)\]\]/g;
19332
+ let match;
19333
+ while ((match = mentionRegex.exec(text)) !== null) {
19334
+ requiredNodeIds.add(match[1]);
19335
+ }
19336
+ while ((match = refRegex.exec(text)) !== null) {
19337
+ if (match[1] === "node") requiredNodeIds.add(match[2]);
19338
+ if (match[1] === "ancestry") requiredAncestryIds.add(match[2]);
19339
+ }
19340
+ };
19341
+ const extractTreeDependencies = (treeItem) => {
19342
+ var _a2;
19343
+ if (!treeItem) return;
19344
+ if (treeItem.node_id) requiredNodeIds.add(String(treeItem.node_id));
19345
+ if ((_a2 = treeItem.node) == null ? void 0 : _a2.id) requiredNodeIds.add(String(treeItem.node.id));
19346
+ if (treeItem.children) {
19347
+ treeItem.children.forEach(extractTreeDependencies);
19348
+ }
19349
+ if (treeItem.parallel_branches) {
19350
+ treeItem.parallel_branches.forEach((branch) => {
19351
+ if (branch.linked_ancestry_id) {
19352
+ requiredAncestryIds.add(String(branch.linked_ancestry_id));
19353
+ }
19354
+ if (branch.description) extractDependenciesFromText(branch.description);
19355
+ if (branch.description_sections) {
19356
+ branch.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19357
+ }
19358
+ extractTreeDependencies(branch.tree);
19359
+ });
19360
+ }
19361
+ };
19362
+ let previousSize;
19363
+ do {
19364
+ previousSize = requiredNodeIds.size + requiredAncestryIds.size;
19365
+ requiredAncestryIds.forEach((ancId) => {
19366
+ const anc = ancestryData.find((a) => String(a.ancestry_id) === ancId);
19367
+ if (anc && !anc.is_private) {
19368
+ if (anc.description) extractDependenciesFromText(anc.description);
19369
+ if (anc.description_sections) {
19370
+ anc.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19371
+ }
19372
+ extractTreeDependencies(anc.tree);
19373
+ extractTreeDependencies(anc.abstraction_tree);
19374
+ }
19375
+ });
19376
+ requiredNodeIds.forEach((nodeId) => {
19377
+ const node = allNodesFromDb.find((n) => String(n.id) === nodeId);
19378
+ if (node) {
19379
+ if (node.description) extractDependenciesFromText(node.description);
19380
+ if (node.description_sections) {
19381
+ node.description_sections.forEach((s) => extractDependenciesFromText(s.content));
19382
+ }
19383
+ }
19384
+ });
19385
+ } while (requiredNodeIds.size + requiredAncestryIds.size > previousSize);
19386
+ const restrictedSceneData = { ...validatedSceneData, nodes: [], links: [] };
19387
+ const restrictedParentData = {};
19388
+ const restrictedAncestryData = ancestryData.filter((a) => requiredAncestryIds.has(String(a.ancestry_id)) && !a.is_private);
19389
+ for (const [dbId, dbContent] of Object.entries(parentData)) {
19390
+ const filteredNodes = (dbContent.nodes || []).filter((n) => requiredNodeIds.has(String(n.id)));
19391
+ if (filteredNodes.length > 0) {
19392
+ restrictedParentData[dbId] = { ...dbContent, nodes: filteredNodes, links: [] };
19393
+ }
19394
+ }
19395
+ if (focusNodeId) {
19396
+ const mainNode = allNodesFromDb.find((n) => String(n.id) === String(focusNodeId));
19397
+ if (mainNode) {
19398
+ restrictedSceneData.nodes = [mainNode];
19399
+ }
19400
+ }
19401
+ return {
19402
+ success: true,
19403
+ data: {
19404
+ scene: restrictedSceneData,
19405
+ parent: restrictedParentData,
19406
+ ancestry: restrictedAncestryData
19407
+ }
19408
+ };
19409
+ }
19410
+ return {
19411
+ success: true,
19412
+ data: { scene: validatedSceneData, parent: parentData, ancestry: ancestryData }
19413
+ };
19414
+ } catch (error) {
19415
+ console.error("Erro em get_scene_view_data_logic:", error);
19416
+ return { success: false, error: error.message };
19417
+ }
19418
+ }
19419
+ async function import_parent_file_modal_get_logic(db_services, session) {
19420
+ try {
19421
+ const call_firestore_get_x_view_data = await db_services.get_firestore_data(session);
19422
+ const x_view_databases = call_firestore_get_x_view_data.data.sub_collections.x_view_databases || [];
19423
+ const x_views = call_firestore_get_x_view_data.data.sub_collections.x_views || [];
19424
+ return {
19425
+ success: true,
19426
+ data: {
19427
+ databases: x_view_databases,
19428
+ views: x_views
19429
+ }
19430
+ };
19431
+ } catch (error) {
19432
+ console.error("catch (error) em import_parent_file_modal_get_logic:", error);
19433
+ return { success: false, error: error.message };
19434
+ }
19435
+ }
19436
+ async function get_single_parent_file_logic(db_services, file_id, session) {
19437
+ try {
19438
+ const call_GCP_cloud_storage_get_file = await db_services.get_file(`x_view_dbs/${session.user.id}/${file_id}`);
19439
+ return call_GCP_cloud_storage_get_file;
19440
+ } catch (error) {
19441
+ console.error("catch (error) em get_single_parent_file_logic:", error);
19442
+ return { success: false, error: error.message };
19443
+ }
19444
+ }
19445
+ async function x_view_add_new_parent_file_to_scene_at_firebase(file_id, session, new_parent, db_services) {
19446
+ try {
19447
+ const update_data = {
19448
+ scene_id: file_id,
19449
+ new_parent,
19450
+ session
19451
+ };
19452
+ const call_firestore_add_new_parent_file_to_scene_at_firebase = await db_services.add_new_parent_file_to_scene_at_firebase(update_data);
19453
+ return call_firestore_add_new_parent_file_to_scene_at_firebase;
19454
+ } catch (error) {
19455
+ console.error("catch (error) em get_single_parent_file_logic:", error);
19456
+ return { success: false, error: error.message };
19457
+ }
19458
+ }
19459
+ async function get_ancestry_file_logic(db_services, file_id, owner_id) {
19460
+ try {
19461
+ const path = `x_view_ancestry/${owner_id}/${file_id}`;
19462
+ const call_GCP_cloud_storage_get_file = await db_services.get_file(path);
19463
+ return call_GCP_cloud_storage_get_file;
19464
+ } catch (error) {
19465
+ console.error("catch (error) em get_ancestry_file_logic:", error);
19466
+ return { success: false, error: error.message };
19467
+ }
19468
+ }
19469
+ async function save_boards_logic(db_services, scene_id, type, boardItems, session, owner_id) {
19470
+ try {
19471
+ const targetOwnerId = owner_id || session.user.id;
19472
+ return await db_services.save_boards(session, scene_id, type, boardItems, targetOwnerId);
19473
+ } catch (error) {
19474
+ console.error("Erro em save_boards_logic:", error);
19475
+ return { success: false, error: error.message };
19476
+ }
19477
+ }
19478
+ async function get_boards_logic(db_services, scene_id, type, session, owner_id) {
19479
+ try {
19480
+ const targetOwnerId = owner_id || session.user.id;
19481
+ return await db_services.get_boards(session, scene_id, type, targetOwnerId);
19482
+ } catch (error) {
19483
+ console.error("Erro em get_boards_logic:", error);
19484
+ return { success: false, error: error.message };
19485
+ }
19486
+ }
19487
+ async function delete_uploaded_file_logic(db_services, fileUrl) {
19488
+ try {
19489
+ if (db_services.delete_public_file) {
19490
+ return await db_services.delete_public_file(fileUrl);
19491
+ }
19492
+ return { success: false, error: "Servi\xE7o de dele\xE7\xE3o n\xE3o configurado." };
19493
+ } catch (error) {
19494
+ console.error("Erro em delete_uploaded_file_logic:", error);
19495
+ return { success: false, error: error.message };
19496
+ }
19497
+ }
19498
+ async function get_view_members_logic(db_services, ownerId, type, itemId) {
19499
+ try {
19500
+ if (!db_services.get_view_members) {
19501
+ return { success: false, error: "Servi\xE7o de busca de membros n\xE3o configurado." };
19502
+ }
19503
+ return await db_services.get_view_members(ownerId, type, itemId);
19504
+ } catch (error) {
19505
+ console.error("Erro em get_view_members_logic:", error);
19506
+ return { success: false, error: error.message };
19507
+ }
19508
+ }
18804
19509
  export {
18805
- XViewScene
19510
+ XViewScene,
19511
+ delete_uploaded_file_logic,
19512
+ get_ancestry_file_logic,
19513
+ get_boards_logic,
19514
+ get_scene_view_data_logic,
19515
+ get_single_parent_file_logic,
19516
+ get_view_members_logic,
19517
+ import_parent_file_modal_get_logic,
19518
+ save_boards_logic,
19519
+ save_view_data_logic,
19520
+ x_view_add_new_parent_file_to_scene_at_firebase
18806
19521
  };