@nice2dev/ui-3d 1.0.0 → 1.0.2

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 (204) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/dist/cjs/collaborative/collaborativeScene.js +210 -0
  3. package/dist/cjs/collaborative/collaborativeScene.js.map +1 -0
  4. package/dist/cjs/core/i18n.js +3 -3
  5. package/dist/cjs/core/i18n.js.map +1 -1
  6. package/dist/cjs/dance/DanceBridge.js +162 -0
  7. package/dist/cjs/dance/DanceBridge.js.map +1 -0
  8. package/dist/cjs/dance/DanceScoreEngine.js +210 -0
  9. package/dist/cjs/dance/DanceScoreEngine.js.map +1 -0
  10. package/dist/cjs/dance/PoseDetector.js +199 -0
  11. package/dist/cjs/dance/PoseDetector.js.map +1 -0
  12. package/dist/cjs/index.js +254 -0
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/material/MaterialEditor.module.css.js +6 -0
  15. package/dist/cjs/material/MaterialEditor.module.css.js.map +1 -0
  16. package/dist/cjs/material/NiceMaterialEditor.js +737 -0
  17. package/dist/cjs/material/NiceMaterialEditor.js.map +1 -0
  18. package/dist/cjs/material/materialEditorTypes.js +73 -0
  19. package/dist/cjs/material/materialEditorTypes.js.map +1 -0
  20. package/dist/cjs/material/materialEditorUtils.js +841 -0
  21. package/dist/cjs/material/materialEditorUtils.js.map +1 -0
  22. package/dist/cjs/material/materialNodeDefinitions.js +1285 -0
  23. package/dist/cjs/material/materialNodeDefinitions.js.map +1 -0
  24. package/dist/cjs/model/ModelEditor.js +4 -1
  25. package/dist/cjs/model/ModelEditor.js.map +1 -1
  26. package/dist/cjs/model/ModelEditor.module.css.js +1 -1
  27. package/dist/cjs/model/ModelEditorLeftPanel.js +5 -4
  28. package/dist/cjs/model/ModelEditorLeftPanel.js.map +1 -1
  29. package/dist/cjs/model/ModelEditorMenuBar.js +8 -3
  30. package/dist/cjs/model/ModelEditorMenuBar.js.map +1 -1
  31. package/dist/cjs/model/ModelEditorRightPanel.js +27 -26
  32. package/dist/cjs/model/ModelEditorRightPanel.js.map +1 -1
  33. package/dist/cjs/model/ModelEditorSubComponents.js +20 -16
  34. package/dist/cjs/model/ModelEditorSubComponents.js.map +1 -1
  35. package/dist/cjs/model/ModelEditorTimeline.js +5 -4
  36. package/dist/cjs/model/ModelEditorTimeline.js.map +1 -1
  37. package/dist/cjs/model/ModelEditorToolbar.js +4 -3
  38. package/dist/cjs/model/ModelEditorToolbar.js.map +1 -1
  39. package/dist/cjs/model/ModelEditorViewport.js +2 -2
  40. package/dist/cjs/model/ModelEditorViewport.js.map +1 -1
  41. package/dist/cjs/model/ModelViewer.js +68 -0
  42. package/dist/cjs/model/ModelViewer.js.map +1 -0
  43. package/dist/cjs/model/ModelViewer.module.css.js +6 -0
  44. package/dist/cjs/model/ModelViewer.module.css.js.map +1 -0
  45. package/dist/cjs/model/NiceArmatureEditor.js +255 -0
  46. package/dist/cjs/model/NiceArmatureEditor.js.map +1 -0
  47. package/dist/cjs/model/NiceMorphTargetEditor.js +206 -0
  48. package/dist/cjs/model/NiceMorphTargetEditor.js.map +1 -0
  49. package/dist/cjs/model/NiceOctree.js +339 -0
  50. package/dist/cjs/model/NiceOctree.js.map +1 -0
  51. package/dist/cjs/model/NicePhysicsSimulation.js +283 -0
  52. package/dist/cjs/model/NicePhysicsSimulation.js.map +1 -0
  53. package/dist/cjs/model/NiceProceduralGeometry.js +269 -0
  54. package/dist/cjs/model/NiceProceduralGeometry.js.map +1 -0
  55. package/dist/cjs/model/NiceTerrainEditor.js +343 -0
  56. package/dist/cjs/model/NiceTerrainEditor.js.map +1 -0
  57. package/dist/cjs/model/NiceWeightPainter.js +258 -0
  58. package/dist/cjs/model/NiceWeightPainter.js.map +1 -0
  59. package/dist/cjs/model/NiceXRPreview.js +269 -0
  60. package/dist/cjs/model/NiceXRPreview.js.map +1 -0
  61. package/dist/cjs/model/cadModeUtils.js +130 -0
  62. package/dist/cjs/model/cadModeUtils.js.map +1 -0
  63. package/dist/cjs/model/editorShortcuts.js +187 -0
  64. package/dist/cjs/model/editorShortcuts.js.map +1 -0
  65. package/dist/cjs/model/modelEditorTypes.js +11 -0
  66. package/dist/cjs/model/modelEditorTypes.js.map +1 -1
  67. package/dist/cjs/model/modelEditorUtils.js +1049 -0
  68. package/dist/cjs/model/modelEditorUtils.js.map +1 -0
  69. package/dist/cjs/model/simsModeUtils.js +358 -0
  70. package/dist/cjs/model/simsModeUtils.js.map +1 -0
  71. package/dist/cjs/model/useModelEditor.js +319 -115
  72. package/dist/cjs/model/useModelEditor.js.map +1 -1
  73. package/dist/cjs/model/useModelViewer.js +634 -0
  74. package/dist/cjs/model/useModelViewer.js.map +1 -0
  75. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  76. package/dist/cjs/particle/NiceParticleEditor.js +526 -0
  77. package/dist/cjs/particle/NiceParticleEditor.js.map +1 -0
  78. package/dist/cjs/particle/ParticleEditor.module.css.js +6 -0
  79. package/dist/cjs/particle/ParticleEditor.module.css.js.map +1 -0
  80. package/dist/cjs/particle/particleEditorTypes.js +92 -0
  81. package/dist/cjs/particle/particleEditorTypes.js.map +1 -0
  82. package/dist/cjs/particle/particleEditorUtils.js +1084 -0
  83. package/dist/cjs/particle/particleEditorUtils.js.map +1 -0
  84. package/dist/cjs/rendering/NiceCascadedShadows.js +266 -0
  85. package/dist/cjs/rendering/NiceCascadedShadows.js.map +1 -0
  86. package/dist/cjs/rendering/NiceRenderExport.js +341 -0
  87. package/dist/cjs/rendering/NiceRenderExport.js.map +1 -0
  88. package/dist/cjs/rendering/NiceSSAO.js +359 -0
  89. package/dist/cjs/rendering/NiceSSAO.js.map +1 -0
  90. package/dist/cjs/rendering/NiceSSR.js +277 -0
  91. package/dist/cjs/rendering/NiceSSR.js.map +1 -0
  92. package/dist/cjs/rendering/NiceWebGPURenderer.js +215 -0
  93. package/dist/cjs/rendering/NiceWebGPURenderer.js.map +1 -0
  94. package/dist/cjs/ui/dist/index.js +50089 -0
  95. package/dist/cjs/ui/dist/index.js.map +1 -0
  96. package/dist/cjs/uv/NiceUVEditor.js +520 -0
  97. package/dist/cjs/uv/NiceUVEditor.js.map +1 -0
  98. package/dist/cjs/uv/UVEditor.module.css.js +6 -0
  99. package/dist/cjs/uv/UVEditor.module.css.js.map +1 -0
  100. package/dist/cjs/uv/uvEditorTypes.js +98 -0
  101. package/dist/cjs/uv/uvEditorTypes.js.map +1 -0
  102. package/dist/cjs/uv/uvEditorUtils.js +670 -0
  103. package/dist/cjs/uv/uvEditorUtils.js.map +1 -0
  104. package/dist/esm/collaborative/collaborativeScene.js +206 -0
  105. package/dist/esm/collaborative/collaborativeScene.js.map +1 -0
  106. package/dist/esm/dance/DanceBridge.js +158 -0
  107. package/dist/esm/dance/DanceBridge.js.map +1 -0
  108. package/dist/esm/dance/DanceScoreEngine.js +207 -0
  109. package/dist/esm/dance/DanceScoreEngine.js.map +1 -0
  110. package/dist/esm/dance/PoseDetector.js +195 -0
  111. package/dist/esm/dance/PoseDetector.js.map +1 -0
  112. package/dist/esm/index.js +35 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/material/MaterialEditor.module.css.js +4 -0
  115. package/dist/esm/material/MaterialEditor.module.css.js.map +1 -0
  116. package/dist/esm/material/NiceMaterialEditor.js +734 -0
  117. package/dist/esm/material/NiceMaterialEditor.js.map +1 -0
  118. package/dist/esm/material/materialEditorTypes.js +62 -0
  119. package/dist/esm/material/materialEditorTypes.js.map +1 -0
  120. package/dist/esm/material/materialEditorUtils.js +811 -0
  121. package/dist/esm/material/materialEditorUtils.js.map +1 -0
  122. package/dist/esm/material/materialNodeDefinitions.js +1280 -0
  123. package/dist/esm/material/materialNodeDefinitions.js.map +1 -0
  124. package/dist/esm/model/ModelEditor.js +4 -2
  125. package/dist/esm/model/ModelEditor.js.map +1 -1
  126. package/dist/esm/model/ModelEditor.module.css.js +1 -1
  127. package/dist/esm/model/ModelEditorLeftPanel.js +5 -4
  128. package/dist/esm/model/ModelEditorLeftPanel.js.map +1 -1
  129. package/dist/esm/model/ModelEditorMenuBar.js +8 -3
  130. package/dist/esm/model/ModelEditorMenuBar.js.map +1 -1
  131. package/dist/esm/model/ModelEditorRightPanel.js +27 -26
  132. package/dist/esm/model/ModelEditorRightPanel.js.map +1 -1
  133. package/dist/esm/model/ModelEditorSubComponents.js +17 -13
  134. package/dist/esm/model/ModelEditorSubComponents.js.map +1 -1
  135. package/dist/esm/model/ModelEditorTimeline.js +5 -4
  136. package/dist/esm/model/ModelEditorTimeline.js.map +1 -1
  137. package/dist/esm/model/ModelEditorToolbar.js +4 -3
  138. package/dist/esm/model/ModelEditorToolbar.js.map +1 -1
  139. package/dist/esm/model/ModelEditorViewport.js +2 -2
  140. package/dist/esm/model/ModelEditorViewport.js.map +1 -1
  141. package/dist/esm/model/ModelViewer.js +65 -0
  142. package/dist/esm/model/ModelViewer.js.map +1 -0
  143. package/dist/esm/model/ModelViewer.module.css.js +4 -0
  144. package/dist/esm/model/ModelViewer.module.css.js.map +1 -0
  145. package/dist/esm/model/NiceArmatureEditor.js +233 -0
  146. package/dist/esm/model/NiceArmatureEditor.js.map +1 -0
  147. package/dist/esm/model/NiceMorphTargetEditor.js +184 -0
  148. package/dist/esm/model/NiceMorphTargetEditor.js.map +1 -0
  149. package/dist/esm/model/NiceOctree.js +317 -0
  150. package/dist/esm/model/NiceOctree.js.map +1 -0
  151. package/dist/esm/model/NicePhysicsSimulation.js +261 -0
  152. package/dist/esm/model/NicePhysicsSimulation.js.map +1 -0
  153. package/dist/esm/model/NiceProceduralGeometry.js +242 -0
  154. package/dist/esm/model/NiceProceduralGeometry.js.map +1 -0
  155. package/dist/esm/model/NiceTerrainEditor.js +321 -0
  156. package/dist/esm/model/NiceTerrainEditor.js.map +1 -0
  157. package/dist/esm/model/NiceWeightPainter.js +236 -0
  158. package/dist/esm/model/NiceWeightPainter.js.map +1 -0
  159. package/dist/esm/model/NiceXRPreview.js +247 -0
  160. package/dist/esm/model/NiceXRPreview.js.map +1 -0
  161. package/dist/esm/model/cadModeUtils.js +103 -0
  162. package/dist/esm/model/cadModeUtils.js.map +1 -0
  163. package/dist/esm/model/editorShortcuts.js +185 -0
  164. package/dist/esm/model/editorShortcuts.js.map +1 -0
  165. package/dist/esm/model/modelEditorTypes.js +11 -0
  166. package/dist/esm/model/modelEditorTypes.js.map +1 -1
  167. package/dist/esm/model/modelEditorUtils.js +997 -0
  168. package/dist/esm/model/modelEditorUtils.js.map +1 -0
  169. package/dist/esm/model/simsModeUtils.js +325 -0
  170. package/dist/esm/model/simsModeUtils.js.map +1 -0
  171. package/dist/esm/model/useModelEditor.js +204 -0
  172. package/dist/esm/model/useModelEditor.js.map +1 -1
  173. package/dist/esm/model/useModelViewer.js +613 -0
  174. package/dist/esm/model/useModelViewer.js.map +1 -0
  175. package/dist/esm/nice2dev-ui-3d.css +1 -1
  176. package/dist/esm/particle/NiceParticleEditor.js +523 -0
  177. package/dist/esm/particle/NiceParticleEditor.js.map +1 -0
  178. package/dist/esm/particle/ParticleEditor.module.css.js +4 -0
  179. package/dist/esm/particle/ParticleEditor.module.css.js.map +1 -0
  180. package/dist/esm/particle/particleEditorTypes.js +84 -0
  181. package/dist/esm/particle/particleEditorTypes.js.map +1 -0
  182. package/dist/esm/particle/particleEditorUtils.js +1054 -0
  183. package/dist/esm/particle/particleEditorUtils.js.map +1 -0
  184. package/dist/esm/rendering/NiceCascadedShadows.js +244 -0
  185. package/dist/esm/rendering/NiceCascadedShadows.js.map +1 -0
  186. package/dist/esm/rendering/NiceRenderExport.js +319 -0
  187. package/dist/esm/rendering/NiceRenderExport.js.map +1 -0
  188. package/dist/esm/rendering/NiceSSAO.js +337 -0
  189. package/dist/esm/rendering/NiceSSAO.js.map +1 -0
  190. package/dist/esm/rendering/NiceSSR.js +255 -0
  191. package/dist/esm/rendering/NiceSSR.js.map +1 -0
  192. package/dist/esm/rendering/NiceWebGPURenderer.js +193 -0
  193. package/dist/esm/rendering/NiceWebGPURenderer.js.map +1 -0
  194. package/dist/esm/ui/dist/index.js +49686 -0
  195. package/dist/esm/ui/dist/index.js.map +1 -0
  196. package/dist/esm/uv/NiceUVEditor.js +518 -0
  197. package/dist/esm/uv/NiceUVEditor.js.map +1 -0
  198. package/dist/esm/uv/UVEditor.module.css.js +4 -0
  199. package/dist/esm/uv/UVEditor.module.css.js.map +1 -0
  200. package/dist/esm/uv/uvEditorTypes.js +88 -0
  201. package/dist/esm/uv/uvEditorTypes.js.map +1 -0
  202. package/dist/esm/uv/uvEditorUtils.js +621 -0
  203. package/dist/esm/uv/uvEditorUtils.js.map +1 -0
  204. package/package.json +3 -4
