@teachinglab/omd 0.1.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.
Files changed (144) hide show
  1. package/README.md +138 -0
  2. package/canvas/core/canvasConfig.js +203 -0
  3. package/canvas/core/omdCanvas.js +475 -0
  4. package/canvas/drawing/segment.js +168 -0
  5. package/canvas/drawing/stroke.js +386 -0
  6. package/canvas/events/eventManager.js +435 -0
  7. package/canvas/events/pointerEventHandler.js +263 -0
  8. package/canvas/features/focusFrameManager.js +287 -0
  9. package/canvas/index.js +49 -0
  10. package/canvas/tools/eraserTool.js +322 -0
  11. package/canvas/tools/pencilTool.js +319 -0
  12. package/canvas/tools/selectTool.js +457 -0
  13. package/canvas/tools/tool.js +223 -0
  14. package/canvas/tools/toolManager.js +394 -0
  15. package/canvas/ui/cursor.js +438 -0
  16. package/canvas/ui/toolbar.js +304 -0
  17. package/canvas/utils/boundingBox.js +378 -0
  18. package/canvas/utils/mathUtils.js +259 -0
  19. package/docs/api/configuration-options.md +104 -0
  20. package/docs/api/eventManager.md +68 -0
  21. package/docs/api/focusFrameManager.md +150 -0
  22. package/docs/api/index.md +91 -0
  23. package/docs/api/main.md +58 -0
  24. package/docs/api/omdBinaryExpressionNode.md +227 -0
  25. package/docs/api/omdCanvas.md +142 -0
  26. package/docs/api/omdConfigManager.md +192 -0
  27. package/docs/api/omdConstantNode.md +117 -0
  28. package/docs/api/omdDisplay.md +121 -0
  29. package/docs/api/omdEquationNode.md +161 -0
  30. package/docs/api/omdEquationSequenceNode.md +301 -0
  31. package/docs/api/omdEquationStack.md +139 -0
  32. package/docs/api/omdFunctionNode.md +141 -0
  33. package/docs/api/omdGroupNode.md +182 -0
  34. package/docs/api/omdHelpers.md +96 -0
  35. package/docs/api/omdLeafNode.md +163 -0
  36. package/docs/api/omdNode.md +101 -0
  37. package/docs/api/omdOperationDisplayNode.md +139 -0
  38. package/docs/api/omdOperatorNode.md +127 -0
  39. package/docs/api/omdParenthesisNode.md +122 -0
  40. package/docs/api/omdPopup.md +117 -0
  41. package/docs/api/omdPowerNode.md +127 -0
  42. package/docs/api/omdRationalNode.md +128 -0
  43. package/docs/api/omdSequenceNode.md +128 -0
  44. package/docs/api/omdSimplification.md +110 -0
  45. package/docs/api/omdSqrtNode.md +79 -0
  46. package/docs/api/omdStepVisualizer.md +115 -0
  47. package/docs/api/omdStepVisualizerHighlighting.md +61 -0
  48. package/docs/api/omdStepVisualizerInteractiveSteps.md +129 -0
  49. package/docs/api/omdStepVisualizerLayout.md +60 -0
  50. package/docs/api/omdStepVisualizerNodeUtils.md +140 -0
  51. package/docs/api/omdStepVisualizerTextBoxes.md +68 -0
  52. package/docs/api/omdToolbar.md +102 -0
  53. package/docs/api/omdTranscriptionService.md +76 -0
  54. package/docs/api/omdTreeDiff.md +134 -0
  55. package/docs/api/omdUnaryExpressionNode.md +174 -0
  56. package/docs/api/omdUtilities.md +70 -0
  57. package/docs/api/omdVariableNode.md +148 -0
  58. package/docs/api/selectTool.md +74 -0
  59. package/docs/api/simplificationEngine.md +98 -0
  60. package/docs/api/simplificationRules.md +77 -0
  61. package/docs/api/simplificationUtils.md +64 -0
  62. package/docs/api/transcribe.md +43 -0
  63. package/docs/api-reference.md +85 -0
  64. package/docs/index.html +454 -0
  65. package/docs/user-guide.md +9 -0
  66. package/index.js +67 -0
  67. package/omd/config/omdConfigManager.js +267 -0
  68. package/omd/core/index.js +150 -0
  69. package/omd/core/omdEquationStack.js +347 -0
  70. package/omd/core/omdUtilities.js +115 -0
  71. package/omd/display/omdDisplay.js +443 -0
  72. package/omd/display/omdToolbar.js +502 -0
  73. package/omd/nodes/omdBinaryExpressionNode.js +460 -0
  74. package/omd/nodes/omdConstantNode.js +142 -0
  75. package/omd/nodes/omdEquationNode.js +1223 -0
  76. package/omd/nodes/omdEquationSequenceNode.js +1273 -0
  77. package/omd/nodes/omdFunctionNode.js +352 -0
  78. package/omd/nodes/omdGroupNode.js +68 -0
  79. package/omd/nodes/omdLeafNode.js +77 -0
  80. package/omd/nodes/omdNode.js +557 -0
  81. package/omd/nodes/omdOperationDisplayNode.js +322 -0
  82. package/omd/nodes/omdOperatorNode.js +109 -0
  83. package/omd/nodes/omdParenthesisNode.js +293 -0
  84. package/omd/nodes/omdPowerNode.js +236 -0
  85. package/omd/nodes/omdRationalNode.js +295 -0
  86. package/omd/nodes/omdSqrtNode.js +308 -0
  87. package/omd/nodes/omdUnaryExpressionNode.js +178 -0
  88. package/omd/nodes/omdVariableNode.js +123 -0
  89. package/omd/simplification/omdSimplification.js +171 -0
  90. package/omd/simplification/omdSimplificationEngine.js +886 -0
  91. package/omd/simplification/package.json +6 -0
  92. package/omd/simplification/rules/binaryRules.js +1037 -0
  93. package/omd/simplification/rules/functionRules.js +111 -0
  94. package/omd/simplification/rules/index.js +48 -0
  95. package/omd/simplification/rules/parenthesisRules.js +19 -0
  96. package/omd/simplification/rules/powerRules.js +143 -0
  97. package/omd/simplification/rules/rationalRules.js +475 -0
  98. package/omd/simplification/rules/sqrtRules.js +48 -0
  99. package/omd/simplification/rules/unaryRules.js +37 -0
  100. package/omd/simplification/simplificationRules.js +32 -0
  101. package/omd/simplification/simplificationUtils.js +1056 -0
  102. package/omd/step-visualizer/omdStepVisualizer.js +597 -0
  103. package/omd/step-visualizer/omdStepVisualizerHighlighting.js +206 -0
  104. package/omd/step-visualizer/omdStepVisualizerLayout.js +245 -0
  105. package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +163 -0
  106. package/omd/utils/omdNodeOverlay.js +638 -0
  107. package/omd/utils/omdPopup.js +1084 -0
  108. package/omd/utils/omdStepVisualizerInteractiveSteps.js +491 -0
  109. package/omd/utils/omdStepVisualizerNodeUtils.js +268 -0
  110. package/omd/utils/omdTranscriptionService.js +125 -0
  111. package/omd/utils/omdTreeDiff.js +734 -0
  112. package/package.json +46 -0
  113. package/src/index.js +62 -0
  114. package/src/json-schemas.md +109 -0
  115. package/src/omd-json-samples.js +115 -0
  116. package/src/omd.js +109 -0
  117. package/src/omdApp.js +391 -0
  118. package/src/omdAppCanvas.js +336 -0
  119. package/src/omdBalanceHanger.js +172 -0
  120. package/src/omdColor.js +13 -0
  121. package/src/omdCoordinatePlane.js +467 -0
  122. package/src/omdEquation.js +125 -0
  123. package/src/omdExpression.js +104 -0
  124. package/src/omdFunction.js +113 -0
  125. package/src/omdMetaExpression.js +287 -0
  126. package/src/omdNaturalExpression.js +564 -0
  127. package/src/omdNode.js +384 -0
  128. package/src/omdNumber.js +53 -0
  129. package/src/omdNumberLine.js +107 -0
  130. package/src/omdNumberTile.js +119 -0
  131. package/src/omdOperator.js +73 -0
  132. package/src/omdPowerExpression.js +92 -0
  133. package/src/omdProblem.js +55 -0
  134. package/src/omdRatioChart.js +232 -0
  135. package/src/omdRationalExpression.js +115 -0
  136. package/src/omdSampleData.js +215 -0
  137. package/src/omdShapes.js +476 -0
  138. package/src/omdSpinner.js +148 -0
  139. package/src/omdString.js +39 -0
  140. package/src/omdTable.js +369 -0
  141. package/src/omdTapeDiagram.js +245 -0
  142. package/src/omdTerm.js +92 -0
  143. package/src/omdTileEquation.js +349 -0
  144. package/src/omdVariable.js +51 -0
