@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.
- package/README.md +138 -0
- package/canvas/core/canvasConfig.js +203 -0
- package/canvas/core/omdCanvas.js +475 -0
- package/canvas/drawing/segment.js +168 -0
- package/canvas/drawing/stroke.js +386 -0
- package/canvas/events/eventManager.js +435 -0
- package/canvas/events/pointerEventHandler.js +263 -0
- package/canvas/features/focusFrameManager.js +287 -0
- package/canvas/index.js +49 -0
- package/canvas/tools/eraserTool.js +322 -0
- package/canvas/tools/pencilTool.js +319 -0
- package/canvas/tools/selectTool.js +457 -0
- package/canvas/tools/tool.js +223 -0
- package/canvas/tools/toolManager.js +394 -0
- package/canvas/ui/cursor.js +438 -0
- package/canvas/ui/toolbar.js +304 -0
- package/canvas/utils/boundingBox.js +378 -0
- package/canvas/utils/mathUtils.js +259 -0
- package/docs/api/configuration-options.md +104 -0
- package/docs/api/eventManager.md +68 -0
- package/docs/api/focusFrameManager.md +150 -0
- package/docs/api/index.md +91 -0
- package/docs/api/main.md +58 -0
- package/docs/api/omdBinaryExpressionNode.md +227 -0
- package/docs/api/omdCanvas.md +142 -0
- package/docs/api/omdConfigManager.md +192 -0
- package/docs/api/omdConstantNode.md +117 -0
- package/docs/api/omdDisplay.md +121 -0
- package/docs/api/omdEquationNode.md +161 -0
- package/docs/api/omdEquationSequenceNode.md +301 -0
- package/docs/api/omdEquationStack.md +139 -0
- package/docs/api/omdFunctionNode.md +141 -0
- package/docs/api/omdGroupNode.md +182 -0
- package/docs/api/omdHelpers.md +96 -0
- package/docs/api/omdLeafNode.md +163 -0
- package/docs/api/omdNode.md +101 -0
- package/docs/api/omdOperationDisplayNode.md +139 -0
- package/docs/api/omdOperatorNode.md +127 -0
- package/docs/api/omdParenthesisNode.md +122 -0
- package/docs/api/omdPopup.md +117 -0
- package/docs/api/omdPowerNode.md +127 -0
- package/docs/api/omdRationalNode.md +128 -0
- package/docs/api/omdSequenceNode.md +128 -0
- package/docs/api/omdSimplification.md +110 -0
- package/docs/api/omdSqrtNode.md +79 -0
- package/docs/api/omdStepVisualizer.md +115 -0
- package/docs/api/omdStepVisualizerHighlighting.md +61 -0
- package/docs/api/omdStepVisualizerInteractiveSteps.md +129 -0
- package/docs/api/omdStepVisualizerLayout.md +60 -0
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -0
- package/docs/api/omdStepVisualizerTextBoxes.md +68 -0
- package/docs/api/omdToolbar.md +102 -0
- package/docs/api/omdTranscriptionService.md +76 -0
- package/docs/api/omdTreeDiff.md +134 -0
- package/docs/api/omdUnaryExpressionNode.md +174 -0
- package/docs/api/omdUtilities.md +70 -0
- package/docs/api/omdVariableNode.md +148 -0
- package/docs/api/selectTool.md +74 -0
- package/docs/api/simplificationEngine.md +98 -0
- package/docs/api/simplificationRules.md +77 -0
- package/docs/api/simplificationUtils.md +64 -0
- package/docs/api/transcribe.md +43 -0
- package/docs/api-reference.md +85 -0
- package/docs/index.html +454 -0
- package/docs/user-guide.md +9 -0
- package/index.js +67 -0
- package/omd/config/omdConfigManager.js +267 -0
- package/omd/core/index.js +150 -0
- package/omd/core/omdEquationStack.js +347 -0
- package/omd/core/omdUtilities.js +115 -0
- package/omd/display/omdDisplay.js +443 -0
- package/omd/display/omdToolbar.js +502 -0
- package/omd/nodes/omdBinaryExpressionNode.js +460 -0
- package/omd/nodes/omdConstantNode.js +142 -0
- package/omd/nodes/omdEquationNode.js +1223 -0
- package/omd/nodes/omdEquationSequenceNode.js +1273 -0
- package/omd/nodes/omdFunctionNode.js +352 -0
- package/omd/nodes/omdGroupNode.js +68 -0
- package/omd/nodes/omdLeafNode.js +77 -0
- package/omd/nodes/omdNode.js +557 -0
- package/omd/nodes/omdOperationDisplayNode.js +322 -0
- package/omd/nodes/omdOperatorNode.js +109 -0
- package/omd/nodes/omdParenthesisNode.js +293 -0
- package/omd/nodes/omdPowerNode.js +236 -0
- package/omd/nodes/omdRationalNode.js +295 -0
- package/omd/nodes/omdSqrtNode.js +308 -0
- package/omd/nodes/omdUnaryExpressionNode.js +178 -0
- package/omd/nodes/omdVariableNode.js +123 -0
- package/omd/simplification/omdSimplification.js +171 -0
- package/omd/simplification/omdSimplificationEngine.js +886 -0
- package/omd/simplification/package.json +6 -0
- package/omd/simplification/rules/binaryRules.js +1037 -0
- package/omd/simplification/rules/functionRules.js +111 -0
- package/omd/simplification/rules/index.js +48 -0
- package/omd/simplification/rules/parenthesisRules.js +19 -0
- package/omd/simplification/rules/powerRules.js +143 -0
- package/omd/simplification/rules/rationalRules.js +475 -0
- package/omd/simplification/rules/sqrtRules.js +48 -0
- package/omd/simplification/rules/unaryRules.js +37 -0
- package/omd/simplification/simplificationRules.js +32 -0
- package/omd/simplification/simplificationUtils.js +1056 -0
- package/omd/step-visualizer/omdStepVisualizer.js +597 -0
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +206 -0
- package/omd/step-visualizer/omdStepVisualizerLayout.js +245 -0
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +163 -0
- package/omd/utils/omdNodeOverlay.js +638 -0
- package/omd/utils/omdPopup.js +1084 -0
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +491 -0
- package/omd/utils/omdStepVisualizerNodeUtils.js +268 -0
- package/omd/utils/omdTranscriptionService.js +125 -0
- package/omd/utils/omdTreeDiff.js +734 -0
- package/package.json +46 -0
- package/src/index.js +62 -0
- package/src/json-schemas.md +109 -0
- package/src/omd-json-samples.js +115 -0
- package/src/omd.js +109 -0
- package/src/omdApp.js +391 -0
- package/src/omdAppCanvas.js +336 -0
- package/src/omdBalanceHanger.js +172 -0
- package/src/omdColor.js +13 -0
- package/src/omdCoordinatePlane.js +467 -0
- package/src/omdEquation.js +125 -0
- package/src/omdExpression.js +104 -0
- package/src/omdFunction.js +113 -0
- package/src/omdMetaExpression.js +287 -0
- package/src/omdNaturalExpression.js +564 -0
- package/src/omdNode.js +384 -0
- package/src/omdNumber.js +53 -0
- package/src/omdNumberLine.js +107 -0
- package/src/omdNumberTile.js +119 -0
- package/src/omdOperator.js +73 -0
- package/src/omdPowerExpression.js +92 -0
- package/src/omdProblem.js +55 -0
- package/src/omdRatioChart.js +232 -0
- package/src/omdRationalExpression.js +115 -0
- package/src/omdSampleData.js +215 -0
- package/src/omdShapes.js +476 -0
- package/src/omdSpinner.js +148 -0
- package/src/omdString.js +39 -0
- package/src/omdTable.js +369 -0
- package/src/omdTapeDiagram.js +245 -0
- package/src/omdTerm.js +92 -0
- package/src/omdTileEquation.js +349 -0
- 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
|
+
}
|