@@ -0,0 +1,737 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var Ae = require('react');
5
+ var materialEditorTypes = require('./materialEditorTypes.js');
6
+ var materialNodeDefinitions = require('./materialNodeDefinitions.js');
7
+ var materialEditorUtils = require('./materialEditorUtils.js');
8
+ var MaterialEditor_module = require('./MaterialEditor.module.css.js');
9
+
10
+ /* ═══════════════════════════════════════════════════════════════════════════
11
+ State Reducer
12
+ ═══════════════════════════════════════════════════════════════════════════ */
13
+ function createInitialState(graph) {
14
+ const initialGraph = graph || materialEditorUtils.createEmptyGraph();
15
+ return {
16
+ graph: initialGraph,
17
+ selectedNodes: [],
18
+ selectedConnections: [],
19
+ clipboard: null,
20
+ viewTransform: { x: 100, y: 100, zoom: 1 },
21
+ previewMode: 'sphere',
22
+ showGrid: true,
23
+ showMinimap: true,
24
+ snapToGrid: true,
25
+ gridSize: materialEditorTypes.DEFAULT_GRID_SIZE,
26
+ undoStack: [],
27
+ redoStack: [],
28
+ compiledShader: null,
29
+ compiling: false,
30
+ autoCompile: true,
31
+ searchQuery: '',
32
+ expandedCategories: ['input', 'output', 'math', 'color', 'texture'],
33
+ };
34
+ }
35
+ function pushToUndoStack(state) {
36
+ return {
37
+ ...state,
38
+ undoStack: [...state.undoStack.slice(-49), state.graph],
39
+ redoStack: [],
40
+ };
41
+ }
42
+ function materialEditorReducer(state, action) {
43
+ switch (action.type) {
44
+ case 'ADD_NODE': {
45
+ const node = materialEditorUtils.createNode(action.nodeType, action.position);
46
+ if (!node)
47
+ return state;
48
+ const newState = pushToUndoStack(state);
49
+ return {
50
+ ...newState,
51
+ graph: materialEditorUtils.addNodeToGraph(newState.graph, node),
52
+ selectedNodes: [node.id],
53
+ };
54
+ }
55
+ case 'DELETE_NODES': {
56
+ const newState = pushToUndoStack(state);
57
+ return {
58
+ ...newState,
59
+ graph: materialEditorUtils.removeNodesFromGraph(newState.graph, action.nodeIds),
60
+ selectedNodes: [],
61
+ selectedConnections: [],
62
+ };
63
+ }
64
+ case 'MOVE_NODES': {
65
+ return {
66
+ ...state,
67
+ graph: materialEditorUtils.moveNodesInGraph(state.graph, action.nodeIds, action.delta),
68
+ };
69
+ }
70
+ case 'UPDATE_NODE_PROPERTY': {
71
+ const newState = pushToUndoStack(state);
72
+ const node = newState.graph.nodes.find(n => n.id === action.nodeId);
73
+ if (!node)
74
+ return state;
75
+ return {
76
+ ...newState,
77
+ graph: materialEditorUtils.updateNodeInGraph(newState.graph, action.nodeId, {
78
+ properties: { ...node.properties, [action.propertyId]: action.value },
79
+ }),
80
+ };
81
+ }
82
+ case 'UPDATE_INPUT_VALUE': {
83
+ const newState = pushToUndoStack(state);
84
+ const node = newState.graph.nodes.find(n => n.id === action.nodeId);
85
+ if (!node)
86
+ return state;
87
+ return {
88
+ ...newState,
89
+ graph: materialEditorUtils.updateNodeInGraph(newState.graph, action.nodeId, {
90
+ inputValues: { ...node.inputValues, [action.portId]: action.value },
91
+ }),
92
+ };
93
+ }
94
+ case 'CONNECT': {
95
+ const sourceNode = state.graph.nodes.find(n => n.id === action.connection.sourceNodeId);
96
+ const targetNode = state.graph.nodes.find(n => n.id === action.connection.targetNodeId);
97
+ if (!sourceNode || !targetNode)
98
+ return state;
99
+ const sourceDef = materialEditorUtils.getNodeDefinition(sourceNode);
100
+ const targetDef = materialEditorUtils.getNodeDefinition(targetNode);
101
+ if (!sourceDef || !targetDef)
102
+ return state;
103
+ const sourcePort = sourceDef.outputs.find(p => p.id === action.connection.sourcePortId);
104
+ const targetPort = targetDef.inputs.find(p => p.id === action.connection.targetPortId);
105
+ if (!sourcePort || !targetPort)
106
+ return state;
107
+ const { valid } = materialEditorUtils.canConnect(sourceNode, sourcePort, targetNode, targetPort, state.graph.connections);
108
+ if (!valid)
109
+ return state;
110
+ const newState = pushToUndoStack(state);
111
+ const connection = materialEditorUtils.createConnection(action.connection.sourceNodeId, action.connection.sourcePortId, action.connection.targetNodeId, action.connection.targetPortId);
112
+ return {
113
+ ...newState,
114
+ graph: materialEditorUtils.addConnectionToGraph(newState.graph, connection),
115
+ };
116
+ }
117
+ case 'DISCONNECT': {
118
+ const newState = pushToUndoStack(state);
119
+ return {
120
+ ...newState,
121
+ graph: materialEditorUtils.removeConnectionFromGraph(newState.graph, action.connectionId),
122
+ selectedConnections: state.selectedConnections.filter(id => id !== action.connectionId),
123
+ };
124
+ }
125
+ case 'SELECT_NODES': {
126
+ return {
127
+ ...state,
128
+ selectedNodes: action.additive
129
+ ? [...new Set([...state.selectedNodes, ...action.nodeIds])]
130
+ : action.nodeIds,
131
+ selectedConnections: action.additive ? state.selectedConnections : [],
132
+ };
133
+ }
134
+ case 'SELECT_CONNECTIONS': {
135
+ return {
136
+ ...state,
137
+ selectedConnections: action.additive
138
+ ? [...new Set([...state.selectedConnections, ...action.connectionIds])]
139
+ : action.connectionIds,
140
+ selectedNodes: action.additive ? state.selectedNodes : [],
141
+ };
142
+ }
143
+ case 'SELECT_ALL': {
144
+ return {
145
+ ...state,
146
+ selectedNodes: state.graph.nodes.map(n => n.id),
147
+ selectedConnections: state.graph.connections.map(c => c.id),
148
+ };
149
+ }
150
+ case 'DESELECT_ALL': {
151
+ return {
152
+ ...state,
153
+ selectedNodes: [],
154
+ selectedConnections: [],
155
+ };
156
+ }
157
+ case 'COPY': {
158
+ const nodesToCopy = state.graph.nodes.filter(n => state.selectedNodes.includes(n.id));
159
+ const connectionsToCopy = state.graph.connections.filter(c => state.selectedNodes.includes(c.sourceNodeId) && state.selectedNodes.includes(c.targetNodeId));
160
+ return {
161
+ ...state,
162
+ clipboard: { nodes: nodesToCopy, connections: connectionsToCopy },
163
+ };
164
+ }
165
+ case 'PASTE': {
166
+ if (!state.clipboard)
167
+ return state;
168
+ const newState = pushToUndoStack(state);
169
+ const idMap = new Map();
170
+ const newNodes = state.clipboard.nodes.map(node => {
171
+ const newId = materialEditorUtils.generateNodeId();
172
+ idMap.set(node.id, newId);
173
+ return {
174
+ ...node,
175
+ id: newId,
176
+ position: {
177
+ x: node.position.x + action.position.x - state.clipboard.nodes[0].position.x + 20,
178
+ y: node.position.y + action.position.y - state.clipboard.nodes[0].position.y + 20,
179
+ },
180
+ selected: false,
181
+ };
182
+ });
183
+ const newConnections = state.clipboard.connections.map(conn => ({
184
+ ...conn,
185
+ id: materialEditorUtils.generateNodeId(),
186
+ sourceNodeId: idMap.get(conn.sourceNodeId),
187
+ targetNodeId: idMap.get(conn.targetNodeId),
188
+ }));
189
+ return {
190
+ ...newState,
191
+ graph: {
192
+ ...newState.graph,
193
+ nodes: [...newState.graph.nodes, ...newNodes],
194
+ connections: [...newState.graph.connections, ...newConnections],
195
+ },
196
+ selectedNodes: newNodes.map(n => n.id),
197
+ };
198
+ }
199
+ case 'CUT': {
200
+ const nodesToCut = state.graph.nodes.filter(n => state.selectedNodes.includes(n.id) && n.id !== state.graph.outputNodeId);
201
+ const connectionsToCut = state.graph.connections.filter(c => state.selectedNodes.includes(c.sourceNodeId) && state.selectedNodes.includes(c.targetNodeId));
202
+ const newState = pushToUndoStack(state);
203
+ return {
204
+ ...newState,
205
+ clipboard: { nodes: nodesToCut, connections: connectionsToCut },
206
+ graph: materialEditorUtils.removeNodesFromGraph(newState.graph, nodesToCut.map(n => n.id)),
207
+ selectedNodes: [],
208
+ };
209
+ }
210
+ case 'DUPLICATE': {
211
+ const nodesToDupe = state.graph.nodes.filter(n => state.selectedNodes.includes(n.id) && n.id !== state.graph.outputNodeId);
212
+ const newState = pushToUndoStack(state);
213
+ const idMap = new Map();
214
+ const newNodes = nodesToDupe.map(node => {
215
+ const cloned = materialEditorUtils.cloneNode(node);
216
+ idMap.set(node.id, cloned.id);
217
+ return cloned;
218
+ });
219
+ const connectionsToDupe = state.graph.connections.filter(c => state.selectedNodes.includes(c.sourceNodeId) && state.selectedNodes.includes(c.targetNodeId));
220
+ const newConnections = connectionsToDupe.map(conn => ({
221
+ ...conn,
222
+ id: materialEditorUtils.generateNodeId(),
223
+ sourceNodeId: idMap.get(conn.sourceNodeId),
224
+ targetNodeId: idMap.get(conn.targetNodeId),
225
+ }));
226
+ return {
227
+ ...newState,
228
+ graph: {
229
+ ...newState.graph,
230
+ nodes: [...newState.graph.nodes, ...newNodes],
231
+ connections: [...newState.graph.connections, ...newConnections],
232
+ },
233
+ selectedNodes: newNodes.map(n => n.id),
234
+ };
235
+ }
236
+ case 'UNDO': {
237
+ if (state.undoStack.length === 0)
238
+ return state;
239
+ const previous = state.undoStack[state.undoStack.length - 1];
240
+ return {
241
+ ...state,
242
+ graph: previous,
243
+ undoStack: state.undoStack.slice(0, -1),
244
+ redoStack: [...state.redoStack, state.graph],
245
+ selectedNodes: [],
246
+ selectedConnections: [],
247
+ };
248
+ }
249
+ case 'REDO': {
250
+ if (state.redoStack.length === 0)
251
+ return state;
252
+ const next = state.redoStack[state.redoStack.length - 1];
253
+ return {
254
+ ...state,
255
+ graph: next,
256
+ undoStack: [...state.undoStack, state.graph],
257
+ redoStack: state.redoStack.slice(0, -1),
258
+ selectedNodes: [],
259
+ selectedConnections: [],
260
+ };
261
+ }
262
+ case 'SET_VIEW_TRANSFORM':
263
+ return { ...state, viewTransform: action.transform };
264
+ case 'ZOOM_TO_FIT': {
265
+ const transform = materialEditorUtils.calculateZoomToFit(state.graph.nodes, 800, 600);
266
+ return { ...state, viewTransform: transform };
267
+ }
268
+ case 'SET_PREVIEW_MODE':
269
+ return { ...state, previewMode: action.mode };
270
+ case 'TOGGLE_GRID':
271
+ return { ...state, showGrid: !state.showGrid };
272
+ case 'TOGGLE_MINIMAP':
273
+ return { ...state, showMinimap: !state.showMinimap };
274
+ case 'TOGGLE_SNAP':
275
+ return { ...state, snapToGrid: !state.snapToGrid };
276
+ case 'SET_GRID_SIZE':
277
+ return { ...state, gridSize: action.size };
278
+ case 'SET_SEARCH_QUERY':
279
+ return { ...state, searchQuery: action.query };
280
+ case 'TOGGLE_CATEGORY':
281
+ return {
282
+ ...state,
283
+ expandedCategories: state.expandedCategories.includes(action.category)
284
+ ? state.expandedCategories.filter(c => c !== action.category)
285
+ : [...state.expandedCategories, action.category],
286
+ };
287
+ case 'COLLAPSE_NODE':
288
+ return {
289
+ ...state,
290
+ graph: materialEditorUtils.updateNodeInGraph(state.graph, action.nodeId, { collapsed: true }),
291
+ };
292
+ case 'EXPAND_NODE':
293
+ return {
294
+ ...state,
295
+ graph: materialEditorUtils.updateNodeInGraph(state.graph, action.nodeId, { collapsed: false }),
296
+ };
297
+ case 'ADD_COMMENT':
298
+ return {
299
+ ...state,
300
+ graph: materialEditorUtils.updateNodeInGraph(state.graph, action.nodeId, { comment: action.comment }),
301
+ };
302
+ case 'LOCK_NODE':
303
+ return {
304
+ ...state,
305
+ graph: materialEditorUtils.updateNodeInGraph(state.graph, action.nodeId, { locked: true }),
306
+ };
307
+ case 'UNLOCK_NODE':
308
+ return {
309
+ ...state,
310
+ graph: materialEditorUtils.updateNodeInGraph(state.graph, action.nodeId, { locked: false }),
311
+ };
312
+ case 'COMPILE':
313
+ return { ...state, compiling: true };
314
+ case 'SET_COMPILED_SHADER':
315
+ return { ...state, compiledShader: action.shader, compiling: false };
316
+ case 'TOGGLE_AUTO_COMPILE':
317
+ return { ...state, autoCompile: !state.autoCompile };
318
+ case 'LOAD_GRAPH':
319
+ return {
320
+ ...createInitialState(action.graph),
321
+ autoCompile: state.autoCompile,
322
+ };
323
+ case 'NEW_GRAPH':
324
+ return createInitialState();
325
+ case 'SET_METADATA':
326
+ return {
327
+ ...state,
328
+ graph: {
329
+ ...state.graph,
330
+ metadata: { ...state.graph.metadata, ...action.metadata },
331
+ },
332
+ };
333
+ default:
334
+ return state;
335
+ }
336
+ }
337
+ /* ═══════════════════════════════════════════════════════════════════════════
338
+ Hook: useMaterialEditor
339
+ ═══════════════════════════════════════════════════════════════════════════ */
340
+ function useMaterialEditor(initialGraph) {
341
+ const [state, dispatch] = Ae.useReducer(materialEditorReducer, initialGraph, createInitialState);
342
+ // Auto-compile when graph changes
343
+ Ae.useEffect(() => {
344
+ if (state.autoCompile && !state.compiling) {
345
+ const timeout = setTimeout(() => {
346
+ dispatch({ type: 'COMPILE' });
347
+ const shader = materialEditorUtils.compileShader(state.graph);
348
+ dispatch({ type: 'SET_COMPILED_SHADER', shader });
349
+ }, 300);
350
+ return () => clearTimeout(timeout);
351
+ }
352
+ }, [state.graph, state.autoCompile]);
353
+ return { state, dispatch };
354
+ }
355
+ function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCategory, onAddNode, onDragStart, }) {
356
+ const filteredNodes = searchQuery
357
+ ? materialNodeDefinitions.searchNodes(searchQuery)
358
+ : materialNodeDefinitions.MATERIAL_NODE_DEFINITIONS;
359
+ const categories = Ae.useMemo(() => {
360
+ const cats = new Map();
361
+ for (const node of filteredNodes) {
362
+ if (!cats.has(node.category)) {
363
+ cats.set(node.category, []);
364
+ }
365
+ cats.get(node.category).push(node);
366
+ }
367
+ return cats;
368
+ }, [filteredNodes]);
369
+ return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.palette, children: [jsxRuntime.jsx("div", { className: MaterialEditor_module.paletteSearch, children: jsxRuntime.jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: e => onSearch(e.target.value), className: MaterialEditor_module.searchInput }) }), jsxRuntime.jsx("div", { className: MaterialEditor_module.paletteCategories, children: Array.from(categories.entries()).map(([category, nodes]) => (jsxRuntime.jsxs("div", { className: MaterialEditor_module.paletteCategory, children: [jsxRuntime.jsxs("button", { className: MaterialEditor_module.categoryHeader, onClick: () => onToggleCategory(category), style: { borderLeftColor: materialEditorTypes.NODE_CATEGORY_COLORS[category] }, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.categoryIcon, children: expandedCategories.includes(category) ? '▼' : '►' }), jsxRuntime.jsx("span", { children: materialEditorTypes.NODE_CATEGORY_NAMES[category] }), jsxRuntime.jsx("span", { className: MaterialEditor_module.categoryCount, children: nodes.length })] }), expandedCategories.includes(category) && (jsxRuntime.jsx("div", { className: MaterialEditor_module.categoryNodes, children: nodes.map(nodeDef => (jsxRuntime.jsxs("div", { className: MaterialEditor_module.paletteNode, draggable: true, onDragStart: e => onDragStart(nodeDef.type, e), onDoubleClick: () => onAddNode(nodeDef.type), title: nodeDef.description, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.nodeColor, style: { backgroundColor: nodeDef.color || materialEditorTypes.NODE_CATEGORY_COLORS[nodeDef.category] } }), jsxRuntime.jsx("span", { className: MaterialEditor_module.nodeName, children: nodeDef.name })] }, nodeDef.type))) }))] }, category))) })] }));
370
+ }
371
+ function GraphNode({ node, definition, selected, viewTransform, connections, onSelect, onMove, onStartConnection, }) {
372
+ const nodeRef = Ae.useRef(null);
373
+ const [isDragging, setIsDragging] = Ae.useState(false);
374
+ const dragStart = Ae.useRef(null);
375
+ const handleMouseDown = (e) => {
376
+ if (e.button !== 0)
377
+ return;
378
+ e.stopPropagation();
379
+ if (!selected) {
380
+ onSelect(node.id, e.shiftKey || e.ctrlKey);
381
+ }
382
+ if (!node.locked) {
383
+ setIsDragging(true);
384
+ dragStart.current = { x: e.clientX, y: e.clientY };
385
+ }
386
+ };
387
+ Ae.useEffect(() => {
388
+ if (!isDragging)
389
+ return;
390
+ const handleMouseMove = (e) => {
391
+ if (dragStart.current) {
392
+ const delta = {
393
+ x: (e.clientX - dragStart.current.x) / viewTransform.zoom,
394
+ y: (e.clientY - dragStart.current.y) / viewTransform.zoom,
395
+ };
396
+ dragStart.current = { x: e.clientX, y: e.clientY };
397
+ onMove(node.id, delta);
398
+ }
399
+ };
400
+ const handleMouseUp = () => {
401
+ setIsDragging(false);
402
+ dragStart.current = null;
403
+ };
404
+ document.addEventListener('mousemove', handleMouseMove);
405
+ document.addEventListener('mouseup', handleMouseUp);
406
+ return () => {
407
+ document.removeEventListener('mousemove', handleMouseMove);
408
+ document.removeEventListener('mouseup', handleMouseUp);
409
+ };
410
+ }, [isDragging, viewTransform.zoom, node.id, onMove]);
411
+ const handlePortMouseDown = (e, portId, portType) => {
412
+ e.stopPropagation();
413
+ const rect = e.target.getBoundingClientRect();
414
+ const position = {
415
+ x: rect.left + rect.width / 2,
416
+ y: rect.top + rect.height / 2,
417
+ };
418
+ onStartConnection(node.id, portId, portType, position);
419
+ };
420
+ const nodeColor = definition.color || materialEditorTypes.NODE_CATEGORY_COLORS[definition.category];
421
+ const maxPorts = Math.max(definition.inputs.length, definition.outputs.length);
422
+ const nodeHeight = node.collapsed
423
+ ? materialEditorTypes.NODE_HEADER_HEIGHT
424
+ : materialEditorTypes.NODE_HEADER_HEIGHT + maxPorts * materialEditorTypes.PORT_HEIGHT + 8;
425
+ return (jsxRuntime.jsxs("div", { ref: nodeRef, className: `${MaterialEditor_module.graphNode} ${selected ? MaterialEditor_module.selected : ''} ${node.locked ? MaterialEditor_module.locked : ''}`, style: {
426
+ left: node.position.x * viewTransform.zoom + viewTransform.x,
427
+ top: node.position.y * viewTransform.zoom + viewTransform.y,
428
+ width: materialEditorTypes.NODE_WIDTH * viewTransform.zoom,
429
+ height: nodeHeight * viewTransform.zoom,
430
+ transform: `scale(${viewTransform.zoom})`,
431
+ transformOrigin: 'top left',
432
+ }, onMouseDown: handleMouseDown, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.nodeHeader, style: { backgroundColor: nodeColor }, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.nodeTitle, children: definition.name }), node.locked && jsxRuntime.jsx("span", { className: MaterialEditor_module.lockIcon, children: "\uD83D\uDD12" })] }), !node.collapsed && (jsxRuntime.jsxs("div", { className: MaterialEditor_module.nodePorts, children: [jsxRuntime.jsx("div", { className: MaterialEditor_module.inputPorts, children: definition.inputs.map((port, index) => {
433
+ const isConnected = connections.some(c => c.targetNodeId === node.id && c.targetPortId === port.id);
434
+ return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.port, style: { top: index * materialEditorTypes.PORT_HEIGHT + materialEditorTypes.NODE_HEADER_HEIGHT }, children: [jsxRuntime.jsx("div", { className: `${MaterialEditor_module.portHandle} ${MaterialEditor_module.inputHandle} ${isConnected ? MaterialEditor_module.connected : ''}`, style: { backgroundColor: materialEditorTypes.PORT_TYPE_COLORS[port.dataType] }, onMouseDown: e => handlePortMouseDown(e, port.id, 'input') }), jsxRuntime.jsx("span", { className: MaterialEditor_module.portLabel, children: port.name })] }, port.id));
435
+ }) }), jsxRuntime.jsx("div", { className: MaterialEditor_module.outputPorts, children: definition.outputs.map((port, index) => {
436
+ const isConnected = connections.some(c => c.sourceNodeId === node.id && c.sourcePortId === port.id);
437
+ return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.port, style: { top: index * materialEditorTypes.PORT_HEIGHT + materialEditorTypes.NODE_HEADER_HEIGHT }, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.portLabel, children: port.name }), jsxRuntime.jsx("div", { className: `${MaterialEditor_module.portHandle} ${MaterialEditor_module.outputHandle} ${isConnected ? MaterialEditor_module.connected : ''}`, style: { backgroundColor: materialEditorTypes.PORT_TYPE_COLORS[port.dataType] }, onMouseDown: e => handlePortMouseDown(e, port.id, 'output') })] }, port.id));
438
+ }) })] })), node.comment && (jsxRuntime.jsx("div", { className: MaterialEditor_module.nodeComment, children: node.comment }))] }));
439
+ }
440
+ function Toolbar({ canUndo, canRedo, canPaste, hasSelection, autoCompile, compiling, showGrid, snapToGrid, onUndo, onRedo, onCopy, onCut, onPaste, onDuplicate, onDelete, onSelectAll, onZoomToFit, onToggleGrid, onToggleSnap, onToggleAutoCompile, onCompile, onNew, onExport, onImport, }) {
441
+ return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbar, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onNew, title: "New Material (Ctrl+N)", children: "\uD83D\uDCC4" }), jsxRuntime.jsx("button", { onClick: onImport, title: "Import (Ctrl+O)", children: "\uD83D\uDCC2" }), jsxRuntime.jsx("button", { onClick: onExport, title: "Export (Ctrl+S)", children: "\uD83D\uDCBE" })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onUndo, disabled: !canUndo, title: "Undo (Ctrl+Z)", children: "\u21A9\uFE0F" }), jsxRuntime.jsx("button", { onClick: onRedo, disabled: !canRedo, title: "Redo (Ctrl+Y)", children: "\u21AA\uFE0F" })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onCut, disabled: !hasSelection, title: "Cut (Ctrl+X)", children: "\u2702\uFE0F" }), jsxRuntime.jsx("button", { onClick: onCopy, disabled: !hasSelection, title: "Copy (Ctrl+C)", children: "\uD83D\uDCCB" }), jsxRuntime.jsx("button", { onClick: onPaste, disabled: !canPaste, title: "Paste (Ctrl+V)", children: "\uD83D\uDCE5" }), jsxRuntime.jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: "Duplicate (Ctrl+D)", children: "\uD83D\uDD04" }), jsxRuntime.jsx("button", { onClick: onDelete, disabled: !hasSelection, title: "Delete (Del)", children: "\uD83D\uDDD1\uFE0F" })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onSelectAll, title: "Select All (Ctrl+A)", children: "\u2B1C" }), jsxRuntime.jsx("button", { onClick: onZoomToFit, title: "Zoom to Fit (F)", children: "\uD83D\uDD0D" }), jsxRuntime.jsx("button", { onClick: onToggleGrid, className: showGrid ? MaterialEditor_module.active : '', title: "Toggle Grid (G)", children: "#" }), jsxRuntime.jsx("button", { onClick: onToggleSnap, className: snapToGrid ? MaterialEditor_module.active : '', title: "Toggle Snap (S)", children: "\uD83E\uDDF2" })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onToggleAutoCompile, className: autoCompile ? MaterialEditor_module.active : '', title: "Auto Compile", children: "\u26A1" }), jsxRuntime.jsx("button", { onClick: onCompile, disabled: compiling, title: "Compile (F5)", children: compiling ? '⏳' : '▶️' })] })] }));
442
+ }
443
+ function PreviewPanel({ shader, mode, compiling, onModeChange }) {
444
+ const canvasRef = Ae.useRef(null);
445
+ const previewModes = ['sphere', 'cube', 'plane', 'cylinder', 'torus'];
446
+ // Simple preview rendering (in real implementation would use Three.js)
447
+ Ae.useEffect(() => {
448
+ const canvas = canvasRef.current;
449
+ if (!canvas)
450
+ return;
451
+ const ctx = canvas.getContext('2d');
452
+ if (!ctx)
453
+ return;
454
+ // Simple gradient preview
455
+ const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 100);
456
+ if ((shader === null || shader === void 0 ? void 0 : shader.errors.length) === 0) {
457
+ gradient.addColorStop(0, '#4CAF50');
458
+ gradient.addColorStop(1, '#2E7D32');
459
+ }
460
+ else {
461
+ gradient.addColorStop(0, '#f44336');
462
+ gradient.addColorStop(1, '#c62828');
463
+ }
464
+ ctx.fillStyle = '#1e1e1e';
465
+ ctx.fillRect(0, 0, 200, 200);
466
+ ctx.fillStyle = gradient;
467
+ ctx.beginPath();
468
+ ctx.arc(100, 100, 80, 0, Math.PI * 2);
469
+ ctx.fill();
470
+ }, [shader, mode]);
471
+ return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewPanel, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewHeader, children: [jsxRuntime.jsx("span", { children: "Preview" }), jsxRuntime.jsx("select", { value: mode, onChange: e => onModeChange(e.target.value), className: MaterialEditor_module.previewModeSelect, children: previewModes.map(m => (jsxRuntime.jsx("option", { value: m, children: m.charAt(0).toUpperCase() + m.slice(1) }, m))) })] }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewCanvas, children: [compiling && jsxRuntime.jsx("div", { className: MaterialEditor_module.previewCompiling, children: "Compiling..." }), jsxRuntime.jsx("canvas", { ref: canvasRef, width: 200, height: 200 })] }), (shader === null || shader === void 0 ? void 0 : shader.errors.length) ? (jsxRuntime.jsx("div", { className: MaterialEditor_module.previewErrors, children: shader.errors.slice(0, 3).map((err, i) => (jsxRuntime.jsx("div", { className: MaterialEditor_module.previewError, children: err.message }, i))) })) : null] }));
472
+ }
473
+ /* ═══════════════════════════════════════════════════════════════════════════
474
+ Main Component
475
+ ═══════════════════════════════════════════════════════════════════════════ */
476
+ function NiceMaterialEditor({ graph: initialGraph, onGraphChange, onCompile, customNodes, textures, width = '100%', height = '600px', readOnly = false, showToolbar = true, showPalette = true, showProperties = true, showPreview = true, previewMode: initialPreviewMode = 'sphere', theme = 'dark', className, }) {
477
+ const { state, dispatch } = useMaterialEditor(initialGraph);
478
+ const canvasRef = Ae.useRef(null);
479
+ const [pendingConnection, setPendingConnection] = Ae.useState(null);
480
+ // Merge custom node definitions
481
+ const definitions = Ae.useMemo(() => {
482
+ const map = new Map(materialNodeDefinitions.MATERIAL_NODE_MAP);
483
+ customNodes === null || customNodes === void 0 ? void 0 : customNodes.forEach(def => map.set(def.type, def));
484
+ return map;
485
+ }, [customNodes]);
486
+ // Notify parent of changes
487
+ Ae.useEffect(() => {
488
+ onGraphChange === null || onGraphChange === void 0 ? void 0 : onGraphChange(state.graph);
489
+ }, [state.graph, onGraphChange]);
490
+ Ae.useEffect(() => {
491
+ if (state.compiledShader) {
492
+ onCompile === null || onCompile === void 0 ? void 0 : onCompile(state.compiledShader);
493
+ }
494
+ }, [state.compiledShader, onCompile]);
495
+ // Keyboard shortcuts
496
+ Ae.useEffect(() => {
497
+ const handleKeyDown = (e) => {
498
+ if (readOnly)
499
+ return;
500
+ const isCtrl = e.ctrlKey || e.metaKey;
501
+ if (isCtrl && e.key === 'z') {
502
+ e.preventDefault();
503
+ dispatch({ type: 'UNDO' });
504
+ }
505
+ else if (isCtrl && e.key === 'y') {
506
+ e.preventDefault();
507
+ dispatch({ type: 'REDO' });
508
+ }
509
+ else if (isCtrl && e.key === 'c') {
510
+ e.preventDefault();
511
+ dispatch({ type: 'COPY' });
512
+ }
513
+ else if (isCtrl && e.key === 'x') {
514
+ e.preventDefault();
515
+ dispatch({ type: 'CUT' });
516
+ }
517
+ else if (isCtrl && e.key === 'v') {
518
+ e.preventDefault();
519
+ dispatch({ type: 'PASTE', position: { x: 100, y: 100 } });
520
+ }
521
+ else if (isCtrl && e.key === 'd') {
522
+ e.preventDefault();
523
+ dispatch({ type: 'DUPLICATE' });
524
+ }
525
+ else if (isCtrl && e.key === 'a') {
526
+ e.preventDefault();
527
+ dispatch({ type: 'SELECT_ALL' });
528
+ }
529
+ else if (e.key === 'Delete' || e.key === 'Backspace') {
530
+ if (state.selectedNodes.length > 0) {
531
+ e.preventDefault();
532
+ dispatch({ type: 'DELETE_NODES', nodeIds: state.selectedNodes });
533
+ }
534
+ }
535
+ else if (e.key === 'f' || e.key === 'F') {
536
+ e.preventDefault();
537
+ dispatch({ type: 'ZOOM_TO_FIT' });
538
+ }
539
+ else if (e.key === 'g' || e.key === 'G') {
540
+ e.preventDefault();
541
+ dispatch({ type: 'TOGGLE_GRID' });
542
+ }
543
+ else if (e.key === 's' && !isCtrl) {
544
+ e.preventDefault();
545
+ dispatch({ type: 'TOGGLE_SNAP' });
546
+ }
547
+ else if (e.key === 'Escape') {
548
+ dispatch({ type: 'DESELECT_ALL' });
549
+ setPendingConnection(null);
550
+ }
551
+ };
552
+ window.addEventListener('keydown', handleKeyDown);
553
+ return () => window.removeEventListener('keydown', handleKeyDown);
554
+ }, [readOnly, state.selectedNodes, dispatch]);
555
+ // Handle wheel zoom
556
+ const handleWheel = Ae.useCallback((e) => {
557
+ var _a;
558
+ e.preventDefault();
559
+ const delta = e.deltaY > 0 ? 0.9 : 1.1;
560
+ const newZoom = Math.min(materialEditorTypes.MAX_ZOOM, Math.max(materialEditorTypes.MIN_ZOOM, state.viewTransform.zoom * delta));
561
+ const rect = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
562
+ if (!rect)
563
+ return;
564
+ const mouseX = e.clientX - rect.left;
565
+ const mouseY = e.clientY - rect.top;
566
+ const newX = mouseX - (mouseX - state.viewTransform.x) * (newZoom / state.viewTransform.zoom);
567
+ const newY = mouseY - (mouseY - state.viewTransform.y) * (newZoom / state.viewTransform.zoom);
568
+ dispatch({
569
+ type: 'SET_VIEW_TRANSFORM',
570
+ transform: { x: newX, y: newY, zoom: newZoom },
571
+ });
572
+ }, [state.viewTransform, dispatch]);
573
+ // Handle canvas pan
574
+ const [isPanning, setIsPanning] = Ae.useState(false);
575
+ const panStart = Ae.useRef(null);
576
+ const handleCanvasMouseDown = (e) => {
577
+ if (e.button === 1 || (e.button === 0 && e.altKey)) {
578
+ setIsPanning(true);
579
+ panStart.current = { x: e.clientX, y: e.clientY };
580
+ }
581
+ else if (e.button === 0 && e.target === canvasRef.current) {
582
+ dispatch({ type: 'DESELECT_ALL' });
583
+ }
584
+ };
585
+ const handleCanvasMouseMove = (e) => {
586
+ if (isPanning && panStart.current) {
587
+ const delta = {
588
+ x: e.clientX - panStart.current.x,
589
+ y: e.clientY - panStart.current.y,
590
+ };
591
+ panStart.current = { x: e.clientX, y: e.clientY };
592
+ dispatch({
593
+ type: 'SET_VIEW_TRANSFORM',
594
+ transform: {
595
+ ...state.viewTransform,
596
+ x: state.viewTransform.x + delta.x,
597
+ y: state.viewTransform.y + delta.y,
598
+ },
599
+ });
600
+ }
601
+ if (pendingConnection) {
602
+ setPendingConnection({
603
+ ...pendingConnection,
604
+ currentPosition: { x: e.clientX, y: e.clientY },
605
+ });
606
+ }
607
+ };
608
+ const handleCanvasMouseUp = () => {
609
+ setIsPanning(false);
610
+ panStart.current = null;
611
+ };
612
+ // Handle drag and drop from palette
613
+ const handleDragOver = (e) => {
614
+ e.preventDefault();
615
+ };
616
+ const handleDrop = (e) => {
617
+ var _a;
618
+ e.preventDefault();
619
+ const nodeType = e.dataTransfer.getData('text/plain');
620
+ if (!nodeType)
621
+ return;
622
+ const rect = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
623
+ if (!rect)
624
+ return;
625
+ const position = materialEditorUtils.screenToGraph({ x: e.clientX - rect.left, y: e.clientY - rect.top }, state.viewTransform);
626
+ dispatch({ type: 'ADD_NODE', nodeType, position });
627
+ };
628
+ // Palette handlers
629
+ const handlePaletteAddNode = (nodeType) => {
630
+ const position = { x: 200, y: 200 };
631
+ dispatch({ type: 'ADD_NODE', nodeType, position });
632
+ };
633
+ const handlePaletteDragStart = (nodeType, e) => {
634
+ e.dataTransfer.setData('text/plain', nodeType);
635
+ };
636
+ // Connection handlers
637
+ const handleStartConnection = (nodeId, portId, portType, position) => {
638
+ setPendingConnection({
639
+ nodeId,
640
+ portId,
641
+ portType,
642
+ startPosition: position,
643
+ currentPosition: position,
644
+ });
645
+ };
646
+ // Export/Import
647
+ const handleExport = () => {
648
+ const json = materialEditorUtils.exportGraphToJSON(state.graph);
649
+ const blob = new Blob([json], { type: 'application/json' });
650
+ const url = URL.createObjectURL(blob);
651
+ const a = document.createElement('a');
652
+ a.href = url;
653
+ a.download = `${state.graph.name}.material.json`;
654
+ a.click();
655
+ URL.revokeObjectURL(url);
656
+ };
657
+ const handleImport = () => {
658
+ const input = document.createElement('input');
659
+ input.type = 'file';
660
+ input.accept = '.json';
661
+ input.onchange = async (e) => {
662
+ var _a;
663
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
664
+ if (!file)
665
+ return;
666
+ const text = await file.text();
667
+ const graph = materialEditorUtils.importGraphFromJSON(text);
668
+ if (graph) {
669
+ dispatch({ type: 'LOAD_GRAPH', graph });
670
+ }
671
+ };
672
+ input.click();
673
+ };
674
+ // Render connections
675
+ const renderConnections = () => {
676
+ return state.graph.connections.map(conn => {
677
+ const sourceNode = state.graph.nodes.find(n => n.id === conn.sourceNodeId);
678
+ const targetNode = state.graph.nodes.find(n => n.id === conn.targetNodeId);
679
+ if (!sourceNode || !targetNode)
680
+ return null;
681
+ const sourceDef = definitions.get(sourceNode.type);
682
+ const targetDef = definitions.get(targetNode.type);
683
+ if (!sourceDef || !targetDef)
684
+ return null;
685
+ const sourcePortIndex = sourceDef.outputs.findIndex(p => p.id === conn.sourcePortId);
686
+ const targetPortIndex = targetDef.inputs.findIndex(p => p.id === conn.targetPortId);
687
+ if (sourcePortIndex === -1 || targetPortIndex === -1)
688
+ return null;
689
+ const sourcePort = sourceDef.outputs[sourcePortIndex];
690
+ const x1 = (sourceNode.position.x + materialEditorTypes.NODE_WIDTH) * state.viewTransform.zoom + state.viewTransform.x;
691
+ const y1 = (sourceNode.position.y + materialEditorTypes.NODE_HEADER_HEIGHT + sourcePortIndex * materialEditorTypes.PORT_HEIGHT + materialEditorTypes.PORT_HEIGHT / 2) * state.viewTransform.zoom + state.viewTransform.y;
692
+ const x2 = targetNode.position.x * state.viewTransform.zoom + state.viewTransform.x;
693
+ const y2 = (targetNode.position.y + materialEditorTypes.NODE_HEADER_HEIGHT + targetPortIndex * materialEditorTypes.PORT_HEIGHT + materialEditorTypes.PORT_HEIGHT / 2) * state.viewTransform.zoom + state.viewTransform.y;
694
+ const isSelected = state.selectedConnections.includes(conn.id);
695
+ const controlX = (x1 + x2) / 2;
696
+ return (jsxRuntime.jsx("path", { d: `M ${x1} ${y1} C ${controlX} ${y1}, ${controlX} ${y2}, ${x2} ${y2}`, fill: "none", stroke: isSelected ? '#fff' : materialEditorTypes.PORT_TYPE_COLORS[sourcePort.dataType], strokeWidth: isSelected ? 3 : 2, className: MaterialEditor_module.connection, onClick: () => dispatch({ type: 'SELECT_CONNECTIONS', connectionIds: [conn.id] }) }, conn.id));
697
+ });
698
+ };
699
+ // Render pending connection
700
+ const renderPendingConnection = () => {
701
+ var _a;
702
+ if (!pendingConnection)
703
+ return null;
704
+ const rect = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
705
+ if (!rect)
706
+ return null;
707
+ const x1 = pendingConnection.startPosition.x - rect.left;
708
+ const y1 = pendingConnection.startPosition.y - rect.top;
709
+ const x2 = pendingConnection.currentPosition.x - rect.left;
710
+ const y2 = pendingConnection.currentPosition.y - rect.top;
711
+ const controlX = (x1 + x2) / 2;
712
+ return (jsxRuntime.jsx("path", { d: `M ${x1} ${y1} C ${controlX} ${y1}, ${controlX} ${y2}, ${x2} ${y2}`, fill: "none", stroke: "#fff", strokeWidth: 2, strokeDasharray: "5,5", className: MaterialEditor_module.pendingConnection }));
713
+ };
714
+ // Render grid
715
+ const renderGrid = () => {
716
+ if (!state.showGrid)
717
+ return null;
718
+ const gridSize = state.gridSize * state.viewTransform.zoom;
719
+ const offsetX = state.viewTransform.x % gridSize;
720
+ const offsetY = state.viewTransform.y % gridSize;
721
+ return (jsxRuntime.jsx("pattern", { id: "grid", width: gridSize, height: gridSize, patternUnits: "userSpaceOnUse", x: offsetX, y: offsetY, children: jsxRuntime.jsx("path", { d: `M ${gridSize} 0 L 0 0 0 ${gridSize}`, fill: "none", stroke: "rgba(255,255,255,0.1)", strokeWidth: "1" }) }));
722
+ };
723
+ return (jsxRuntime.jsxs("div", { className: `${MaterialEditor_module.materialEditor} ${MaterialEditor_module[theme]} ${className || ''}`, style: { width, height }, children: [showToolbar && (jsxRuntime.jsx(Toolbar, { canUndo: state.undoStack.length > 0, canRedo: state.redoStack.length > 0, canPaste: state.clipboard !== null, hasSelection: state.selectedNodes.length > 0, autoCompile: state.autoCompile, compiling: state.compiling, showGrid: state.showGrid, snapToGrid: state.snapToGrid, onUndo: () => dispatch({ type: 'UNDO' }), onRedo: () => dispatch({ type: 'REDO' }), onCopy: () => dispatch({ type: 'COPY' }), onCut: () => dispatch({ type: 'CUT' }), onPaste: () => dispatch({ type: 'PASTE', position: { x: 100, y: 100 } }), onDuplicate: () => dispatch({ type: 'DUPLICATE' }), onDelete: () => dispatch({ type: 'DELETE_NODES', nodeIds: state.selectedNodes }), onSelectAll: () => dispatch({ type: 'SELECT_ALL' }), onZoomToFit: () => dispatch({ type: 'ZOOM_TO_FIT' }), onToggleGrid: () => dispatch({ type: 'TOGGLE_GRID' }), onToggleSnap: () => dispatch({ type: 'TOGGLE_SNAP' }), onToggleAutoCompile: () => dispatch({ type: 'TOGGLE_AUTO_COMPILE' }), onCompile: () => {
724
+ dispatch({ type: 'COMPILE' });
725
+ const shader = materialEditorUtils.compileShader(state.graph, definitions);
726
+ dispatch({ type: 'SET_COMPILED_SHADER', shader });
727
+ }, onNew: () => dispatch({ type: 'NEW_GRAPH' }), onExport: handleExport, onImport: handleImport })), jsxRuntime.jsxs("div", { className: MaterialEditor_module.editorBody, children: [showPalette && (jsxRuntime.jsx(NodePalette, { searchQuery: state.searchQuery, expandedCategories: state.expandedCategories, onSearch: query => dispatch({ type: 'SET_SEARCH_QUERY', query }), onToggleCategory: cat => dispatch({ type: 'TOGGLE_CATEGORY', category: cat }), onAddNode: handlePaletteAddNode, onDragStart: handlePaletteDragStart })), jsxRuntime.jsxs("div", { ref: canvasRef, className: MaterialEditor_module.graphCanvas, onWheel: handleWheel, onMouseDown: handleCanvasMouseDown, onMouseMove: handleCanvasMouseMove, onMouseUp: handleCanvasMouseUp, onMouseLeave: handleCanvasMouseUp, onDragOver: handleDragOver, onDrop: handleDrop, children: [jsxRuntime.jsxs("svg", { className: MaterialEditor_module.connectionsSvg, children: [jsxRuntime.jsx("defs", { children: renderGrid() }), state.showGrid && jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: "url(#grid)" }), renderConnections(), renderPendingConnection()] }), state.graph.nodes.map(node => {
728
+ const def = definitions.get(node.type);
729
+ if (!def)
730
+ return null;
731
+ return (jsxRuntime.jsx(GraphNode, { node: node, definition: def, selected: state.selectedNodes.includes(node.id), viewTransform: state.viewTransform, connections: state.graph.connections, onSelect: (nodeId, additive) => dispatch({ type: 'SELECT_NODES', nodeIds: [nodeId], additive }), onMove: (nodeId, delta) => dispatch({ type: 'MOVE_NODES', nodeIds: state.selectedNodes.includes(nodeId) ? state.selectedNodes : [nodeId], delta }), onStartConnection: handleStartConnection }, node.id));
732
+ })] }), showPreview && (jsxRuntime.jsx(PreviewPanel, { shader: state.compiledShader, mode: state.previewMode, compiling: state.compiling, onModeChange: mode => dispatch({ type: 'SET_PREVIEW_MODE', mode }) }))] })] }));
733
+ }
734
+
735
+ exports.NiceMaterialEditor = NiceMaterialEditor;
736
+ exports.useMaterialEditor = useMaterialEditor;
737
+ //# sourceMappingURL=NiceMaterialEditor.js.map