@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,491 @@
|
|
|
1
|
+
import { omdColor } from '../../src/omdColor.js';
|
|
2
|
+
import { jsvgLayoutGroup, jsvgTextBox } from '@teachinglab/jsvg';
|
|
3
|
+
/**
|
|
4
|
+
* Creates interactive step elements using jsvgLayoutGroup for multiple simplification steps
|
|
5
|
+
* Each step is a separate jsvgTextBox that can have hover interactions with the omdSequence
|
|
6
|
+
*/
|
|
7
|
+
export class omdStepVisualizerInteractiveSteps {
|
|
8
|
+
constructor(stepVisualizer, simplificationData) {
|
|
9
|
+
this.stepVisualizer = stepVisualizer;
|
|
10
|
+
this.simplificationData = simplificationData || {};
|
|
11
|
+
this.messages = this._extractMessages(simplificationData);
|
|
12
|
+
this.ruleNames = this._extractRuleNames(simplificationData);
|
|
13
|
+
this.stepElements = [];
|
|
14
|
+
this.layoutGroup = new jsvgLayoutGroup();
|
|
15
|
+
this.layoutGroup.setSpacer(20); // Much larger spacing to prevent clipping
|
|
16
|
+
|
|
17
|
+
// Styling configuration
|
|
18
|
+
this.stepWidth = 380; // Increased width to prevent text cutoff
|
|
19
|
+
this.baseStepHeight = 40; // Increased base height
|
|
20
|
+
this.headerHeight = 40;
|
|
21
|
+
this.fontSize = 14;
|
|
22
|
+
this.smallFontSize = 12;
|
|
23
|
+
|
|
24
|
+
this.setupLayoutGroup();
|
|
25
|
+
this.createStepElements();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extracts messages from simplification data
|
|
30
|
+
* @param {Object} data - Simplification data
|
|
31
|
+
* @returns {Array} Array of clean messages
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
34
|
+
_extractMessages(data) {
|
|
35
|
+
if (!data) return [];
|
|
36
|
+
|
|
37
|
+
let messages = [];
|
|
38
|
+
if (data.rawMessages && Array.isArray(data.rawMessages)) {
|
|
39
|
+
messages = data.rawMessages;
|
|
40
|
+
} else if (data.message) {
|
|
41
|
+
messages = [data.message];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Clean up messages - remove HTML tags and bullet points
|
|
45
|
+
return messages.map(msg => {
|
|
46
|
+
let clean = msg.replace(/<[^>]*>/g, ''); // Strip HTML tags
|
|
47
|
+
clean = clean.replace(/^[•·◦▪▫‣⁃]\s*/, ''); // Strip bullet points
|
|
48
|
+
return clean.trim();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extracts rule names from simplification data
|
|
54
|
+
* @param {Object} data - Simplification data
|
|
55
|
+
* @returns {Array} Array of rule names
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
_extractRuleNames(data) {
|
|
59
|
+
if (!data) return ['Operation'];
|
|
60
|
+
|
|
61
|
+
if (data.ruleNames && Array.isArray(data.ruleNames)) {
|
|
62
|
+
return data.ruleNames;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Default based on data type
|
|
66
|
+
if (data.multipleSimplifications) {
|
|
67
|
+
return ['Multiple Rules'];
|
|
68
|
+
} else {
|
|
69
|
+
return ['Operation'];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sets up the main layout group properties
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
setupLayoutGroup() {
|
|
78
|
+
// Add background using explainColor for the entire step group
|
|
79
|
+
this.backgroundRect = new jsvgRect();
|
|
80
|
+
this.backgroundRect.setWidthAndHeight(this.stepWidth + 24, 100); // Height will be updated, wider for increased padding
|
|
81
|
+
this.backgroundRect.setFillColor(omdColor.lightGray);
|
|
82
|
+
this.backgroundRect.setStrokeColor('#e0e0e0');
|
|
83
|
+
this.backgroundRect.setStrokeWidth(1);
|
|
84
|
+
this.backgroundRect.setCornerRadius(6);
|
|
85
|
+
this.backgroundRect.setPosition(0, 0); // Start at origin, not negative offset
|
|
86
|
+
this.layoutGroup.addChild(this.backgroundRect);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates individual step elements from the messages array
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
createStepElements() {
|
|
94
|
+
if (!this.messages || this.messages.length === 0) return;
|
|
95
|
+
|
|
96
|
+
// Create content container to separate from background
|
|
97
|
+
this.contentGroup = new jsvgLayoutGroup();
|
|
98
|
+
this.contentGroup.setSpacer(8); // Larger spacing between elements
|
|
99
|
+
this.contentGroup.setPosition(12, 12); // More offset from background edge
|
|
100
|
+
|
|
101
|
+
if (this.messages.length === 1) {
|
|
102
|
+
|
|
103
|
+
this.createSingleStepElement(this.messages[0], 0);
|
|
104
|
+
} else {
|
|
105
|
+
|
|
106
|
+
this.createMultipleStepElements();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.contentGroup.doVerticalLayout();
|
|
110
|
+
this.layoutGroup.addChild(this.contentGroup);
|
|
111
|
+
this.updateBackgroundSize();
|
|
112
|
+
|
|
113
|
+
// Debug logging
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Creates a single step element with header
|
|
119
|
+
* @param {string} message - The step message
|
|
120
|
+
* @param {number} index - Step index
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
createSingleStepElement(message, index) {
|
|
124
|
+
// Create header for single step using rule name
|
|
125
|
+
const ruleName = this.ruleNames[0] || 'Operation';
|
|
126
|
+
const headerBox = this.createHeaderBox(ruleName + ':');
|
|
127
|
+
this.contentGroup.addChild(headerBox);
|
|
128
|
+
|
|
129
|
+
// Create the step box
|
|
130
|
+
const stepBox = this.createStepTextBox(message, index, false);
|
|
131
|
+
this.stepElements.push(stepBox);
|
|
132
|
+
this.contentGroup.addChild(stepBox);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates multiple step elements with header
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
createMultipleStepElements() {
|
|
140
|
+
// Only create header for truly multiple steps (more than 1)
|
|
141
|
+
if (this.messages.length > 1) {
|
|
142
|
+
// Create header showing rule names
|
|
143
|
+
let headerText;
|
|
144
|
+
if (this.ruleNames.length === 1) {
|
|
145
|
+
headerText = this.ruleNames[0] + ':';
|
|
146
|
+
} else if (this.ruleNames.length <= 3) {
|
|
147
|
+
headerText = this.ruleNames.join(' + ') + ':';
|
|
148
|
+
} else {
|
|
149
|
+
headerText = `${this.ruleNames.length} Rules Applied:`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const headerBox = this.createHeaderBox(headerText);
|
|
153
|
+
this.contentGroup.addChild(headerBox);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Create individual step elements
|
|
157
|
+
this.messages.forEach((message, index) => {
|
|
158
|
+
const stepBox = this.createStepTextBox(message, index, this.messages.length > 1);
|
|
159
|
+
this.stepElements.push(stepBox);
|
|
160
|
+
this.contentGroup.addChild(stepBox);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Creates a header box with custom text
|
|
166
|
+
* @param {string} headerText - Text to display in header
|
|
167
|
+
* @returns {jsvgTextBox} Header text box
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
createHeaderBox(headerText = 'Operation:') {
|
|
171
|
+
const headerBox = new jsvgTextBox();
|
|
172
|
+
const headerHeight = Math.max(this.headerHeight, 40); // Ensure minimum height
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
headerBox.setWidthAndHeight(this.stepWidth, headerHeight);
|
|
176
|
+
headerBox.setText(headerText);
|
|
177
|
+
headerBox.setFontSize(this.fontSize);
|
|
178
|
+
headerBox.setFontWeight('600');
|
|
179
|
+
headerBox.setFontColor('#2c3e50');
|
|
180
|
+
|
|
181
|
+
// Style the header with border
|
|
182
|
+
if (headerBox.div) {
|
|
183
|
+
Object.assign(headerBox.div.style, {
|
|
184
|
+
borderBottom: '1px solid #e0e0e0',
|
|
185
|
+
padding: '8px 12px 6px 12px',
|
|
186
|
+
margin: '0',
|
|
187
|
+
boxSizing: 'border-box',
|
|
188
|
+
minHeight: `${headerHeight}px`,
|
|
189
|
+
overflow: 'visible',
|
|
190
|
+
whiteSpace: 'normal',
|
|
191
|
+
wordWrap: 'break-word',
|
|
192
|
+
overflowWrap: 'break-word',
|
|
193
|
+
width: '100%'
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
return headerBox;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Creates an individual step text box
|
|
203
|
+
* @param {string} message - Step message
|
|
204
|
+
* @param {number} index - Step index
|
|
205
|
+
* @param {boolean} isMultiple - Whether this is part of multiple steps
|
|
206
|
+
* @returns {jsvgTextBox} Step text box
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
createStepTextBox(message, index, isMultiple) {
|
|
210
|
+
const stepBox = new jsvgTextBox();
|
|
211
|
+
const height = this.calculateStepHeight(message);
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
stepBox.setWidthAndHeight(this.stepWidth, height);
|
|
216
|
+
stepBox.setFontSize(this.fontSize);
|
|
217
|
+
stepBox.setFontColor('#2c3e50');
|
|
218
|
+
|
|
219
|
+
// Store step data for interactions
|
|
220
|
+
stepBox.stepIndex = index;
|
|
221
|
+
stepBox.stepMessage = message;
|
|
222
|
+
stepBox.isMultiple = isMultiple;
|
|
223
|
+
|
|
224
|
+
// Format the step content
|
|
225
|
+
const formattedContent = this.formatStepContent(message, index, isMultiple);
|
|
226
|
+
|
|
227
|
+
// Apply styling and content
|
|
228
|
+
if (stepBox.div) {
|
|
229
|
+
this.applyStepStyling(stepBox, formattedContent, isMultiple);
|
|
230
|
+
this.setupStepInteractions(stepBox);
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return stepBox;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Formats the content for a step
|
|
240
|
+
* @param {string} message - Raw message
|
|
241
|
+
* @param {number} index - Step index
|
|
242
|
+
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
243
|
+
* @returns {string} Formatted content
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
formatStepContent(message, index, isMultiple) {
|
|
247
|
+
const cleanMessage = message.trim();
|
|
248
|
+
let content = '';
|
|
249
|
+
|
|
250
|
+
// Only show step numbers for multiple steps
|
|
251
|
+
if (isMultiple && this.messages.length > 1) {
|
|
252
|
+
content += `<div class="step-number" style="color: #666; font-size: ${this.smallFontSize}px; margin: 0 0 2px 0; font-weight: 500;">Step ${index + 1}</div>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
content += '<div class="step-content" style="display: flex; align-items: flex-start; gap: 8px; margin: 0; width: 100%;">';
|
|
256
|
+
content += '<span class="bullet" style="color: #666; font-weight: bold; flex-shrink: 0; margin-top: 2px;">•</span>';
|
|
257
|
+
content += '<div class="step-text" style="margin: 0; flex: 1; min-width: 0; word-wrap: break-word; overflow-wrap: break-word;">';
|
|
258
|
+
|
|
259
|
+
// Parse operation details
|
|
260
|
+
if (this.isOperationMessage(cleanMessage)) {
|
|
261
|
+
const action = this.extractOperationAction(cleanMessage);
|
|
262
|
+
const value = this.extractOperationValue(cleanMessage);
|
|
263
|
+
const valueNode = this.extractOperationValueNode(cleanMessage);
|
|
264
|
+
|
|
265
|
+
if (action && (value || valueNode)) {
|
|
266
|
+
content += `<span style="font-weight: 600; color: #2c3e50;">${action}</span> `;
|
|
267
|
+
const displayValue = valueNode ? valueNode.toString() : value;
|
|
268
|
+
content += `<span style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #d63384;">${displayValue}</span>`;
|
|
269
|
+
content += `<span style="color: #666; font-size: ${this.smallFontSize}px;"> to both sides</span>`;
|
|
270
|
+
} else {
|
|
271
|
+
content += `<span>${cleanMessage}</span>`;
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
content += `<span style="font-weight: 500;">${cleanMessage}</span>`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
content += '</div></div>';
|
|
278
|
+
return content;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Applies styling to a step text box
|
|
283
|
+
* @param {jsvgTextBox} stepBox - The step box
|
|
284
|
+
* @param {string} content - Formatted content
|
|
285
|
+
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
applyStepStyling(stepBox, content, isMultiple) {
|
|
289
|
+
const baseStyles = {
|
|
290
|
+
padding: '8px 12px',
|
|
291
|
+
borderRadius: '4px',
|
|
292
|
+
transition: 'all 0.2s ease',
|
|
293
|
+
cursor: 'pointer',
|
|
294
|
+
lineHeight: '1.5',
|
|
295
|
+
backgroundColor: 'transparent',
|
|
296
|
+
margin: '0',
|
|
297
|
+
boxSizing: 'border-box',
|
|
298
|
+
overflow: 'visible',
|
|
299
|
+
minHeight: `${this.baseStepHeight}px`,
|
|
300
|
+
width: '100%',
|
|
301
|
+
whiteSpace: 'normal', // Allow text wrapping
|
|
302
|
+
wordWrap: 'break-word', // Break long words if necessary
|
|
303
|
+
overflowWrap: 'break-word', // Modern standard for word breaking
|
|
304
|
+
maxWidth: '100%' // Ensure content doesn't exceed container
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
Object.assign(stepBox.div.style, baseStyles);
|
|
308
|
+
stepBox.div.innerHTML = content;
|
|
309
|
+
|
|
310
|
+
// Force a reflow to ensure proper sizing
|
|
311
|
+
stepBox.div.offsetHeight;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Sets up hover and click interactions for a step
|
|
316
|
+
* @param {jsvgTextBox} stepBox - The step box
|
|
317
|
+
* @private
|
|
318
|
+
*/
|
|
319
|
+
setupStepInteractions(stepBox) {
|
|
320
|
+
// Hover effects
|
|
321
|
+
stepBox.div.addEventListener('mouseenter', () => {
|
|
322
|
+
stepBox.div.style.backgroundColor = omdColor.mediumGray; // Slightly darker version of explainColor
|
|
323
|
+
stepBox.div.style.transform = 'translateX(2px)';
|
|
324
|
+
|
|
325
|
+
// Console logging for hover enter
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
// Call hover callback if provided
|
|
329
|
+
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
stepBox.div.addEventListener('mouseleave', () => {
|
|
333
|
+
stepBox.div.style.backgroundColor = 'transparent';
|
|
334
|
+
stepBox.div.style.transform = 'translateX(0)';
|
|
335
|
+
|
|
336
|
+
// Console logging for hover leave
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
// Call hover callback if provided
|
|
340
|
+
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, false);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Click interactions
|
|
344
|
+
stepBox.div.addEventListener('click', () => {
|
|
345
|
+
// Console logging for clicks
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
this.onStepClick?.(stepBox.stepIndex, stepBox.stepMessage);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Calculates the height needed for a step box
|
|
354
|
+
* @param {string} message - Step message
|
|
355
|
+
* @returns {number} Height in pixels
|
|
356
|
+
* @private
|
|
357
|
+
*/
|
|
358
|
+
calculateStepHeight(message) {
|
|
359
|
+
// Create a temporary element to measure actual text height
|
|
360
|
+
const tempDiv = document.createElement('div');
|
|
361
|
+
tempDiv.style.position = 'absolute';
|
|
362
|
+
tempDiv.style.visibility = 'hidden';
|
|
363
|
+
tempDiv.style.width = `${this.stepWidth - 24}px`; // Account for padding
|
|
364
|
+
tempDiv.style.fontSize = `${this.fontSize}px`;
|
|
365
|
+
tempDiv.style.lineHeight = '1.5';
|
|
366
|
+
tempDiv.style.fontFamily = 'inherit';
|
|
367
|
+
tempDiv.style.whiteSpace = 'normal';
|
|
368
|
+
tempDiv.style.wordWrap = 'break-word';
|
|
369
|
+
tempDiv.style.overflowWrap = 'break-word';
|
|
370
|
+
tempDiv.style.padding = '8px 12px';
|
|
371
|
+
tempDiv.style.boxSizing = 'border-box';
|
|
372
|
+
|
|
373
|
+
// Use actual formatted content for measurement
|
|
374
|
+
const isMultiple = this.messages && this.messages.length > 1;
|
|
375
|
+
const formattedContent = this.formatStepContent(message, 0, isMultiple);
|
|
376
|
+
tempDiv.innerHTML = formattedContent;
|
|
377
|
+
|
|
378
|
+
// Append to document to measure
|
|
379
|
+
document.body.appendChild(tempDiv);
|
|
380
|
+
const measuredHeight = tempDiv.offsetHeight;
|
|
381
|
+
document.body.removeChild(tempDiv);
|
|
382
|
+
|
|
383
|
+
// Ensure minimum height and add buffer for interactions
|
|
384
|
+
const finalHeight = Math.max(this.baseStepHeight, measuredHeight + 8);
|
|
385
|
+
|
|
386
|
+
return finalHeight;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Updates the background rectangle size after layout
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
updateBackgroundSize() {
|
|
394
|
+
if (this.backgroundRect && this.contentGroup) {
|
|
395
|
+
const totalHeight = this.contentGroup.height + 24; // More padding for larger offset
|
|
396
|
+
const totalWidth = this.stepWidth + 24; // More padding for larger offset
|
|
397
|
+
this.backgroundRect.setWidthAndHeight(totalWidth, totalHeight);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Sets callback for step hover events
|
|
403
|
+
* @param {Function} callback - Function called with (stepIndex, message, isEntering)
|
|
404
|
+
*/
|
|
405
|
+
setOnStepHover(callback) {
|
|
406
|
+
this.onStepHover = callback;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Sets callback for step click events
|
|
411
|
+
* @param {Function} callback - Function called with (stepIndex, message)
|
|
412
|
+
*/
|
|
413
|
+
setOnStepClick(callback) {
|
|
414
|
+
this.onStepClick = callback;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Gets the main layout group for adding to parent containers
|
|
419
|
+
* @returns {jsvgLayoutGroup} The layout group
|
|
420
|
+
*/
|
|
421
|
+
getLayoutGroup() {
|
|
422
|
+
return this.layoutGroup;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Sets the position of the entire step group
|
|
427
|
+
* @param {number} x - X position
|
|
428
|
+
* @param {number} y - Y position
|
|
429
|
+
*/
|
|
430
|
+
setPosition(x, y) {
|
|
431
|
+
this.layoutGroup.setPosition(x, y);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Gets the dimensions of the step group
|
|
436
|
+
* @returns {Object} Width and height
|
|
437
|
+
*/
|
|
438
|
+
getDimensions() {
|
|
439
|
+
return {
|
|
440
|
+
width: this.backgroundRect ? this.backgroundRect.width : this.stepWidth + 16,
|
|
441
|
+
height: this.backgroundRect ? this.backgroundRect.height : 100
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Helper methods for message parsing (same as in formatter)
|
|
446
|
+
|
|
447
|
+
isOperationMessage(message) {
|
|
448
|
+
const operationKeywords = ['Applied', 'added', 'subtracted', 'multiplied', 'divided', 'both sides'];
|
|
449
|
+
return operationKeywords.some(keyword =>
|
|
450
|
+
message.toLowerCase().includes(keyword.toLowerCase())
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
extractOperationAction(message) {
|
|
455
|
+
const match = message.match(/^(Added|Subtracted|Multiplied|Divided)/i);
|
|
456
|
+
return match ? match[0] : null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
extractOperationValue(message) {
|
|
460
|
+
// Updated regex to handle simple values and expressions
|
|
461
|
+
const match = message.match(/(?:Added|Subtracted|Multiplied|Divided)\s(.*?)\s(?:to|by)/i);
|
|
462
|
+
if (match && match[1]) {
|
|
463
|
+
// Avoid returning "[object Object]"
|
|
464
|
+
if (match[1].includes('[object Object]')) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
return match[1];
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
extractOperationValueNode(message) {
|
|
473
|
+
if (this.simplificationData && this.simplificationData.operationValueNode) {
|
|
474
|
+
return this.simplificationData.operationValueNode;
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Destroys the step group and cleans up resources
|
|
481
|
+
*/
|
|
482
|
+
destroy() {
|
|
483
|
+
this.stepElements = [];
|
|
484
|
+
if (this.contentGroup) {
|
|
485
|
+
this.contentGroup.removeAllChildren();
|
|
486
|
+
}
|
|
487
|
+
this.layoutGroup.removeAllChildren();
|
|
488
|
+
this.onStepHover = null;
|
|
489
|
+
this.onStepClick = null;
|
|
490
|
+
}
|
|
491
|
+
}
|