@@ -0,0 +1,597 @@
1
+ import { omdEquationSequenceNode } from "../nodes/omdEquationSequenceNode.js";
2
+ import { omdEquationNode } from "../nodes/omdEquationNode.js";
3
+ import { omdOperationDisplayNode } from "../nodes/omdOperationDisplayNode.js";
4
+ import { omdColor } from "../../src/omdColor.js";
5
+ import { omdStepVisualizerHighlighting } from "./omdStepVisualizerHighlighting.js";
6
+ import { omdStepVisualizerTextBoxes } from "./omdStepVisualizerTextBoxes.js";
7
+ import { omdStepVisualizerLayout } from "./omdStepVisualizerLayout.js";
8
+ import { getDotRadius } from "../config/omdConfigManager.js";
9
+ import { jsvgLayoutGroup, jsvgEllipse, jsvgLine } from '@teachinglab/jsvg';
10
+
11
+
12
+ /**
13
+ * A visual step tracker that extends omdEquationSequenceNode to show step progression
14
+ * with dots and connecting lines to the right of the sequence.
15
+ * @extends omdEquationSequenceNode
16
+ */
17
+ export class omdStepVisualizer extends omdEquationSequenceNode {
18
+ constructor(steps) {
19
+ super(steps);
20
+
21
+ // Visual elements for step tracking
22
+ this.stepDots = [];
23
+ this.stepLines = [];
24
+ this.visualContainer = new jsvgLayoutGroup();
25
+ this.dotRadius = getDotRadius(0);
26
+ this.lineWidth = 2;
27
+ this.visualSpacing = 30;
28
+ this.activeDotIndex = -1;
29
+ this.dotsClickable = true;
30
+ this.nodeToStepMap = new Map();
31
+
32
+ // Highlighting system
33
+ this.stepVisualizerHighlights = new Set();
34
+ this.highlighting = new omdStepVisualizerHighlighting(this);
35
+ this.textBoxManager = new omdStepVisualizerTextBoxes(this, this.highlighting);
36
+ this.layoutManager = new omdStepVisualizerLayout(this);
37
+
38
+ this.addChild(this.visualContainer);
39
+ this._initializeVisualElements();
40
+ this.computeDimensions();
41
+ this.updateLayout();
42
+ }
43
+
44
+ /**
45
+ * Force rebuild visual container (dots/lines) from scratch
46
+ */
47
+ rebuildVisualizer() {
48
+ try {
49
+ if (this.visualContainer) {
50
+ try {
51
+ console.log('[rebuildVisualizer] removing old visualContainer', {
52
+ childCount: Array.isArray(this.visualContainer.childList) ? this.visualContainer.childList.length : 'n/a'
53
+ });
54
+ } catch (_) {}
55
+ this.removeChild(this.visualContainer);
56
+ try {
57
+ const stillAttached = !!(this.visualContainer?.svgObject?.parentNode);
58
+ console.log('[rebuildVisualizer] removed old visualContainer, stillAttached?', stillAttached);
59
+ } catch (_) {}
60
+ }
61
+ } catch (_) {}
62
+ this.visualContainer = new jsvgLayoutGroup();
63
+ this.addChild(this.visualContainer);
64
+ this._initializeVisualElements();
65
+ try {
66
+ console.log('[rebuildVisualizer] new visualContainer', {
67
+ dots: Array.isArray(this.stepDots) ? this.stepDots.length : 'n/a',
68
+ lines: Array.isArray(this.stepLines) ? this.stepLines.length : 'n/a'
69
+ });
70
+ } catch (_) {}
71
+ this.computeDimensions();
72
+ this.updateLayout();
73
+ }
74
+
75
+ /**
76
+ * Initializes visual elements (dots and lines) for all existing steps
77
+ * @private
78
+ */
79
+ _initializeVisualElements() {
80
+ this._clearVisualElements();
81
+ this.nodeToStepMap.clear();
82
+
83
+ const equations = this.steps.filter(step => step instanceof omdEquationNode);
84
+
85
+ equations.forEach((equation, index) => {
86
+ this._createStepDot(equation, index);
87
+ equation.findAllNodes().forEach(node => {
88
+ this.nodeToStepMap.set(node.id, index);
89
+ });
90
+
91
+ if (index > 0) {
92
+ this._createStepLine(index - 1, index);
93
+ }
94
+ });
95
+
96
+ this.layoutManager.updateVisualZOrder();
97
+ this.layoutManager.updateVisualLayout();
98
+ }
99
+
100
+ /**
101
+ * Creates a visual dot for a step
102
+ * @private
103
+ */
104
+ _createStepDot(equation, index) {
105
+ const radius = getDotRadius(equation.stepMark ?? 0);
106
+ const dot = new jsvgEllipse();
107
+ dot.setWidthAndHeight(radius * 2, radius * 2);
108
+ dot.setFillColor(omdColor.stepColor);
109
+ dot.setStrokeColor(omdColor.stepColor);
110
+ dot.setStrokeWidth(this.lineWidth);
111
+ dot.radius = radius;
112
+
113
+ dot.equationRef = equation;
114
+ dot.stepIndex = index;
115
+
116
+ if (equation.visible === false) {
117
+ dot.hide();
118
+ }
119
+
120
+ this.layoutManager.updateDotClickability(dot);
121
+
122
+ this.stepDots.push(dot);
123
+ this.visualContainer.addChild(dot);
124
+
125
+ return dot;
126
+ }
127
+
128
+ /**
129
+ * Creates a connecting line between two step dots
130
+ * @private
131
+ */
132
+ _createStepLine(fromIndex, toIndex) {
133
+ const line = new jsvgLine();
134
+ line.setStrokeColor(omdColor.stepColor);
135
+ line.setStrokeWidth(this.lineWidth);
136
+
137
+ line.fromDotIndex = fromIndex;
138
+ line.toDotIndex = toIndex;
139
+
140
+ const fromEquation = this.stepDots[fromIndex]?.equationRef;
141
+ const toEquation = this.stepDots[toIndex]?.equationRef;
142
+
143
+ if (fromEquation?.visible === false || toEquation?.visible === false) {
144
+ line.hide();
145
+ }
146
+
147
+ this.stepLines.push(line);
148
+ this.visualContainer.addChild(line);
149
+
150
+ return line;
151
+ }
152
+
153
+ /**
154
+ * Clears all visual elements
155
+ * @private
156
+ */
157
+ _clearVisualElements() {
158
+ this.stepDots.forEach(dot => this.visualContainer.removeChild(dot));
159
+ this.stepLines.forEach(line => this.visualContainer.removeChild(line));
160
+ this.textBoxManager.clearAllTextBoxes();
161
+
162
+ this.stepDots = [];
163
+ this.stepLines = [];
164
+ this.activeDotIndex = -1;
165
+ }
166
+
167
+ /**
168
+ * Override addStep to update visual elements when new steps are added
169
+ */
170
+ addStep(step, options = {}) {
171
+ let createdDot=null;
172
+ if (step instanceof omdEquationNode) {
173
+ this.steps.push(step);
174
+ const equations = this.steps.filter(s => s instanceof omdEquationNode);
175
+ const newIndex = equations.length - 1;
176
+ this.steps.pop();
177
+ createdDot = this._createStepDot(step, newIndex);
178
+ step.findAllNodes().forEach(node => {
179
+ this.nodeToStepMap.set(node.id, newIndex);
180
+ });
181
+
182
+ if (newIndex > 0) {
183
+ this._createStepLine(newIndex - 1, newIndex);
184
+ }
185
+ }
186
+
187
+ super.addStep(step, options);
188
+
189
+ // after stepMark is set, adjust dot radius
190
+ if(createdDot){
191
+ const radius=getDotRadius(step.stepMark??0);
192
+ createdDot.setWidthAndHeight(radius*2,radius*2);
193
+ createdDot.radius=radius;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Gets the step number for a given node ID.
199
+ */
200
+ getNodeStepNumber(nodeId) {
201
+ return this.nodeToStepMap.get(nodeId);
202
+ }
203
+
204
+ /**
205
+ * Override computeDimensions to account for visual elements
206
+ */
207
+ computeDimensions() {
208
+ super.computeDimensions();
209
+ this.sequenceWidth = this.width;
210
+
211
+ // Ensure stepDots is initialized before accessing its length
212
+ if (this.stepDots && this.stepDots.length > 0) {
213
+ const containerWidth = this.dotRadius * 3;
214
+ const visualWidth = this.visualSpacing + containerWidth;
215
+ this.setWidthAndHeight(this.width + visualWidth, this.height);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Override updateLayout to update visual elements as well
221
+ */
222
+ updateLayout() {
223
+ super.updateLayout();
224
+
225
+ // Only update visual layout if layoutManager is initialized
226
+ if (this.layoutManager) {
227
+ this.layoutManager.updateVisualLayout();
228
+ this.layoutManager.updateVisualVisibility();
229
+ this.layoutManager.updateAllLinePositions();
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Removes the most recent operation and refreshes visual dots/lines accordingly.
235
+ * @returns {boolean} Whether an operation was undone
236
+ */
237
+ undoLastOperation() {
238
+ // Remove bottom-most equation and its preceding operation display
239
+ const beforeCount = this.steps.length;
240
+ const removed = super.undoLastOperation ? super.undoLastOperation() : false;
241
+ if (removed || this.steps.length < beforeCount) {
242
+ // Hard rebuild the visual container to avoid stale dots/lines
243
+ this.rebuildVisualizer();
244
+ return true;
245
+ }
246
+ return false;
247
+ }
248
+
249
+ /**
250
+ * Sets the color of a specific dot and its associated lines
251
+ */
252
+ setDotColor(dotIndex, color) {
253
+ if (this.stepDots && dotIndex >= 0 && dotIndex < this.stepDots.length) {
254
+ const dot = this.stepDots[dotIndex];
255
+ dot.setFillColor(color);
256
+ dot.setStrokeColor(color);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Sets the color of the line above a specific dot
262
+ */
263
+ setLineAboveColor(dotIndex, color) {
264
+ let targetLine = this.stepLines.find(line =>
265
+ line.toDotIndex === dotIndex && line.isTemporary && line.svgObject && line.svgObject.style.display !== 'none'
266
+ );
267
+
268
+ if (!targetLine) {
269
+ targetLine = this.stepLines.find(line =>
270
+ line.toDotIndex === dotIndex && !line.isTemporary && line.svgObject && line.svgObject.style.display !== 'none'
271
+ );
272
+ }
273
+
274
+ if (targetLine) {
275
+ targetLine.setStrokeColor(color);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Enables or disables dot clicking functionality
281
+ */
282
+ setDotsClickable(enabled) {
283
+ this.dotsClickable = enabled;
284
+ this.stepDots.forEach(dot => {
285
+ this.layoutManager.updateDotClickability(dot);
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Handles clicking on a step dot
291
+ * @private
292
+ */
293
+ _handleDotClick(dot, dotIndex) {
294
+ if (!this.dotsClickable) return;
295
+ // Guard against stale dot references
296
+ if (dotIndex < 0 || dotIndex >= this.stepDots.length) return;
297
+ if (this.stepDots[dotIndex] !== dot) {
298
+ // try to resolve current index
299
+ const idx = this.stepDots.indexOf(dot);
300
+ if (idx === -1) return;
301
+ dotIndex = idx;
302
+ }
303
+
304
+ try {
305
+ if (this.activeDotIndex === dotIndex) {
306
+ this._clearActiveDot();
307
+ } else {
308
+ if (this.activeDotIndex !== -1) {
309
+ this._clearActiveDot();
310
+ }
311
+ this.setActiveDot(dotIndex);
312
+ const equation = this.stepDots[dotIndex].equationRef;
313
+ const equationIndex = this.steps.indexOf(equation);
314
+ const isOperation = this._checkForOperationStep(equationIndex);
315
+ this.highlighting.highlightAffectedNodes(dotIndex, isOperation);
316
+ }
317
+ } catch (error) {
318
+ console.error('Error handling dot click:', error);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Sets a dot to be the visually active one.
324
+ * @private
325
+ */
326
+ setActiveDot(dotIndex) {
327
+ if (!this.stepDots || dotIndex < 0 || dotIndex >= this.stepDots.length) return;
328
+
329
+ this.activeDotIndex = dotIndex;
330
+ this.activeDot = this.stepDots[dotIndex];
331
+
332
+ const dot = this.stepDots[dotIndex];
333
+ dot.setFillColor(omdColor.explainColor);
334
+ dot.setStrokeColor(omdColor.explainColor);
335
+
336
+ this.setLineAboveColor(dotIndex, omdColor.explainColor);
337
+ this.textBoxManager.createTextBoxForDot(dotIndex);
338
+ this.layoutManager.updateVisualZOrder();
339
+ }
340
+
341
+ /**
342
+ * Clears the currently active dot
343
+ * @private
344
+ */
345
+ _clearActiveDot() {
346
+ try {
347
+ if (this.activeDotIndex !== -1) {
348
+ const dot = this.stepDots[this.activeDotIndex];
349
+ dot.setFillColor(omdColor.stepColor);
350
+ dot.setStrokeColor(omdColor.stepColor);
351
+
352
+ this.setLineAboveColor(this.activeDotIndex, omdColor.stepColor);
353
+ this.textBoxManager.removeTextBoxForDot(this.activeDotIndex);
354
+
355
+ this.highlighting.clearHighlights();
356
+ this.layoutManager.updateVisualZOrder();
357
+
358
+ this.activeDot = null;
359
+ this.activeDotIndex = -1;
360
+ }
361
+ } catch (error) {
362
+ console.error('Error clearing active dot:', error);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Gets simplification data for a specific dot
368
+ * @private
369
+ */
370
+ _getSimplificationDataForDot(dotIndex) {
371
+ try {
372
+ const dot = this.stepDots[dotIndex];
373
+ if (!dot || !dot.equationRef) {
374
+ return this._createDefaultSimplificationData("No equation found for this step");
375
+ }
376
+
377
+ const equationIndex = this.steps.indexOf(dot.equationRef);
378
+ if (equationIndex === -1) {
379
+ return this._createDefaultSimplificationData("Step not found in sequence");
380
+ }
381
+
382
+ // Find the previous visible equation
383
+ const previousVisibleIndex = this._findPreviousVisibleEquationIndex(equationIndex);
384
+
385
+ // Get all steps between previous visible equation and current
386
+ const allSteps = [];
387
+
388
+ // Get simplifications
389
+ const simplificationHistory = this.getSimplificationHistory();
390
+ const relevantSimplifications = this._getRelevantSimplifications(
391
+ simplificationHistory,
392
+ previousVisibleIndex,
393
+ equationIndex
394
+ );
395
+ allSteps.push(...relevantSimplifications);
396
+
397
+ // Get operations
398
+ for (let i = previousVisibleIndex + 1; i <= equationIndex; i++) {
399
+ const operationData = this._checkForOperationStep(i);
400
+ if (operationData) {
401
+ allSteps.push({
402
+ message: operationData.message,
403
+ affectedNodes: operationData.affectedNodes,
404
+ stepNumber: i - 1
405
+ });
406
+ }
407
+ }
408
+
409
+ // Sort steps by step number
410
+ allSteps.sort((a, b) => a.stepNumber - b.stepNumber);
411
+
412
+ if (allSteps.length > 0) {
413
+ return this._createMultipleSimplificationsData(allSteps);
414
+ }
415
+
416
+ // Check for single simplification
417
+ const singleSimplificationData = this._checkForSingleSimplification(
418
+ simplificationHistory,
419
+ equationIndex
420
+ );
421
+ if (singleSimplificationData) {
422
+ return singleSimplificationData;
423
+ }
424
+
425
+ // Fallback cases
426
+ return this._getFallbackSimplificationData(equationIndex);
427
+
428
+ } catch (error) {
429
+ console.error('Error getting simplification data for dot:', error);
430
+ return this._createDefaultSimplificationData("Error retrieving step data");
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Gets the step text boxes
436
+ */
437
+ getStepTextBoxes() {
438
+ return this.textBoxManager.getStepTextBoxes();
439
+ }
440
+
441
+ // ===== SIMPLIFICATION DATA METHODS =====
442
+
443
+ /**
444
+ * Creates default simplification data
445
+ * @param {string} message - The message to display
446
+ * @returns {Object} Default data object
447
+ * @private
448
+ */
449
+ _createDefaultSimplificationData(message) {
450
+ return {
451
+ message: message,
452
+ rawMessages: [message],
453
+ ruleNames: ['Step Description'],
454
+ affectedNodes: [],
455
+ resultNodeIds: [],
456
+ resultProvSources: [],
457
+ multipleSimplifications: false
458
+ };
459
+ }
460
+
461
+ /**
462
+ * Finds the index of the previous visible equation
463
+ * @param {number} currentIndex - Current equation index
464
+ * @returns {number} Index of previous visible equation, or -1 if none found
465
+ * @private
466
+ */
467
+ _findPreviousVisibleEquationIndex(currentIndex) {
468
+ for (let i = currentIndex - 1; i >= 0; i--) {
469
+ if (this.steps[i] instanceof omdEquationNode && this.steps[i].visible !== false) {
470
+ return i;
471
+ }
472
+ }
473
+ return -1;
474
+ }
475
+
476
+ /**
477
+ * Gets relevant simplifications between two step indices
478
+ * @param {Array} simplificationHistory - Full simplification history
479
+ * @param {number} startIndex - Starting step index
480
+ * @param {number} endIndex - Ending step index
481
+ * @returns {Array} Array of relevant simplification entries
482
+ * @private
483
+ */
484
+ _getRelevantSimplifications(simplificationHistory, startIndex, endIndex) {
485
+ const relevantSimplifications = [];
486
+
487
+ for (let stepNum = startIndex; stepNum < endIndex; stepNum++) {
488
+ const entries = simplificationHistory.filter(entry => entry.stepNumber === stepNum);
489
+ if (entries.length > 0) {
490
+ relevantSimplifications.push(...entries);
491
+ }
492
+ }
493
+
494
+ return relevantSimplifications;
495
+ }
496
+
497
+ /**
498
+ * Creates data object for multiple simplifications
499
+ * @param {Array} simplifications - Array of simplification entries
500
+ * @returns {Object} Data object for multiple simplifications
501
+ * @private
502
+ */
503
+ _createMultipleSimplificationsData(simplifications) {
504
+ const messages = simplifications.map(s => s.message);
505
+ const ruleNames = simplifications.map(s => s.name || 'Operation').filter(Boolean);
506
+
507
+ const allAffectedNodes = [];
508
+ const allResultNodeIds = [];
509
+ const allResultProvSources = [];
510
+
511
+ simplifications.forEach(entry => {
512
+ if (entry.affectedNodes) {
513
+ allAffectedNodes.push(...entry.affectedNodes);
514
+ }
515
+ if (entry.resultNodeId) {
516
+ allResultNodeIds.push(entry.resultNodeId);
517
+ }
518
+ if (entry.resultProvSources) {
519
+ allResultProvSources.push(...entry.resultProvSources);
520
+ }
521
+ });
522
+
523
+ return {
524
+ message: messages.join('. '),
525
+ rawMessages: messages,
526
+ ruleNames: ruleNames,
527
+ affectedNodes: allAffectedNodes,
528
+ resultNodeIds: allResultNodeIds,
529
+ resultProvSources: allResultProvSources,
530
+ multipleSimplifications: true
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Checks for operation step data
536
+ * @param {number} equationIndex - Current equation index
537
+ * @returns {Object|null} Operation data object or null
538
+ * @private
539
+ */
540
+ _checkForOperationStep(equationIndex) {
541
+ if (equationIndex > 0) {
542
+ const step = this.steps[equationIndex - 1];
543
+ if (step instanceof omdOperationDisplayNode) {
544
+ return {
545
+ message: `Applied ${step.operation} ${step.value} to both sides`,
546
+ affectedNodes: [step.operatorLeft, step.valueLeft, step.operatorRight, step.valueRight]
547
+ };
548
+ }
549
+ }
550
+ return null;
551
+ }
552
+
553
+ /**
554
+ * Checks for single simplification data
555
+ * @param {Array} simplificationHistory - Full simplification history
556
+ * @param {number} equationIndex - Current equation index
557
+ * @returns {Object|null} Single simplification data or null
558
+ * @private
559
+ */
560
+ _checkForSingleSimplification(simplificationHistory, equationIndex) {
561
+ const relevantSimplification = simplificationHistory.find(entry =>
562
+ entry.stepNumber === equationIndex - 1
563
+ );
564
+
565
+ if (relevantSimplification) {
566
+ return {
567
+ message: relevantSimplification.message,
568
+ rawMessages: [relevantSimplification.message],
569
+ ruleNames: [relevantSimplification.name || 'Operation'],
570
+ affectedNodes: relevantSimplification.affectedNodes || [],
571
+ resultNodeIds: relevantSimplification.resultNodeId ? [relevantSimplification.resultNodeId] : [],
572
+ resultProvSources: relevantSimplification.resultProvSources || [],
573
+ multipleSimplifications: false
574
+ };
575
+ }
576
+
577
+ return null;
578
+ }
579
+
580
+ /**
581
+ * Gets fallback data for special cases
582
+ * @param {number} equationIndex - Current equation index
583
+ * @returns {Object} Fallback data object
584
+ * @private
585
+ */
586
+ _getFallbackSimplificationData(equationIndex) {
587
+ const currentStep = this.steps[equationIndex];
588
+ if (equationIndex === 0 && currentStep.stepMark === 0) {
589
+ const equationStr = currentStep.toString();
590
+ return this._createDefaultSimplificationData(`Starting with equation: ${equationStr}`);
591
+ } else if (currentStep && currentStep.description) {
592
+ return this._createDefaultSimplificationData(currentStep.description);
593
+ } else {
594
+ return this._createDefaultSimplificationData("Step explanation not available");
595
+ }
596
+ }
597
+ }