@mindexec/cli 0.2.0
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.
- package/README.md +275 -0
- package/codex-runtime.js +960 -0
- package/launch-bridge.cjs +162 -0
- package/package.json +61 -0
- package/port-guard.cjs +232 -0
- package/scripts/setup-tree-sitter-grammars.mjs +59 -0
- package/server.js +8422 -0
- package/start-bridge.bat +32 -0
- package/start-bridge.sh +81 -0
- package/tree-sitter-grammars/README.md +18 -0
- package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
- package/wwwroot/MindExecution.Web.styles.css +3 -0
- package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -0
- package/wwwroot/_content/MindExecution.Plugins.Directory/MindExecution.Plugins.Directory.u7utcng611.bundle.scp.css +7 -0
- package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
- package/wwwroot/_content/MindExecution.Plugins.Directory/directory-manager.js +202 -0
- package/wwwroot/_content/MindExecution.Plugins.Directory/exampleJsInterop.js +6 -0
- package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -0
- package/wwwroot/_content/MindExecution.Shared/MindExecution.Shared.wsano1j4wp.bundle.scp.css +4 -0
- package/wwwroot/_content/MindExecution.Shared/css/admin-dashboard.css +559 -0
- package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -0
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +2936 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +359 -0
- package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1721 -0
- package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -0
- package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +639 -0
- package/wwwroot/_content/MindExecution.Shared/js/helpers/InfiniteGridHelper.js +109 -0
- package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +7982 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +1059 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +15803 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1430 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +434 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +260 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7640 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9923 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +977 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1431 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +1716 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +553 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4541 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +489 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +372 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2075 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +646 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +4323 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2260 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +1258 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +890 -0
- package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +594 -0
- package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +170 -0
- package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +788 -0
- package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +50 -0
- package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +188 -0
- package/wwwroot/_content/MindExecution.Shared/js/texture-worker.js +208 -0
- package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -0
- package/wwwroot/_content/MindExecution.Shared/js/titlebar-handler.js +191 -0
- package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +37 -0
- package/wwwroot/_content/MindExecution.Shared/js/token-worker.js +28 -0
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +5626 -0
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +7 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +9 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.woff2 +0 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.woff2 +0 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.woff2 +0 -0
- package/wwwroot/_content/MindExecution.Shared/models/all-MiniLM-L6-v2-quantized.onnx +0 -0
- package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -0
- package/wwwroot/_framework/Google.Protobuf.9h59ukbel7.dll +0 -0
- package/wwwroot/_framework/Markdig.d1j7v41cl1.dll +0 -0
- package/wwwroot/_framework/MessagePack.Annotations.l6qv48kgpt.dll +0 -0
- package/wwwroot/_framework/MessagePack.eqoptzx9d5.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Authorization.k7dsih5y5g.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Components.6nyje9sa0g.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Components.Authorization.iycd6unprw.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Components.Web.487u3twia4.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Components.WebAssembly.d0gcnmlxxz.dll +0 -0
- package/wwwroot/_framework/Microsoft.AspNetCore.Metadata.h4yevl9adi.dll +0 -0
- package/wwwroot/_framework/Microsoft.CSharp.qrvp77qmhs.dll +0 -0
- package/wwwroot/_framework/Microsoft.Data.Sqlite.jdlxgv2jtg.dll +0 -0
- package/wwwroot/_framework/Microsoft.EntityFrameworkCore.4gjazp7kjf.dll +0 -0
- package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Abstractions.gocudnvz7b.dll +0 -0
- package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Relational.lt4rsvinuo.dll +0 -0
- package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Sqlite.69luj0fa9r.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Caching.Abstractions.364t4jh3zz.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Caching.Memory.izlxhpzosu.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Configuration.8zq7hh41o7.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Configuration.Abstractions.8if74zs6ea.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Configuration.Json.duvlngw8i0.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.Abstractions.t2hh9kvx0o.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.n4tg99oy8l.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.DependencyModel.h0d06ixk3e.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Logging.Abstractions.rl32bkx2sd.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Logging.dlht1xei0t.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Options.qeunebioml.dll +0 -0
- package/wwwroot/_framework/Microsoft.Extensions.Primitives.18cr6vnuuz.dll +0 -0
- package/wwwroot/_framework/Microsoft.IO.RecyclableMemoryStream.r915vovvw4.dll +0 -0
- package/wwwroot/_framework/Microsoft.IdentityModel.Abstractions.1ejljk3erv.dll +0 -0
- package/wwwroot/_framework/Microsoft.IdentityModel.JsonWebTokens.1596zr8gne.dll +0 -0
- package/wwwroot/_framework/Microsoft.IdentityModel.Logging.229uyvpgio.dll +0 -0
- package/wwwroot/_framework/Microsoft.IdentityModel.Tokens.9sibtajc9f.dll +0 -0
- package/wwwroot/_framework/Microsoft.JSInterop.17lq4j1j7g.dll +0 -0
- package/wwwroot/_framework/Microsoft.JSInterop.WebAssembly.ryia5gxiad.dll +0 -0
- package/wwwroot/_framework/Microsoft.ML.OnnxRuntime.w9deo1m5ss.dll +0 -0
- package/wwwroot/_framework/Microsoft.ML.Tokenizers.cm2vuv2z61.dll +0 -0
- package/wwwroot/_framework/Microsoft.NET.StringTools.3qbrf4v2ki.dll +0 -0
- package/wwwroot/_framework/MimeMapping.og9ys58ylm.dll +0 -0
- package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
- package/wwwroot/_framework/MindExecution.Kernel.gwwc40sc45.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.Admin.0jgrn1sckv.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.Business.13mme2qcag.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.Concept.dfp2mdt45q.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.Directory.3w4t6n3se0.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.PlanMaster.s0qpntz420.dll +0 -0
- package/wwwroot/_framework/MindExecution.Plugins.YouTube.iu11fq8d16.dll +0 -0
- package/wwwroot/_framework/MindExecution.Shared.7j27dcqnrc.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.pq1ty8ov2v.dll +0 -0
- package/wwwroot/_framework/Newtonsoft.Json.a56zs13vug.dll +0 -0
- package/wwwroot/_framework/SQLitePCLRaw.batteries_v2.rrd1nzawpp.dll +0 -0
- package/wwwroot/_framework/SQLitePCLRaw.core.1dxloztpfz.dll +0 -0
- package/wwwroot/_framework/SQLitePCLRaw.provider.e_sqlite3.oekyzl53i1.dll +0 -0
- package/wwwroot/_framework/Supabase.Core.s1pkj4aj0l.dll +0 -0
- package/wwwroot/_framework/Supabase.Functions.qz4nu782sg.dll +0 -0
- package/wwwroot/_framework/Supabase.Gotrue.twah27pkik.dll +0 -0
- package/wwwroot/_framework/Supabase.Postgrest.gmuuv369ih.dll +0 -0
- package/wwwroot/_framework/Supabase.Realtime.ox3kchdy3w.dll +0 -0
- package/wwwroot/_framework/Supabase.Storage.fnjnepaowr.dll +0 -0
- package/wwwroot/_framework/Supabase.azmaw5pgcz.dll +0 -0
- package/wwwroot/_framework/System.Collections.Concurrent.y1zmvuyipi.dll +0 -0
- package/wwwroot/_framework/System.Collections.Immutable.ug3j698qms.dll +0 -0
- package/wwwroot/_framework/System.Collections.NonGeneric.h66hj3863h.dll +0 -0
- package/wwwroot/_framework/System.Collections.Specialized.umr3y27ntj.dll +0 -0
- package/wwwroot/_framework/System.Collections.x53e19vfsj.dll +0 -0
- package/wwwroot/_framework/System.ComponentModel.Annotations.tz6gnt4ebt.dll +0 -0
- package/wwwroot/_framework/System.ComponentModel.Primitives.j7tiphu4rg.dll +0 -0
- package/wwwroot/_framework/System.ComponentModel.TypeConverter.ujlztox1gx.dll +0 -0
- package/wwwroot/_framework/System.ComponentModel.x9xz0ojfb6.dll +0 -0
- package/wwwroot/_framework/System.Console.ijzpqmj7ne.dll +0 -0
- package/wwwroot/_framework/System.Data.Common.1r0sqffq1p.dll +0 -0
- package/wwwroot/_framework/System.Diagnostics.DiagnosticSource.9upoqwq09o.dll +0 -0
- package/wwwroot/_framework/System.Diagnostics.Process.m99azzntjm.dll +0 -0
- package/wwwroot/_framework/System.Diagnostics.TraceSource.pl7wv26myr.dll +0 -0
- package/wwwroot/_framework/System.Diagnostics.Tracing.crlhfx6tut.dll +0 -0
- package/wwwroot/_framework/System.Drawing.Primitives.22e4y9ikq9.dll +0 -0
- package/wwwroot/_framework/System.Drawing.mi7d8hwowb.dll +0 -0
- package/wwwroot/_framework/System.Formats.Asn1.jx23sjiqnn.dll +0 -0
- package/wwwroot/_framework/System.IO.Compression.6fyoii3uej.dll +0 -0
- package/wwwroot/_framework/System.IO.Pipelines.vg77t4cd4d.dll +0 -0
- package/wwwroot/_framework/System.IdentityModel.Tokens.Jwt.t67es60z5b.dll +0 -0
- package/wwwroot/_framework/System.Linq.1bkoxlqgmq.dll +0 -0
- package/wwwroot/_framework/System.Linq.Expressions.24xqiypwdt.dll +0 -0
- package/wwwroot/_framework/System.Linq.Queryable.hvd01d6rsa.dll +0 -0
- package/wwwroot/_framework/System.Memory.8dx3lwgym4.dll +0 -0
- package/wwwroot/_framework/System.Net.Http.Json.3mhdm9l1rf.dll +0 -0
- package/wwwroot/_framework/System.Net.Http.eitrz660my.dll +0 -0
- package/wwwroot/_framework/System.Net.NetworkInformation.3pkuofcv9r.dll +0 -0
- package/wwwroot/_framework/System.Net.Ping.8clj5pklrp.dll +0 -0
- package/wwwroot/_framework/System.Net.Primitives.qrp4wcjz1p.dll +0 -0
- package/wwwroot/_framework/System.Net.WebSockets.Client.2u6pv01g69.dll +0 -0
- package/wwwroot/_framework/System.Net.WebSockets.qp6u31zvm5.dll +0 -0
- package/wwwroot/_framework/System.Numerics.Tensors.0c7z4mt3on.dll +0 -0
- package/wwwroot/_framework/System.Numerics.Vectors.kc7ufp2j4l.dll +0 -0
- package/wwwroot/_framework/System.ObjectModel.qv82fot1ib.dll +0 -0
- package/wwwroot/_framework/System.Private.CoreLib.rkafq04oma.dll +0 -0
- package/wwwroot/_framework/System.Private.Uri.t9542hmr6j.dll +0 -0
- package/wwwroot/_framework/System.Private.Xml.Linq.n8n3ptrbwu.dll +0 -0
- package/wwwroot/_framework/System.Private.Xml.rxd3tytisn.dll +0 -0
- package/wwwroot/_framework/System.Reactive.t3fuon548l.dll +0 -0
- package/wwwroot/_framework/System.Reflection.Emit.9tjhp6y0j3.dll +0 -0
- package/wwwroot/_framework/System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll +0 -0
- package/wwwroot/_framework/System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll +0 -0
- package/wwwroot/_framework/System.Reflection.Primitives.wgn8fpwwvv.dll +0 -0
- package/wwwroot/_framework/System.Runtime.InteropServices.JavaScript.sliym526xh.dll +0 -0
- package/wwwroot/_framework/System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll +0 -0
- package/wwwroot/_framework/System.Runtime.InteropServices.te07xr2we9.dll +0 -0
- package/wwwroot/_framework/System.Runtime.Intrinsics.507y4h8nzq.dll +0 -0
- package/wwwroot/_framework/System.Runtime.Loader.v7gk4bse0k.dll +0 -0
- package/wwwroot/_framework/System.Runtime.Numerics.eqy5xjv3nd.dll +0 -0
- package/wwwroot/_framework/System.Runtime.Serialization.Formatters.zpkrub8lab.dll +0 -0
- package/wwwroot/_framework/System.Runtime.Serialization.Primitives.vhkpnbxjip.dll +0 -0
- package/wwwroot/_framework/System.Runtime.jn319d5nyg.dll +0 -0
- package/wwwroot/_framework/System.Security.Claims.0ztig1q9vo.dll +0 -0
- package/wwwroot/_framework/System.Security.Cryptography.vttizqc9ho.dll +0 -0
- package/wwwroot/_framework/System.Text.Encoding.Extensions.utdd47ny8f.dll +0 -0
- package/wwwroot/_framework/System.Text.Encodings.Web.wah8r1zoe0.dll +0 -0
- package/wwwroot/_framework/System.Text.Json.kxlfxj0wrs.dll +0 -0
- package/wwwroot/_framework/System.Text.RegularExpressions.dbqn58klox.dll +0 -0
- package/wwwroot/_framework/System.Threading.42ao9vi047.dll +0 -0
- package/wwwroot/_framework/System.Threading.Channels.hfa7j0uv2w.dll +0 -0
- package/wwwroot/_framework/System.Threading.Thread.caul0pdqul.dll +0 -0
- package/wwwroot/_framework/System.Transactions.Local.fimi2hamzo.dll +0 -0
- package/wwwroot/_framework/System.Web.HttpUtility.gq8yz50p2e.dll +0 -0
- package/wwwroot/_framework/System.Xml.Linq.kitin4zjoj.dll +0 -0
- package/wwwroot/_framework/System.Xml.ReaderWriter.kzvw3qgxb0.dll +0 -0
- package/wwwroot/_framework/System.Xml.XDocument.c539ki6cuq.dll +0 -0
- package/wwwroot/_framework/System.m05i39uvk9.dll +0 -0
- package/wwwroot/_framework/Websocket.Client.vapounvmnl.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +305 -0
- package/wwwroot/_framework/blazor.webassembly.js +1 -0
- package/wwwroot/_framework/dotnet.js +4 -0
- package/wwwroot/_framework/dotnet.native.vz0adxojrz.wasm +0 -0
- package/wwwroot/_framework/dotnet.native.xsn1d6x2kd.js +16 -0
- package/wwwroot/_framework/dotnet.runtime.dstopyvqzi.js +4 -0
- package/wwwroot/_framework/icudt_CJK.tjcz0u77k5.dat +0 -0
- package/wwwroot/_framework/icudt_EFIGS.tptq2av103.dat +0 -0
- package/wwwroot/_framework/icudt_no_CJK.lfu7j35m59.dat +0 -0
- package/wwwroot/_framework/netstandard.0xet7jg7ky.dll +0 -0
- package/wwwroot/_headers +40 -0
- package/wwwroot/_redirects +1 -0
- package/wwwroot/appsettings.json +71 -0
- package/wwwroot/icon-192.png +0 -0
- package/wwwroot/icon-512.png +0 -0
- package/wwwroot/index.html +710 -0
- package/wwwroot/js/marketing-tool.js +180 -0
- package/wwwroot/manifest.webmanifest +22 -0
- package/wwwroot/robots.txt +4 -0
- package/wwwroot/service-worker-assets.js +857 -0
- package/wwwroot/service-worker.js +33 -0
- package/wwwroot/sitemap.xml +27 -0
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
// File: mind-map-core.js
|
|
2
|
+
// MindMap3D and component interface - core logic (simplified: style queue removed)
|
|
3
|
+
(function () {
|
|
4
|
+
if (typeof MindMapInteractions === 'undefined' ||
|
|
5
|
+
typeof MindMapNodes === 'undefined' ||
|
|
6
|
+
typeof MindMapDnD === 'undefined' ||
|
|
7
|
+
typeof globalThis.THREE === 'undefined' ||
|
|
8
|
+
typeof window.MindMapThemes === 'undefined') {
|
|
9
|
+
console.error('[mind-map-core.js] Failed: dependencies not loaded.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let moduleInstance = null;
|
|
14
|
+
|
|
15
|
+
class MindMapModule {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.scene = null;
|
|
18
|
+
this.camera = null;
|
|
19
|
+
this.renderer = null;
|
|
20
|
+
this.cssRenderer = null; // ▼▼▼ [Added] CSS3DRenderer reference ▼▼▼
|
|
21
|
+
this.dotNetHelper = null;
|
|
22
|
+
this.container = null;
|
|
23
|
+
this.animationFrameId = null;
|
|
24
|
+
this.nodeObjectsById = new Map();
|
|
25
|
+
this.lodUpdateQueue = []; // Initialize lodUpdateQueue
|
|
26
|
+
this.isPanning = false;
|
|
27
|
+
this.lastMousePos = { x: 0, y: 0 };
|
|
28
|
+
this.targetZ = null;
|
|
29
|
+
this.targetX = null;
|
|
30
|
+
this.targetY = null;
|
|
31
|
+
this.isDraggingNode = false;
|
|
32
|
+
this.draggedNodeObject = null;
|
|
33
|
+
this.startMousePos = { x: 0, y: 0 };
|
|
34
|
+
this.wasDragged = false;
|
|
35
|
+
this.clickStartTime = 0;
|
|
36
|
+
this.selectedNodeIdJs = null;
|
|
37
|
+
this.raycaster = null;
|
|
38
|
+
this.pointer = null;
|
|
39
|
+
this.plane = null;
|
|
40
|
+
this.intersectPoint = new THREE.Vector3();
|
|
41
|
+
this.dragOffset = new THREE.Vector3();
|
|
42
|
+
this.isSelecting = false;
|
|
43
|
+
this.isShiftSelecting = false;
|
|
44
|
+
this.selectionRectStartPos = { x: 0, y: 0 };
|
|
45
|
+
this.selectionRectElement = null;
|
|
46
|
+
this.isDraggingMultipleNodes = false;
|
|
47
|
+
this.multiSelectBox = null;
|
|
48
|
+
// ▼▼▼ [Changed] Initialize cursor position ▼▼▼
|
|
49
|
+
this.cursorPosition = new THREE.Vector3(0, 0, 0);
|
|
50
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
51
|
+
this.cursorMesh = null;
|
|
52
|
+
this.cursorMaterial = null;
|
|
53
|
+
this.cursorLineMaterial = null;
|
|
54
|
+
|
|
55
|
+
// ▼▼▼ [Changed] Initialize multi-drag related fields ▼▼▼
|
|
56
|
+
this.multiDragInitialPositions = new Map(); // Naming aligned (previous: initialMultiSelectPositions)
|
|
57
|
+
this.clickedNodeInitialPos = new THREE.Vector3(); // Fix: missing initialization
|
|
58
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
59
|
+
|
|
60
|
+
this.nodeZCounter = 1; // renderOrder counter
|
|
61
|
+
|
|
62
|
+
// ▼▼▼ [Core addition] Flag to track Note node editing state ▼▼▼
|
|
63
|
+
this.isEditingNote = false;
|
|
64
|
+
// ▲▲▲ [Core addition] ▲▲▲
|
|
65
|
+
|
|
66
|
+
// ▼▼▼ [Core addition] Multi-selection and spatial grid initialization ▼▼▼
|
|
67
|
+
this.multiSelectedNodeIds = new Set();
|
|
68
|
+
this.spatialGrid = new Map();
|
|
69
|
+
this.GRID_CELL_SIZE = 500;
|
|
70
|
+
// ▼▼▼ [Changed] 25 -> 10 (match background-themes.js 'XYGrid' size1) ▼▼▼
|
|
71
|
+
this.GRID_SIZE = 10;
|
|
72
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
73
|
+
|
|
74
|
+
// ▼▼▼ [Optimization] Add render-loop optimization fields ▼▼▼
|
|
75
|
+
this.frameCount = 0;
|
|
76
|
+
this.lastCameraPos = new THREE.Vector3();
|
|
77
|
+
this.lastCameraRot = new THREE.Euler();
|
|
78
|
+
// ▲▲▲ [Optimization] ▲▲▲
|
|
79
|
+
|
|
80
|
+
// ▼▼▼ [New] Texture Atlas Manager for batched rendering ▼▼▼
|
|
81
|
+
this.atlasManager = null; // Lazy init when needed
|
|
82
|
+
this.useAtlasRendering = false; // Feature flag - enable when stable
|
|
83
|
+
// ▲▲▲ [New] ▲▲▲
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Cursor snap function (keep legacy behavior: floor/ceil)
|
|
87
|
+
snapToGrid(v) { return Math.floor(v / this.GRID_SIZE) * this.GRID_SIZE; }
|
|
88
|
+
snapToGridY(v) { return Math.ceil(v / this.GRID_SIZE) * this.GRID_SIZE; }
|
|
89
|
+
|
|
90
|
+
// ▼▼▼ [New] Node snap function - use Math.round to snap to nearest grid ▼▼▼
|
|
91
|
+
snapNodeToGrid(v) { return Math.round(v / this.GRID_SIZE) * this.GRID_SIZE; }
|
|
92
|
+
// ▲▲▲ [New] ▲▲▲
|
|
93
|
+
|
|
94
|
+
init(containerId, helper) {
|
|
95
|
+
// Re-activate
|
|
96
|
+
if (this.container) {
|
|
97
|
+
const target = document.getElementById(containerId);
|
|
98
|
+
if (target && this.renderer) {
|
|
99
|
+
try {
|
|
100
|
+
if (this.renderer.domElement.parentElement) {
|
|
101
|
+
this.renderer.domElement.parentElement.removeChild(this.renderer.domElement);
|
|
102
|
+
}
|
|
103
|
+
// ▼▼▼ [Added] Also remove the CSS3D canvas from the DOM ▼▼▼
|
|
104
|
+
if (this.cssRenderer && this.cssRenderer.domElement.parentElement) {
|
|
105
|
+
this.cssRenderer.domElement.parentElement.removeChild(this.cssRenderer.domElement);
|
|
106
|
+
}
|
|
107
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
108
|
+
} catch { }
|
|
109
|
+
target.appendChild(this.renderer.domElement);
|
|
110
|
+
// ▼▼▼ [Critical fix] Re-add missing CSS3D canvas ▼▼▼
|
|
111
|
+
if (this.cssRenderer) {
|
|
112
|
+
target.appendChild(this.cssRenderer.domElement);
|
|
113
|
+
}
|
|
114
|
+
// ▲▲▲ [Critical fix] ▲▲▲
|
|
115
|
+
this.container = target;
|
|
116
|
+
this.dotNetHelper = helper;
|
|
117
|
+
window.dotNetHelper = helper; // Expose globally for NativeDropHandler access
|
|
118
|
+
target.style.display = 'block';
|
|
119
|
+
MindMapInteractions.onResize(this);
|
|
120
|
+
if (!this.animationFrameId) this.animate();
|
|
121
|
+
console.log(`[mind-map-core.js] Re-activated for #${containerId}.`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Initialize
|
|
127
|
+
this.dotNetHelper = helper;
|
|
128
|
+
window.dotNetHelper = helper; // Expose globally for NativeDropHandler access
|
|
129
|
+
this.container = document.getElementById(containerId);
|
|
130
|
+
if (!this.container) {
|
|
131
|
+
console.error('[mind-map-core.js] Container not found!');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.container.style.display = 'block';
|
|
135
|
+
const THREE = globalThis.THREE;
|
|
136
|
+
this.scene = new THREE.Scene();
|
|
137
|
+
this.debugGroup = new THREE.Group();
|
|
138
|
+
this.scene.add(this.debugGroup);
|
|
139
|
+
|
|
140
|
+
// ▼▼▼ [Cursor creation] Mesh + Line hybrid -----
|
|
141
|
+
this.cursorMesh = new THREE.Group();
|
|
142
|
+
const cursorSize = 20; // Cursor length
|
|
143
|
+
const thickness = 4; // Cursor thickness
|
|
144
|
+
|
|
145
|
+
// Mesh material (background / thickness)
|
|
146
|
+
this.cursorMaterial = new THREE.MeshBasicMaterial({
|
|
147
|
+
color: 0xffffff,
|
|
148
|
+
transparent: true,
|
|
149
|
+
opacity: 1.0,
|
|
150
|
+
depthTest: false
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Line material (centerline)
|
|
154
|
+
this.cursorLineMaterial = new THREE.LineBasicMaterial({
|
|
155
|
+
color: 0xffffff,
|
|
156
|
+
transparent: false,
|
|
157
|
+
opacity: 1,
|
|
158
|
+
depthTest: false
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Mesh geometry (vertical/horizontal bars)
|
|
162
|
+
const vGeo = new THREE.BoxGeometry(thickness, cursorSize, 1);
|
|
163
|
+
const vMesh = new THREE.Mesh(vGeo, this.cursorMaterial);
|
|
164
|
+
vMesh.position.set(thickness / 2, -cursorSize / 2, 0);
|
|
165
|
+
vMesh.renderOrder = 9998;
|
|
166
|
+
|
|
167
|
+
const hGeo = new THREE.BoxGeometry(cursorSize, thickness, 1);
|
|
168
|
+
const hMesh = new THREE.Mesh(hGeo, this.cursorMaterial);
|
|
169
|
+
hMesh.position.set(cursorSize / 2, -thickness / 2, 0);
|
|
170
|
+
hMesh.renderOrder = 9998;
|
|
171
|
+
|
|
172
|
+
this.cursorMesh.add(vMesh);
|
|
173
|
+
this.cursorMesh.add(hMesh);
|
|
174
|
+
|
|
175
|
+
// Line geometry (position adjusted)
|
|
176
|
+
const lineGeo = new THREE.BufferGeometry().setFromPoints([
|
|
177
|
+
new THREE.Vector3(0, -cursorSize, 0),
|
|
178
|
+
new THREE.Vector3(0, 0, 0),
|
|
179
|
+
new THREE.Vector3(cursorSize, 0, 0)
|
|
180
|
+
]);
|
|
181
|
+
const line = new THREE.Line(lineGeo, this.cursorLineMaterial);
|
|
182
|
+
line.position.z = 0.6;
|
|
183
|
+
line.renderOrder = 9999;
|
|
184
|
+
this.cursorMesh.add(line);
|
|
185
|
+
|
|
186
|
+
this.cursorMesh.position.set(0, 0, 1);
|
|
187
|
+
this.cursorMesh.renderOrder = 9999;
|
|
188
|
+
this.scene.add(this.cursorMesh);
|
|
189
|
+
|
|
190
|
+
if (this.updateCursorPosition) {
|
|
191
|
+
this.updateCursorPosition(0, 0);
|
|
192
|
+
}
|
|
193
|
+
// ▲▲▲ [Cursor creation] ▲▲▲
|
|
194
|
+
|
|
195
|
+
const NEAR = 0.1, FAR = 1e7;
|
|
196
|
+
this.camera = new THREE.PerspectiveCamera(50, this.container.clientWidth / this.container.clientHeight, NEAR, FAR);
|
|
197
|
+
const initialZ = 1200;
|
|
198
|
+
this.camera.position.z = initialZ;
|
|
199
|
+
this.targetZ = initialZ;
|
|
200
|
+
this.targetX = this.camera.position.x;
|
|
201
|
+
this.targetY = this.camera.position.y;
|
|
202
|
+
|
|
203
|
+
// ▼▼▼ [Changed] Add fallback logic if WebGLRenderer init fails (context limits, etc.) ▼▼▼
|
|
204
|
+
try {
|
|
205
|
+
this.renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true, alpha: true });
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.warn('[MindMap] WebGL context creation failed with advanced settings. Retrying with defaults.', e);
|
|
208
|
+
try {
|
|
209
|
+
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
210
|
+
} catch (e2) {
|
|
211
|
+
console.warn('[MindMap] WebGL context creation failed with antialias. Retrying plain.', e2);
|
|
212
|
+
this.renderer = new THREE.WebGLRenderer({ alpha: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
216
|
+
|
|
217
|
+
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
|
|
218
|
+
this.renderer.setPixelRatio(window.devicePixelRatio || 1);
|
|
219
|
+
this.renderer.setClearColor(0x000000, 0); // Transparent background for Space3D theme
|
|
220
|
+
// [Changed] Z-Index: 1, pointerEvents: 'none' (same as CodeMaster)
|
|
221
|
+
Object.assign(this.renderer.domElement.style, { position: 'absolute', top: 0, left: 0, zIndex: 1, pointerEvents: 'none' });
|
|
222
|
+
this.container.appendChild(this.renderer.domElement);
|
|
223
|
+
|
|
224
|
+
// ▼▼▼ [Changed] CSS3DRenderer initialization (same as CodeMaster) ▼▼▼
|
|
225
|
+
if (globalThis.CSS3DRenderer) {
|
|
226
|
+
this.cssRenderer = new globalThis.CSS3DRenderer();
|
|
227
|
+
this.cssRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
|
|
228
|
+
// [Changed] Z-Index: 2 (top-most), pointerEvents: 'auto' (receive all events)
|
|
229
|
+
Object.assign(this.cssRenderer.domElement.style, {
|
|
230
|
+
position: 'absolute',
|
|
231
|
+
top: 0,
|
|
232
|
+
left: 0,
|
|
233
|
+
zIndex: 2,
|
|
234
|
+
pointerEvents: 'auto'
|
|
235
|
+
});
|
|
236
|
+
this.container.appendChild(this.cssRenderer.domElement);
|
|
237
|
+
console.log('[mind-map-core.js] CSS3DRenderer initialized (Z-Index: 2, pointerEvents: auto)');
|
|
238
|
+
} else {
|
|
239
|
+
console.warn('[mind-map-core.js] CSS3DRenderer not available');
|
|
240
|
+
}
|
|
241
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
242
|
+
|
|
243
|
+
this.raycaster = new THREE.Raycaster();
|
|
244
|
+
this.pointer = new THREE.Vector2();
|
|
245
|
+
this.plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
|
246
|
+
this.selectionRectElement = document.getElementById('selection-rectangle');
|
|
247
|
+
|
|
248
|
+
this.setBackgroundTheme('Space3D');
|
|
249
|
+
|
|
250
|
+
MindMapInteractions.addEventListeners(this);
|
|
251
|
+
MindMapDnD.addEventListeners(this);
|
|
252
|
+
|
|
253
|
+
this.animate();
|
|
254
|
+
// ▼▼▼ [Added] Initialize MenuManager ▼▼▼
|
|
255
|
+
if (window.MindMapMenuManager) {
|
|
256
|
+
window.MindMapMenuManager.init(this);
|
|
257
|
+
}
|
|
258
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
259
|
+
// ▼▼▼ [Added] Initialize MultiSelect ▼▼▼
|
|
260
|
+
if (window.MindMapMultiSelect) {
|
|
261
|
+
window.MindMapMultiSelect.init(this);
|
|
262
|
+
}
|
|
263
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
264
|
+
|
|
265
|
+
// ▼▼▼ [New] Initialize Atlas Manager (can be enabled later via enableAtlasRendering) ▼▼▼
|
|
266
|
+
if (window.TextureAtlasManager) {
|
|
267
|
+
this.initAtlasManager({ atlasSize: 4096, maxAtlasPages: 16 });
|
|
268
|
+
console.log('[mind-map-core.js] TextureAtlasManager ready (enable with mindMap.enableAtlasRendering(true))');
|
|
269
|
+
}
|
|
270
|
+
// ▲▲▲ [New] ▲▲▲
|
|
271
|
+
|
|
272
|
+
// ▼▼▼ [New] Initialize LOD Renderer for massive node counts ▼▼▼
|
|
273
|
+
// ▼▼▼ [TEMP DISABLED] LOD Renderer initialization to test if it causes rendering issues ▼▼▼
|
|
274
|
+
/*
|
|
275
|
+
if (window.LODRenderer) {
|
|
276
|
+
this.lodRenderer = new window.LODRenderer(this.renderer, this.scene);
|
|
277
|
+
console.log('[mind-map-core.js] LODRenderer ready (enable with mindMap.enableLOD(true))');
|
|
278
|
+
}
|
|
279
|
+
*/
|
|
280
|
+
this.lodRenderer = null;
|
|
281
|
+
this.useLODRendering = false; // Disabled for testing - enable with mindMap.enableLOD(true)
|
|
282
|
+
// ▲▲▲ [TEMP DISABLED] ▲▲▲
|
|
283
|
+
|
|
284
|
+
console.log(`[mind-map-core.js] Initialized for #${containerId}.`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setBackgroundTheme(themeName) {
|
|
288
|
+
if (themeName === this.currentThemeName || !this.scene || !window.MindMapThemes) return;
|
|
289
|
+
const newTheme = window.MindMapThemes.getTheme(themeName);
|
|
290
|
+
if (!newTheme) { console.warn(`[mind-map-core.js] Theme '${themeName}' not found.`); return; }
|
|
291
|
+
if (this.currentThemeObject) {
|
|
292
|
+
const oldTheme = window.MindMapThemes.getTheme(this.currentThemeName);
|
|
293
|
+
try { oldTheme?.dispose(this.currentThemeObject); } catch { }
|
|
294
|
+
if (this.scene) this.scene.remove(this.currentThemeObject);
|
|
295
|
+
}
|
|
296
|
+
this.currentThemeObject = newTheme.create(this.scene);
|
|
297
|
+
if (this.currentThemeObject) this.scene.add(this.currentThemeObject);
|
|
298
|
+
this.currentThemeName = themeName;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
cullAndLOD() {
|
|
302
|
+
// ▼▼▼ [Optimization] Frustum culling ▼▼▼
|
|
303
|
+
// Nodes outside the camera frustum have visible=false, reducing draw calls
|
|
304
|
+
if (!this.camera || !this.scene) return;
|
|
305
|
+
|
|
306
|
+
// 1. Create a frustum and update it from the current camera projection matrix.
|
|
307
|
+
const frustum = new THREE.Frustum();
|
|
308
|
+
const projScreenMatrix = new THREE.Matrix4();
|
|
309
|
+
projScreenMatrix.multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse);
|
|
310
|
+
frustum.setFromProjectionMatrix(projScreenMatrix);
|
|
311
|
+
|
|
312
|
+
// Check if LOD is controlling visibility
|
|
313
|
+
const lodControlsVisibility = this.useLODRendering && this.lodRenderer?.isInLODMode;
|
|
314
|
+
|
|
315
|
+
// 2. Iterate all nodes and check whether they are inside the frustum.
|
|
316
|
+
this.nodeObjectsById.forEach((nodeEntry, nodeId) => {
|
|
317
|
+
const glObject = nodeEntry.glObject;
|
|
318
|
+
if (!glObject || !glObject.userData) return;
|
|
319
|
+
|
|
320
|
+
// Compute a node bounding box.
|
|
321
|
+
const width = glObject.userData.worldWidth || 300;
|
|
322
|
+
const height = glObject.userData.worldHeight || 200;
|
|
323
|
+
const nodeBox = new THREE.Box3(
|
|
324
|
+
new THREE.Vector3(glObject.position.x, glObject.position.y - height, glObject.position.z),
|
|
325
|
+
new THREE.Vector3(glObject.position.x + width, glObject.position.y, glObject.position.z)
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// 3. Determine visibility by checking frustum-box intersection.
|
|
329
|
+
const isVisible = frustum.intersectsBox(nodeBox);
|
|
330
|
+
|
|
331
|
+
// 4. Control visibility for both WebGL and CSS3D objects.
|
|
332
|
+
// Skip glObject visibility if LOD is controlling it
|
|
333
|
+
if (!lodControlsVisibility && glObject.visible !== isVisible) {
|
|
334
|
+
glObject.visible = isVisible;
|
|
335
|
+
}
|
|
336
|
+
if (nodeEntry.cssObject && nodeEntry.cssObject.visible !== (isVisible && nodeEntry.currentType === 'CSS')) {
|
|
337
|
+
nodeEntry.cssObject.visible = (isVisible && nodeEntry.currentType === 'CSS');
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// ▲▲▲ [Optimization] ▲▲▲
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
animate() {
|
|
344
|
+
const loop = async () => {
|
|
345
|
+
this.animationFrameId = requestAnimationFrame(loop);
|
|
346
|
+
|
|
347
|
+
// Rendering always runs (camera interpolation, etc.)
|
|
348
|
+
// During resize the canvas is hidden and not visible to the user
|
|
349
|
+
|
|
350
|
+
if (this.currentThemeObject && typeof this.currentThemeObject.animate === 'function') {
|
|
351
|
+
this.currentThemeObject.animate(this.camera);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const s = 0.25;
|
|
355
|
+
const oldPos = this.camera.position.clone(); // Position before movement
|
|
356
|
+
|
|
357
|
+
this.camera.position.x += (this.targetX - this.camera.position.x) * s;
|
|
358
|
+
this.camera.position.y += (this.targetY - this.camera.position.y) * s;
|
|
359
|
+
this.camera.position.z += (this.targetZ - this.camera.position.z) * s;
|
|
360
|
+
|
|
361
|
+
// ▼▼▼ [Optimization] If the camera barely moved, skip movement work (sleep mode) ▼▼▼
|
|
362
|
+
// (avoid unnecessary matrix updates)
|
|
363
|
+
const deltaMove = this.camera.position.distanceToSquared(oldPos);
|
|
364
|
+
const isCameraMoving = deltaMove > 0.0001;
|
|
365
|
+
|
|
366
|
+
// ▼▼▼ [Changed] Do not sync node positions during camera movement (Zoom/Pan) ▼▼▼
|
|
367
|
+
// Reason: PageUp/Down or wheel zoom moves only the camera.
|
|
368
|
+
// Forcing WebGL/CSS coordinate sync here can introduce numeric drift and cause nodes to "jump".
|
|
369
|
+
|
|
370
|
+
// [Changed] Only sync during drag operations (when nodes actually move)
|
|
371
|
+
if (this.isDraggingNode || this.isDraggingMultipleNodes) {
|
|
372
|
+
this.nodeObjectsById.forEach((nodeEntry) => {
|
|
373
|
+
if (nodeEntry.currentType === 'CSS' && nodeEntry.cssObject && nodeEntry.glObject) {
|
|
374
|
+
if (!this.isEditingNote) {
|
|
375
|
+
nodeEntry.glObject.position.copy(nodeEntry.cssObject.position);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
381
|
+
|
|
382
|
+
// ▼▼▼ [New] LOD Rendering for massive node counts ▼▼▼
|
|
383
|
+
if (this.useLODRendering && this.lodRenderer) {
|
|
384
|
+
this.lodRenderer.updateLOD(this.camera, this.nodeObjectsById);
|
|
385
|
+
}
|
|
386
|
+
// ▲▲▲ [New] ▲▲▲
|
|
387
|
+
|
|
388
|
+
// ▼▼▼ [Optimization] Throttle frustum culling & LOD frequency ▼▼▼
|
|
389
|
+
// Run only when the camera moves, or once every 10 frames
|
|
390
|
+
this.frameCount = (this.frameCount || 0) + 1;
|
|
391
|
+
if (isCameraMoving || this.frameCount % 10 === 0) {
|
|
392
|
+
this.cullAndLOD();
|
|
393
|
+
}
|
|
394
|
+
// ▲▲▲ [Optimization] ▲▲▲
|
|
395
|
+
|
|
396
|
+
if (this.cursorMaterial) {
|
|
397
|
+
const time = Date.now() * 0.005;
|
|
398
|
+
const val = (Math.sin(time) * 0.5) + 0.5;
|
|
399
|
+
this.cursorMaterial.color.setRGB(val, val, val);
|
|
400
|
+
if (this.cursorLineMaterial) {
|
|
401
|
+
this.cursorLineMaterial.color.setRGB(val, val, val);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
this.renderer.render(this.scene, this.camera);
|
|
405
|
+
|
|
406
|
+
if (this.cssRenderer) {
|
|
407
|
+
this.cssRenderer.render(this.scene, this.camera);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ▼▼▼ [Added] Sync menu position ▼▼▼
|
|
411
|
+
if (window.MindMapMenuManager) {
|
|
412
|
+
window.MindMapMenuManager.update();
|
|
413
|
+
}
|
|
414
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
415
|
+
// ▼▼▼ [Added] Sync multi-select overlay position ▼▼▼
|
|
416
|
+
if (window.MindMapMultiSelect) {
|
|
417
|
+
window.MindMapMultiSelect.update();
|
|
418
|
+
}
|
|
419
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
420
|
+
};
|
|
421
|
+
loop();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
updateCursorPosition(x, y, moveCamera = false, cameraTarget = null) {
|
|
425
|
+
const snappedX = this.snapToGrid(x);
|
|
426
|
+
const snappedY = this.snapToGridY(y);
|
|
427
|
+
|
|
428
|
+
this.cursorPosition.set(snappedX, snappedY, 0);
|
|
429
|
+
if (this.cursorMesh) {
|
|
430
|
+
this.cursorMesh.position.set(snappedX, snappedY, 1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (moveCamera) {
|
|
434
|
+
let targetCenterX = snappedX;
|
|
435
|
+
let targetCenterY = snappedY;
|
|
436
|
+
|
|
437
|
+
if (cameraTarget) {
|
|
438
|
+
targetCenterX = cameraTarget.x;
|
|
439
|
+
targetCenterY = cameraTarget.y;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (this.camera) {
|
|
443
|
+
const vfov = (this.camera.fov * Math.PI) / 180;
|
|
444
|
+
const viewHeight = 2 * Math.tan(vfov / 2) * this.camera.position.z;
|
|
445
|
+
const yOffset = viewHeight * 0.3;
|
|
446
|
+
this.targetX = targetCenterX;
|
|
447
|
+
this.targetY = targetCenterY - yOffset;
|
|
448
|
+
} else {
|
|
449
|
+
this.targetX = targetCenterX;
|
|
450
|
+
this.targetY = targetCenterY;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
hide() {
|
|
456
|
+
if (this.container) {
|
|
457
|
+
this.container.style.display = 'none';
|
|
458
|
+
if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
cleanupAndDispose() {
|
|
463
|
+
if (!this.container) return;
|
|
464
|
+
if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; }
|
|
465
|
+
|
|
466
|
+
MindMapInteractions.removeEventListeners(this);
|
|
467
|
+
MindMapDnD.removeEventListeners(this);
|
|
468
|
+
|
|
469
|
+
if (this.scene && this.debugGroup) { this.scene.remove(this.debugGroup); }
|
|
470
|
+
this.debugGroup = null;
|
|
471
|
+
|
|
472
|
+
if (this.currentThemeObject && window.MindMapThemes) {
|
|
473
|
+
const theme = window.MindMapThemes.getTheme(this.currentThemeName);
|
|
474
|
+
try { theme?.dispose(this.currentThemeObject); } catch { }
|
|
475
|
+
if (this.scene) this.scene.remove(this.currentThemeObject);
|
|
476
|
+
}
|
|
477
|
+
this.currentThemeObject = null;
|
|
478
|
+
this.currentThemeName = null;
|
|
479
|
+
|
|
480
|
+
MindMapNodes.resetState(this);
|
|
481
|
+
|
|
482
|
+
if (this.scene) {
|
|
483
|
+
this.scene.traverse(o => {
|
|
484
|
+
if (o.geometry) o.geometry.dispose();
|
|
485
|
+
if (o.material) {
|
|
486
|
+
if (Array.isArray(o.material)) o.material.forEach(m => m.dispose());
|
|
487
|
+
else o.material.dispose();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (this.renderer) {
|
|
493
|
+
this.renderer.dispose();
|
|
494
|
+
// ▼▼▼ [Changed] Force context loss to prevent context leaks ▼▼▼
|
|
495
|
+
try {
|
|
496
|
+
if (typeof this.renderer.forceContextLoss === 'function') {
|
|
497
|
+
this.renderer.forceContextLoss();
|
|
498
|
+
} else {
|
|
499
|
+
const gl = this.renderer.getContext();
|
|
500
|
+
if (gl && gl.getExtension) {
|
|
501
|
+
const ext = gl.getExtension('WEBGL_lose_context');
|
|
502
|
+
if (ext) ext.loseContext();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} catch (e) { console.warn('[MindMap] Failed to lose context:', e); }
|
|
506
|
+
this.renderer.domElement = null;
|
|
507
|
+
this.renderer = null; // Release reference
|
|
508
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ▼▼▼ [Added] Cleanup CSS3DRenderer ▼▼▼
|
|
512
|
+
if (this.cssRenderer && this.cssRenderer.domElement) {
|
|
513
|
+
if (this.cssRenderer.domElement.parentElement) {
|
|
514
|
+
this.cssRenderer.domElement.parentElement.removeChild(this.cssRenderer.domElement);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
this.cssRenderer = null;
|
|
518
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
519
|
+
|
|
520
|
+
this.scene = null;
|
|
521
|
+
this.camera = null;
|
|
522
|
+
this.renderer = null;
|
|
523
|
+
this.container = null;
|
|
524
|
+
this.dotNetHelper = null;
|
|
525
|
+
window.dotNetHelper = null; // Cleanup global reference too
|
|
526
|
+
this.lodUpdateQueue = [];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
toggleDebugLines() {
|
|
530
|
+
if (!this.scene) return;
|
|
531
|
+
const DEBUG_GROUP_NAME = 'MindMapDebugLines';
|
|
532
|
+
let debugGroup = this.scene.getObjectByName(DEBUG_GROUP_NAME);
|
|
533
|
+
if (debugGroup) {
|
|
534
|
+
this.scene.remove(debugGroup);
|
|
535
|
+
debugGroup.traverse(child => { if (child.geometry) child.geometry.dispose(); if (child.material) child.material.dispose(); });
|
|
536
|
+
console.log('[MindMap] Debug lines hidden.');
|
|
537
|
+
} else {
|
|
538
|
+
debugGroup = new THREE.Group();
|
|
539
|
+
debugGroup.name = DEBUG_GROUP_NAME;
|
|
540
|
+
this.nodeObjectsById.forEach(nodeEntry => {
|
|
541
|
+
const glObject = nodeEntry.glObject;
|
|
542
|
+
if (glObject) {
|
|
543
|
+
glObject.traverse(child => {
|
|
544
|
+
if (child.isMesh) {
|
|
545
|
+
const edges = new THREE.EdgesGeometry(child.geometry);
|
|
546
|
+
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff00ff }));
|
|
547
|
+
const childWorldPos = new THREE.Vector3();
|
|
548
|
+
child.getWorldPosition(childWorldPos);
|
|
549
|
+
line.position.copy(childWorldPos);
|
|
550
|
+
line.rotation.copy(child.rotation);
|
|
551
|
+
line.scale.copy(child.scale);
|
|
552
|
+
debugGroup.add(line);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
this.scene.add(debugGroup);
|
|
558
|
+
console.log('[MindMap] Debug lines shown.');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// ▼▼▼ [Changed] Expand camera & cursor state save/restore API ▼▼▼
|
|
563
|
+
getCameraState() {
|
|
564
|
+
if (!this.camera) return { x: 0, y: 0, z: 1200, cursorX: 0, cursorY: 0 };
|
|
565
|
+
return {
|
|
566
|
+
x: this.camera.position.x,
|
|
567
|
+
y: this.camera.position.y,
|
|
568
|
+
z: this.camera.position.z,
|
|
569
|
+
cursorX: this.cursorPosition?.x ?? 0,
|
|
570
|
+
cursorY: this.cursorPosition?.y ?? 0
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
setCameraState(x, y, z, cursorX, cursorY) {
|
|
575
|
+
if (!this.camera) return;
|
|
576
|
+
|
|
577
|
+
this.targetX = x;
|
|
578
|
+
this.targetY = y;
|
|
579
|
+
this.targetZ = z;
|
|
580
|
+
this.camera.position.set(x, y, z);
|
|
581
|
+
|
|
582
|
+
if (cursorX !== undefined && cursorY !== undefined) {
|
|
583
|
+
this.updateCursorPosition(cursorX, cursorY, false);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
getNodePositions() {
|
|
588
|
+
if (!this.nodeObjectsById) return [];
|
|
589
|
+
|
|
590
|
+
const positions = [];
|
|
591
|
+
this.nodeObjectsById.forEach((entry, nodeId) => {
|
|
592
|
+
const obj = entry?.glObject || entry?.cssObject;
|
|
593
|
+
if (!obj || !obj.position) return;
|
|
594
|
+
positions.push({
|
|
595
|
+
nodeId: String(nodeId),
|
|
596
|
+
x: obj.position.x,
|
|
597
|
+
y: obj.position.y
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
return positions;
|
|
601
|
+
}
|
|
602
|
+
// ▲▲▲ [Modified] ▲▲▲
|
|
603
|
+
|
|
604
|
+
// ▼▼▼ [New] Texture Atlas Manager methods ▼▼▼
|
|
605
|
+
initAtlasManager(options = {}) {
|
|
606
|
+
if (!window.TextureAtlasManager) {
|
|
607
|
+
console.warn('[MindMapModule] TextureAtlasManager not loaded');
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
if (!this.atlasManager) {
|
|
611
|
+
this.atlasManager = new window.TextureAtlasManager({
|
|
612
|
+
atlasSize: options.atlasSize || 4096,
|
|
613
|
+
maxAtlasPages: options.maxAtlasPages || 16
|
|
614
|
+
});
|
|
615
|
+
console.log('[MindMapModule] TextureAtlasManager initialized');
|
|
616
|
+
}
|
|
617
|
+
return this.atlasManager;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
getAtlasManager() {
|
|
621
|
+
return this.atlasManager;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
getAtlasStats() {
|
|
625
|
+
if (!this.atlasManager) return null;
|
|
626
|
+
return this.atlasManager.getStats();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
enableAtlasRendering(enabled = true) {
|
|
630
|
+
this.useAtlasRendering = enabled;
|
|
631
|
+
console.log(`[MindMapModule] Atlas rendering ${enabled ? 'enabled' : 'disabled'}`);
|
|
632
|
+
}
|
|
633
|
+
// ▲▲▲ [New] ▲▲▲
|
|
634
|
+
|
|
635
|
+
// ▼▼▼ [New] LOD Renderer methods ▼▼▼
|
|
636
|
+
enableLOD(enabled = true) {
|
|
637
|
+
this.useLODRendering = enabled;
|
|
638
|
+
if (this.lodRenderer) {
|
|
639
|
+
if (enabled) {
|
|
640
|
+
// When enabling, generate thumbnails for existing nodes SYNCHRONOUSLY
|
|
641
|
+
const textureCache = window.MindMapNodes?.textureCache;
|
|
642
|
+
let generatedCount = 0;
|
|
643
|
+
this.nodeObjectsById.forEach((entry, nodeId) => {
|
|
644
|
+
// Try to get the source canvas from texture cache
|
|
645
|
+
const cached = textureCache?.get(nodeId);
|
|
646
|
+
if (cached?.default?.body?.image) {
|
|
647
|
+
const sourceCanvas = cached.default.body.image;
|
|
648
|
+
const model = entry.model;
|
|
649
|
+
const w = model?.width || 400;
|
|
650
|
+
const h = model?.height || 200;
|
|
651
|
+
// Use generateThumbnail directly (sync) instead of addNode (async queue)
|
|
652
|
+
this.lodRenderer.generateThumbnail(nodeId, sourceCanvas, w, h);
|
|
653
|
+
generatedCount++;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
console.log(`[MindMapModule] LOD enabled, generated ${generatedCount} thumbnails for existing nodes`);
|
|
657
|
+
} else {
|
|
658
|
+
// When disabling, make sure all individual nodes are visible
|
|
659
|
+
this.nodeObjectsById.forEach((entry) => {
|
|
660
|
+
if (entry.glObject) entry.glObject.visible = true;
|
|
661
|
+
});
|
|
662
|
+
if (this.lodRenderer.instancedMesh) {
|
|
663
|
+
this.lodRenderer.instancedMesh.visible = false;
|
|
664
|
+
}
|
|
665
|
+
this.lodRenderer.isInLODMode = false;
|
|
666
|
+
console.log(`[MindMapModule] LOD rendering disabled`);
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
console.warn('[MindMapModule] LODRenderer not initialized');
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
getLODStats() {
|
|
673
|
+
if (!this.lodRenderer) return null;
|
|
674
|
+
return this.lodRenderer.getStats();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
addNodeToLOD(nodeId, canvas, width, height) {
|
|
678
|
+
if (this.lodRenderer && canvas) {
|
|
679
|
+
this.lodRenderer.addNode(nodeId, canvas, width, height);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
removeNodeFromLOD(nodeId) {
|
|
684
|
+
if (this.lodRenderer) {
|
|
685
|
+
this.lodRenderer.removeNode(nodeId);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// ▲▲▲ [New] ▲▲▲
|
|
689
|
+
} // End MindMapModule class
|
|
690
|
+
|
|
691
|
+
window.mindMap = {
|
|
692
|
+
init: (id, h) => { if (!moduleInstance) moduleInstance = new MindMapModule(); moduleInstance.init(id, h); },
|
|
693
|
+
addNode: async (m) => { if (moduleInstance) await MindMapNodes.addNode(moduleInstance, m); },
|
|
694
|
+
// ▼▼▼ [Optimization] Batch processing to avoid blocking the UI thread ▼▼▼
|
|
695
|
+
addNodes: async (models) => {
|
|
696
|
+
if (!moduleInstance || !models || models.length === 0) return;
|
|
697
|
+
|
|
698
|
+
console.log(`[mindMap.addNodes] Starting batch add for ${models.length} nodes`);
|
|
699
|
+
const CHUNK_SIZE = 5; // Nodes per chunk
|
|
700
|
+
|
|
701
|
+
for (let i = 0; i < models.length; i += CHUNK_SIZE) {
|
|
702
|
+
const chunk = models.slice(i, i + CHUNK_SIZE);
|
|
703
|
+
console.log(`[mindMap.addNodes] Processing chunk ${Math.floor(i / CHUNK_SIZE) + 1}/${Math.ceil(models.length / CHUNK_SIZE)} (${chunk.length} nodes)`);
|
|
704
|
+
|
|
705
|
+
// Process nodes sequentially within the chunk (each node may do heavy work like texture creation)
|
|
706
|
+
for (const m of chunk) {
|
|
707
|
+
await MindMapNodes.addNode(moduleInstance, m);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Yield to the UI thread (allow one render cycle)
|
|
711
|
+
if (i + CHUNK_SIZE < models.length) {
|
|
712
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
console.log(`[mindMap.addNodes] Batch add completed for ${models.length} nodes`);
|
|
717
|
+
},
|
|
718
|
+
// ▲▲▲ [Optimization] ▲▲▲
|
|
719
|
+
removeNode: (id) => { if (moduleInstance) MindMapNodes.removeNode(moduleInstance, id); },
|
|
720
|
+
updateNodeContent: async (id, content, w = null, h = null, isLoading = null) => { if (moduleInstance) await MindMapNodes.updateNodeContent(moduleInstance, id, content, w, h, isLoading); },
|
|
721
|
+
// ▼▼▼ [Changed] Apply chunked batch processing to reduce browser load during bulk updates ▼▼▼
|
|
722
|
+
updateMultipleNodesContent: async (details) => {
|
|
723
|
+
if (!moduleInstance || !details || details.length === 0) return;
|
|
724
|
+
|
|
725
|
+
console.log(`[mindMap.updateMultipleNodesContent] Starting batch update for ${details.length} nodes`);
|
|
726
|
+
|
|
727
|
+
// Nodes per chunk (to reduce browser load)
|
|
728
|
+
// 5-10 is typically stable.
|
|
729
|
+
const CHUNK_SIZE = 5;
|
|
730
|
+
|
|
731
|
+
// Split the array into chunks
|
|
732
|
+
for (let i = 0; i < details.length; i += CHUNK_SIZE) {
|
|
733
|
+
const chunk = details.slice(i, i + CHUNK_SIZE);
|
|
734
|
+
|
|
735
|
+
// Run only the current chunk in parallel
|
|
736
|
+
const updatePromises = chunk.map(detail => {
|
|
737
|
+
return new Promise(async (resolve, reject) => {
|
|
738
|
+
const maxWaitTime = 2000; // Increased wait time: 1s -> 2s
|
|
739
|
+
const startTime = performance.now();
|
|
740
|
+
|
|
741
|
+
// Wait until the node object is created (polling)
|
|
742
|
+
while (!moduleInstance.nodeObjectsById.has(detail.nodeId) && (performance.now() - startTime < maxWaitTime)) {
|
|
743
|
+
await new Promise(r => setTimeout(r, 50));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (!moduleInstance.nodeObjectsById.has(detail.nodeId)) {
|
|
747
|
+
console.warn(`[mindMap] Timeout waiting for node: ${detail.nodeId}`);
|
|
748
|
+
// Resolve so the overall process continues even on failure
|
|
749
|
+
return resolve();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Perform the actual content (image) update
|
|
753
|
+
MindMapNodes.updateNodeContent(moduleInstance, detail.nodeId, detail.fileUrl, detail.width, detail.height)
|
|
754
|
+
.then(resolve)
|
|
755
|
+
.catch(err => {
|
|
756
|
+
console.warn(`[mindMap] Update failed for ${detail.nodeId}:`, err);
|
|
757
|
+
resolve(); // Resolve to keep going even if an error occurs
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// Wait for all work in the current chunk
|
|
763
|
+
await Promise.all(updatePromises);
|
|
764
|
+
|
|
765
|
+
// Yield to the UI thread (allow a render cycle)
|
|
766
|
+
if (i + CHUNK_SIZE < details.length) {
|
|
767
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
console.log(`[mindMap.updateMultipleNodesContent] Batch update completed for ${details.length} nodes`);
|
|
772
|
+
},
|
|
773
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
774
|
+
// ▼▼▼ [New] Add a function to sync selection state from C# to JS ▼▼▼
|
|
775
|
+
updateNodeSelection: (oldNodeId, newNodeId) => {
|
|
776
|
+
if (!moduleInstance) return;
|
|
777
|
+
|
|
778
|
+
// Convert empty strings to null
|
|
779
|
+
const actualOldId = oldNodeId || null;
|
|
780
|
+
const actualNewId = newNodeId || null;
|
|
781
|
+
|
|
782
|
+
console.log(`[mindMap.updateNodeSelection] old=${actualOldId}, new=${actualNewId}`);
|
|
783
|
+
|
|
784
|
+
// ▼▼▼ [Changed] Remove automatic WebGL switching logic ▼▼▼
|
|
785
|
+
// (This logic was moved to interactions.js blurActiveEditable() and
|
|
786
|
+
// the blur event handler in css3d-manager.js.)
|
|
787
|
+
/*
|
|
788
|
+
if (actualOldId && actualOldId !== actualNewId) {
|
|
789
|
+
const oldEntry = moduleInstance.nodeObjectsById.get(actualOldId);
|
|
790
|
+
if (oldEntry && oldEntry.model.contentType === 'note' && oldEntry.currentType === 'CSS') {
|
|
791
|
+
console.log(`[mindMap.updateNodeSelection] Switching old note ${actualOldId} back to WebGL`);
|
|
792
|
+
MindMapNodes.switchToWebGL(moduleInstance, actualOldId);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
*/
|
|
796
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
797
|
+
|
|
798
|
+
// 2. Update JS internal state
|
|
799
|
+
if (actualNewId) {
|
|
800
|
+
console.log(`[mindMap.updateNodeSelection] Setting selectedNodeIdJs to ${actualNewId}`);
|
|
801
|
+
MindMapNodes.clearMultiSelection(moduleInstance, actualNewId);
|
|
802
|
+
moduleInstance.selectedNodeIdJs = actualNewId;
|
|
803
|
+
MindMapNodes.updateNodeSelectionStyle(moduleInstance, actualNewId, true, true);
|
|
804
|
+
} else {
|
|
805
|
+
console.log(`[mindMap.updateNodeSelection] Clearing selection (null)`);
|
|
806
|
+
MindMapNodes.clearMultiSelection(moduleInstance, null);
|
|
807
|
+
moduleInstance.selectedNodeIdJs = null;
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
// ▲▲▲ [New] ▲▲▲
|
|
811
|
+
// ▼▼▼ [New] Add hybrid rendering helpers ▼▼▼
|
|
812
|
+
switchToCss3D: async (id) => { if (moduleInstance) await MindMapNodes.switchToCss3D(moduleInstance, id); },
|
|
813
|
+
switchToWebGL: async (id) => { if (moduleInstance) await MindMapNodes.switchToWebGL(moduleInstance, id); },
|
|
814
|
+
// ▲▲▲ [New] ▲▲▲
|
|
815
|
+
// ▼▼▼ [New] Convert node content type (e.g., text to note for editing) ▼▼▼
|
|
816
|
+
updateNodeContentType: async (id, newType) => { if (moduleInstance) await MindMapNodes.updateNodeContentType(moduleInstance, id, newType); },
|
|
817
|
+
// ▲▲▲ [New] ▲▲▲
|
|
818
|
+
|
|
819
|
+
// ▼▼▼ [Added] Pause/resume rendering ▼▼▼
|
|
820
|
+
pause: () => {
|
|
821
|
+
if (moduleInstance && moduleInstance.animationFrameId) {
|
|
822
|
+
cancelAnimationFrame(moduleInstance.animationFrameId);
|
|
823
|
+
moduleInstance.animationFrameId = null;
|
|
824
|
+
console.log('[MindMap] Animation paused.');
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
resume: () => {
|
|
828
|
+
if (moduleInstance && !moduleInstance.animationFrameId) {
|
|
829
|
+
moduleInstance.animate();
|
|
830
|
+
console.log('[MindMap] Animation resumed.');
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
// ▲▲▲ [Added] ▲▲▲
|
|
834
|
+
|
|
835
|
+
// ▼▼▼ [Changed] Expand camera & cursor state save/restore API ▼▼▼
|
|
836
|
+
getCameraState: () => {
|
|
837
|
+
if (moduleInstance) return moduleInstance.getCameraState();
|
|
838
|
+
return { x: 0, y: 0, z: 1200, cursorX: 0, cursorY: 0 };
|
|
839
|
+
},
|
|
840
|
+
setCameraState: (x, y, z, cx, cy) => {
|
|
841
|
+
if (moduleInstance) moduleInstance.setCameraState(x, y, z, cx, cy);
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
getNodePositions: () => {
|
|
845
|
+
if (moduleInstance) return moduleInstance.getNodePositions();
|
|
846
|
+
return [];
|
|
847
|
+
},
|
|
848
|
+
// ▲▲▲ [Changed] ▲▲▲
|
|
849
|
+
|
|
850
|
+
setBackgroundTheme: (name) => { if (moduleInstance) moduleInstance.setBackgroundTheme(name); },
|
|
851
|
+
hide: () => { if (moduleInstance) moduleInstance.hide(); },
|
|
852
|
+
toggleDebug: () => { if (moduleInstance) moduleInstance.toggleDebugLines(); },
|
|
853
|
+
cleanupAndDispose: () => { if (moduleInstance) { moduleInstance.cleanupAndDispose(); moduleInstance = null; } },
|
|
854
|
+
|
|
855
|
+
// ▼▼▼ [New] LOD Rendering API ▼▼▼
|
|
856
|
+
enableLOD: (enabled = true) => { if (moduleInstance) moduleInstance.enableLOD(enabled); },
|
|
857
|
+
getLODStats: () => { if (moduleInstance) return moduleInstance.getLODStats(); return null; },
|
|
858
|
+
// ▲▲▲ [New] ▲▲▲
|
|
859
|
+
|
|
860
|
+
// ▼▼▼ [New] Reset nodes on board switch (keep WebGL context) ▼▼▼
|
|
861
|
+
resetBoard: () => {
|
|
862
|
+
if (!moduleInstance) return;
|
|
863
|
+
console.log('[MindMap] resetBoard: Clearing all nodes for board switch...');
|
|
864
|
+
|
|
865
|
+
// 1. Remove all node objects from the scene
|
|
866
|
+
if (moduleInstance.nodeObjectsById) {
|
|
867
|
+
moduleInstance.nodeObjectsById.forEach((nodeEntry, nodeId) => {
|
|
868
|
+
try {
|
|
869
|
+
// Remove WebGL object
|
|
870
|
+
if (nodeEntry.glObject && moduleInstance.scene) {
|
|
871
|
+
moduleInstance.scene.remove(nodeEntry.glObject);
|
|
872
|
+
nodeEntry.glObject.traverse(child => {
|
|
873
|
+
if (child.geometry) child.geometry.dispose();
|
|
874
|
+
if (child.material) {
|
|
875
|
+
if (Array.isArray(child.material)) {
|
|
876
|
+
child.material.forEach(m => m.dispose());
|
|
877
|
+
} else {
|
|
878
|
+
child.material.dispose();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// Remove CSS3D object
|
|
884
|
+
if (nodeEntry.cssObject && moduleInstance.scene) {
|
|
885
|
+
moduleInstance.scene.remove(nodeEntry.cssObject);
|
|
886
|
+
}
|
|
887
|
+
} catch (e) {
|
|
888
|
+
console.warn(`[MindMap] Failed to remove node ${nodeId}:`, e);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// 2. Reset MindMapNodes state (texture cache, etc.)
|
|
894
|
+
MindMapNodes.resetState(moduleInstance);
|
|
895
|
+
|
|
896
|
+
console.log('[MindMap] resetBoard: All nodes cleared.');
|
|
897
|
+
},
|
|
898
|
+
// ▲▲▲ [New] ▲▲▲
|
|
899
|
+
|
|
900
|
+
// ▼▼▼ [New] Hide canvases immediately before window resize ▼▼▼
|
|
901
|
+
hideForResize: () => {
|
|
902
|
+
if (!moduleInstance) return;
|
|
903
|
+
moduleInstance.isWindowResizing = true;
|
|
904
|
+
|
|
905
|
+
// Fully hide canvases
|
|
906
|
+
if (moduleInstance.renderer && moduleInstance.renderer.domElement) {
|
|
907
|
+
moduleInstance.renderer.domElement.style.visibility = 'hidden';
|
|
908
|
+
}
|
|
909
|
+
if (moduleInstance.cssRenderer && moduleInstance.cssRenderer.domElement) {
|
|
910
|
+
moduleInstance.cssRenderer.domElement.style.visibility = 'hidden';
|
|
911
|
+
}
|
|
912
|
+
// Set a white background on the container
|
|
913
|
+
if (moduleInstance.container) {
|
|
914
|
+
moduleInstance.container.style.backgroundColor = 'white';
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
// ▲▲▲ [New] ▲▲▲
|
|
918
|
+
|
|
919
|
+
// ▼▼▼ [New] Settings application API ▼▼▼
|
|
920
|
+
applySettings: (settings) => {
|
|
921
|
+
if (!moduleInstance) return;
|
|
922
|
+
console.log('[MindMap] Applying settings:', settings);
|
|
923
|
+
|
|
924
|
+
// Enable/disable grid snapping
|
|
925
|
+
if (settings.gridSnapEnabled !== undefined) {
|
|
926
|
+
moduleInstance.gridSnapEnabled = settings.gridSnapEnabled;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Change grid type
|
|
930
|
+
if (settings.gridType) {
|
|
931
|
+
const themeMap = {
|
|
932
|
+
'space3d': 'Space3D',
|
|
933
|
+
'default': 'XYGrid',
|
|
934
|
+
'dot': 'SpatialGrid2D'
|
|
935
|
+
};
|
|
936
|
+
const themeName = themeMap[settings.gridType] || 'Space3D';
|
|
937
|
+
moduleInstance.setBackgroundTheme(themeName);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Camera trace mode
|
|
941
|
+
if (settings.cameraTraceMode !== undefined) {
|
|
942
|
+
moduleInstance.cameraTraceMode = settings.cameraTraceMode;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// ▼▼▼ [New] Default AI node size settings ▼▼▼
|
|
946
|
+
if (settings.aiNodeDefaultWidth !== undefined) {
|
|
947
|
+
moduleInstance.aiNodeDefaultWidth = settings.aiNodeDefaultWidth;
|
|
948
|
+
}
|
|
949
|
+
if (settings.aiNodeDefaultHeight !== undefined) {
|
|
950
|
+
moduleInstance.aiNodeDefaultHeight = settings.aiNodeDefaultHeight;
|
|
951
|
+
}
|
|
952
|
+
// Show full AI response (no height limit)
|
|
953
|
+
if (settings.showFullAiResponse !== undefined) {
|
|
954
|
+
moduleInstance.showFullAiResponse = settings.showFullAiResponse;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Cursor direction (horizontal / vertical)
|
|
958
|
+
if (settings.cursorDirection !== undefined) {
|
|
959
|
+
moduleInstance.cursorDirection = settings.cursorDirection;
|
|
960
|
+
}
|
|
961
|
+
// ▲▲▲ [New] ▲▲▲
|
|
962
|
+
},
|
|
963
|
+
|
|
964
|
+
// Get current settings
|
|
965
|
+
getSettings: () => {
|
|
966
|
+
if (!moduleInstance) return {};
|
|
967
|
+
return {
|
|
968
|
+
gridSnapEnabled: moduleInstance.gridSnapEnabled !== false, // Default: true
|
|
969
|
+
gridType: moduleInstance.currentThemeName === 'SpatialGrid2D' ? 'dot' : 'default',
|
|
970
|
+
cameraTraceMode: moduleInstance.cameraTraceMode || 'cursor',
|
|
971
|
+
aiNodeDefaultWidth: moduleInstance.aiNodeDefaultWidth || 400,
|
|
972
|
+
aiNodeDefaultHeight: moduleInstance.aiNodeDefaultHeight || 200,
|
|
973
|
+
showFullAiResponse: moduleInstance.showFullAiResponse || false,
|
|
974
|
+
cursorDirection: moduleInstance.cursorDirection || 'horizontal'
|
|
975
|
+
};
|
|
976
|
+
},
|
|
977
|
+
// ▲▲▲ [New] ▲▲▲
|
|
978
|
+
|
|
979
|
+
// ▼▼▼ [New] Helper functions for native drop ▼▼▼
|
|
980
|
+
// Screen coordinates -> world coordinates
|
|
981
|
+
screenToWorld: (screenX, screenY) => {
|
|
982
|
+
if (!moduleInstance || !moduleInstance.camera || !moduleInstance.container) {
|
|
983
|
+
return [0, 0];
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const container = moduleInstance.container;
|
|
987
|
+
const camera = moduleInstance.camera;
|
|
988
|
+
|
|
989
|
+
// Compute NDC coordinates
|
|
990
|
+
const ndcX = (screenX / container.clientWidth) * 2 - 1;
|
|
991
|
+
const ndcY = -(screenY / container.clientHeight) * 2 + 1;
|
|
992
|
+
|
|
993
|
+
// Compute view extents
|
|
994
|
+
const vfov = (camera.fov * Math.PI) / 180;
|
|
995
|
+
const viewHeight = 2 * Math.tan(vfov / 2) * camera.position.z;
|
|
996
|
+
const viewWidth = viewHeight * camera.aspect;
|
|
997
|
+
|
|
998
|
+
// Compute world coordinates
|
|
999
|
+
const worldX = camera.position.x + (ndcX * viewWidth) / 2;
|
|
1000
|
+
const worldY = camera.position.y + (ndcY * viewHeight) / 2;
|
|
1001
|
+
|
|
1002
|
+
return [worldX, worldY];
|
|
1003
|
+
},
|
|
1004
|
+
|
|
1005
|
+
// Measure image size (for C# native drop)
|
|
1006
|
+
measureImageSize: (src) => {
|
|
1007
|
+
return new Promise((resolve) => {
|
|
1008
|
+
const img = new Image();
|
|
1009
|
+
img.onload = () => {
|
|
1010
|
+
resolve([img.naturalWidth, img.naturalHeight]);
|
|
1011
|
+
};
|
|
1012
|
+
img.onerror = () => {
|
|
1013
|
+
resolve([400, 300]); // Default
|
|
1014
|
+
};
|
|
1015
|
+
img.src = src;
|
|
1016
|
+
});
|
|
1017
|
+
},
|
|
1018
|
+
|
|
1019
|
+
// Create Blob URL from byte array (much faster than base64)
|
|
1020
|
+
createBlobUrl: (bytes, mimeType) => {
|
|
1021
|
+
try {
|
|
1022
|
+
const blob = new Blob([bytes], { type: mimeType });
|
|
1023
|
+
const url = URL.createObjectURL(blob);
|
|
1024
|
+
console.log(`[mindMap.createBlobUrl] Created Blob URL (${Math.round(bytes.length / 1024)}KB, ${mimeType})`);
|
|
1025
|
+
return url;
|
|
1026
|
+
} catch (ex) {
|
|
1027
|
+
console.error(`[mindMap.createBlobUrl] Failed: ${ex}`);
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
// Revoke Blob URL to free memory
|
|
1033
|
+
revokeBlobUrl: (url) => {
|
|
1034
|
+
if (url && url.startsWith('blob:')) {
|
|
1035
|
+
URL.revokeObjectURL(url);
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
// ▲▲▲ [New] ▲▲▲
|
|
1039
|
+
|
|
1040
|
+
// ▼▼▼ [New] Atlas rendering API ▼▼▼
|
|
1041
|
+
initAtlas: (options) => {
|
|
1042
|
+
if (moduleInstance) return moduleInstance.initAtlasManager(options);
|
|
1043
|
+
return null;
|
|
1044
|
+
},
|
|
1045
|
+
getAtlasStats: () => {
|
|
1046
|
+
if (moduleInstance) return moduleInstance.getAtlasStats();
|
|
1047
|
+
return null;
|
|
1048
|
+
},
|
|
1049
|
+
enableAtlasRendering: (enabled) => {
|
|
1050
|
+
if (moduleInstance) moduleInstance.enableAtlasRendering(enabled);
|
|
1051
|
+
}
|
|
1052
|
+
// ▲▲▲ [New] ▲▲▲
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// Backward-compatible alias: MindCanvas is the new page/internal name, but the underlying surface is the same.
|
|
1056
|
+
window.mindCanvas = window.mindMap;
|
|
1057
|
+
|
|
1058
|
+
console.log('? mind-map-core.js loaded (simplified, style queue removed).');
|
|
1059
|
+
})();
|