@teachinglab/omd 0.6.0 → 0.6.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.
- package/README.md +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +546 -546
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +969 -962
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -56
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
|
@@ -1,685 +1,685 @@
|
|
|
1
|
-
import { omdColor } from '../../src/omdColor.js';
|
|
2
|
-
import { jsvgLayoutGroup, jsvgTextBox, jsvgRect } 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, stylingOptions = {}) {
|
|
9
|
-
this.stepVisualizer = stepVisualizer;
|
|
10
|
-
this.simplificationData = simplificationData || {};
|
|
11
|
-
this.stylingOptions = stylingOptions || {};
|
|
12
|
-
this.messages = this._extractMessages(simplificationData);
|
|
13
|
-
this.ruleNames = this._extractRuleNames(simplificationData);
|
|
14
|
-
this.stepElements = [];
|
|
15
|
-
this.layoutGroup = new jsvgLayoutGroup();
|
|
16
|
-
this.layoutGroup.setSpacer(4); // Minimal spacing for tight layout
|
|
17
|
-
|
|
18
|
-
// Styling configuration with defaults that can be overridden
|
|
19
|
-
this.stepWidth = this.stylingOptions.maxWidth || 300; // Use maxWidth from styling options
|
|
20
|
-
this.baseStepHeight = 30; // Minimal height for tight fit
|
|
21
|
-
this.headerHeight = 28; // Minimal header height
|
|
22
|
-
this.fontSize = this.stylingOptions.fontSize || 14;
|
|
23
|
-
this.smallFontSize = 12;
|
|
24
|
-
|
|
25
|
-
this.setupLayoutGroup();
|
|
26
|
-
this.createStepElements();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Extracts messages from simplification data
|
|
31
|
-
* @param {Object} data - Simplification data
|
|
32
|
-
* @returns {Array} Array of clean messages
|
|
33
|
-
* @private
|
|
34
|
-
*/
|
|
35
|
-
_extractMessages(data) {
|
|
36
|
-
if (!data) return [];
|
|
37
|
-
|
|
38
|
-
let messages = [];
|
|
39
|
-
if (data.rawMessages && Array.isArray(data.rawMessages)) {
|
|
40
|
-
messages = data.rawMessages;
|
|
41
|
-
} else if (data.message) {
|
|
42
|
-
messages = [data.message];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Clean up messages - remove HTML tags and bullet points
|
|
46
|
-
return messages.map(msg => {
|
|
47
|
-
let clean = msg.replace(/<[^>]*>/g, ''); // Strip HTML tags
|
|
48
|
-
clean = clean.replace(/^[•·◦▪▫‣⁃]\s*/, ''); // Strip bullet points
|
|
49
|
-
return clean.trim();
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Extracts rule names from simplification data
|
|
55
|
-
* @param {Object} data - Simplification data
|
|
56
|
-
* @returns {Array} Array of rule names
|
|
57
|
-
* @private
|
|
58
|
-
*/
|
|
59
|
-
_extractRuleNames(data) {
|
|
60
|
-
if (!data) return ['Operation'];
|
|
61
|
-
|
|
62
|
-
if (data.ruleNames && Array.isArray(data.ruleNames)) {
|
|
63
|
-
return data.ruleNames;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Default based on data type
|
|
67
|
-
if (data.multipleSimplifications) {
|
|
68
|
-
return ['Multiple Rules'];
|
|
69
|
-
} else {
|
|
70
|
-
return ['Operation'];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Sets up the main layout group properties
|
|
76
|
-
* @private
|
|
77
|
-
*/
|
|
78
|
-
setupLayoutGroup() {
|
|
79
|
-
// Add background using styling options for the entire step group
|
|
80
|
-
this.backgroundRect = new jsvgRect();
|
|
81
|
-
this.backgroundRect.setWidthAndHeight(this.stepWidth + 16, 60); // Minimal padding and height for tight fit
|
|
82
|
-
|
|
83
|
-
// Apply styling options to the background container
|
|
84
|
-
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
|
|
85
|
-
const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
|
|
86
|
-
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
87
|
-
const borderRadius = this.stylingOptions.borderRadius || 6;
|
|
88
|
-
|
|
89
|
-
this.backgroundRect.setFillColor(backgroundColor);
|
|
90
|
-
this.backgroundRect.setStrokeColor(borderColor);
|
|
91
|
-
this.backgroundRect.setStrokeWidth(borderWidth);
|
|
92
|
-
this.backgroundRect.setCornerRadius(borderRadius);
|
|
93
|
-
this.backgroundRect.setPosition(0, 0); // Start at origin, not negative offset
|
|
94
|
-
|
|
95
|
-
// Apply drop shadow to the SVG element if requested
|
|
96
|
-
if (this.stylingOptions.dropShadow && this.backgroundRect.svgObject) {
|
|
97
|
-
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.layoutGroup.addChild(this.backgroundRect);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Creates individual step elements from the messages array
|
|
105
|
-
* @private
|
|
106
|
-
*/
|
|
107
|
-
createStepElements() {
|
|
108
|
-
if (!this.messages || this.messages.length === 0) return;
|
|
109
|
-
|
|
110
|
-
// Create content container to separate from background
|
|
111
|
-
this.contentGroup = new jsvgLayoutGroup();
|
|
112
|
-
this.contentGroup.setSpacer(2); // Minimal spacing between elements
|
|
113
|
-
this.contentGroup.setPosition(8, 8); // Minimal offset for tight fit
|
|
114
|
-
|
|
115
|
-
if (this.messages.length === 1) {
|
|
116
|
-
|
|
117
|
-
this.createSingleStepElement(this.messages[0], 0);
|
|
118
|
-
} else {
|
|
119
|
-
|
|
120
|
-
this.createMultipleStepElements();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.contentGroup.doVerticalLayout();
|
|
124
|
-
this.layoutGroup.addChild(this.contentGroup);
|
|
125
|
-
this.updateBackgroundSize();
|
|
126
|
-
|
|
127
|
-
// Apply drop shadow after SVG element is created
|
|
128
|
-
setTimeout(() => {
|
|
129
|
-
this.applyDropShadowIfNeeded();
|
|
130
|
-
}, 10);
|
|
131
|
-
|
|
132
|
-
// Debug logging
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Creates a single step element with header
|
|
138
|
-
* @param {string} message - The step message
|
|
139
|
-
* @param {number} index - Step index
|
|
140
|
-
* @private
|
|
141
|
-
*/
|
|
142
|
-
createSingleStepElement(message, index) {
|
|
143
|
-
// Create header for single step using rule name
|
|
144
|
-
const ruleName = this.ruleNames[0] || 'Operation';
|
|
145
|
-
const headerBox = this.createHeaderBox(ruleName + ':');
|
|
146
|
-
this.contentGroup.addChild(headerBox);
|
|
147
|
-
|
|
148
|
-
// Create the step box
|
|
149
|
-
const stepBox = this.createStepTextBox(message, index, false);
|
|
150
|
-
this.stepElements.push(stepBox);
|
|
151
|
-
this.contentGroup.addChild(stepBox);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Creates multiple step elements with header
|
|
156
|
-
* @private
|
|
157
|
-
*/
|
|
158
|
-
createMultipleStepElements() {
|
|
159
|
-
// Only create header for truly multiple steps (more than 1)
|
|
160
|
-
if (this.messages.length > 1) {
|
|
161
|
-
// Create header showing rule names
|
|
162
|
-
let headerText;
|
|
163
|
-
if (this.ruleNames.length === 1) {
|
|
164
|
-
headerText = this.ruleNames[0] + ':';
|
|
165
|
-
} else if (this.ruleNames.length <= 3) {
|
|
166
|
-
headerText = this.ruleNames.join(' + ') + ':';
|
|
167
|
-
} else {
|
|
168
|
-
headerText = `${this.ruleNames.length} Rules Applied:`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const headerBox = this.createHeaderBox(headerText);
|
|
172
|
-
this.contentGroup.addChild(headerBox);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Create individual step elements
|
|
176
|
-
this.messages.forEach((message, index) => {
|
|
177
|
-
const stepBox = this.createStepTextBox(message, index, this.messages.length > 1);
|
|
178
|
-
this.stepElements.push(stepBox);
|
|
179
|
-
this.contentGroup.addChild(stepBox);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Creates a header box with custom text
|
|
185
|
-
* @param {string} headerText - Text to display in header
|
|
186
|
-
* @returns {jsvgTextBox} Header text box
|
|
187
|
-
* @private
|
|
188
|
-
*/
|
|
189
|
-
createHeaderBox(headerText = 'Operation:') {
|
|
190
|
-
const headerBox = new jsvgTextBox();
|
|
191
|
-
const headerHeight = Math.max(this.headerHeight, 50); // Increased minimum height
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
headerBox.setWidthAndHeight(this.stepWidth, headerHeight);
|
|
195
|
-
headerBox.setText(headerText);
|
|
196
|
-
headerBox.setFontSize(this.fontSize);
|
|
197
|
-
headerBox.setFontWeight('600');
|
|
198
|
-
headerBox.setFontColor('#2c3e50');
|
|
199
|
-
|
|
200
|
-
// Style the header with border and minimal spacing
|
|
201
|
-
if (headerBox.div) {
|
|
202
|
-
Object.assign(headerBox.div.style, {
|
|
203
|
-
borderBottom: '1px solid #e0e0e0',
|
|
204
|
-
padding: '6px 8px 4px 8px', // Minimal padding for tight fit
|
|
205
|
-
margin: '0',
|
|
206
|
-
boxSizing: 'border-box',
|
|
207
|
-
minHeight: `${headerHeight}px`,
|
|
208
|
-
overflow: 'visible',
|
|
209
|
-
whiteSpace: 'normal',
|
|
210
|
-
wordWrap: 'break-word',
|
|
211
|
-
overflowWrap: 'break-word',
|
|
212
|
-
width: '100%',
|
|
213
|
-
lineHeight: '1.2', // Tight line height
|
|
214
|
-
fontFamily: 'Albert Sans, Arial, sans-serif',
|
|
215
|
-
display: 'flex',
|
|
216
|
-
alignItems: 'center', // Center text vertically
|
|
217
|
-
justifyContent: 'flex-start'
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return headerBox;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Creates an individual step text box
|
|
227
|
-
* @param {string} message - Step message
|
|
228
|
-
* @param {number} index - Step index
|
|
229
|
-
* @param {boolean} isMultiple - Whether this is part of multiple steps
|
|
230
|
-
* @returns {jsvgTextBox} Step text box
|
|
231
|
-
* @private
|
|
232
|
-
*/
|
|
233
|
-
createStepTextBox(message, index, isMultiple) {
|
|
234
|
-
const stepBox = new jsvgTextBox();
|
|
235
|
-
// Calculate actual height needed for content with minimal padding
|
|
236
|
-
const contentHeight = this.calculateContentHeight(message, index, isMultiple);
|
|
237
|
-
const height = Math.max(contentHeight, this.baseStepHeight); // Use calculated height
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
stepBox.setWidthAndHeight(this.stepWidth, height);
|
|
242
|
-
stepBox.setFontSize(this.fontSize);
|
|
243
|
-
stepBox.setFontColor('#2c3e50');
|
|
244
|
-
|
|
245
|
-
// Store step data for interactions
|
|
246
|
-
stepBox.stepIndex = index;
|
|
247
|
-
stepBox.stepMessage = message;
|
|
248
|
-
stepBox.isMultiple = isMultiple;
|
|
249
|
-
|
|
250
|
-
// Format the step content
|
|
251
|
-
const formattedContent = this.formatStepContent(message, index, isMultiple);
|
|
252
|
-
|
|
253
|
-
// Apply styling and content
|
|
254
|
-
if (stepBox.div) {
|
|
255
|
-
this.applyStepStyling(stepBox, formattedContent, isMultiple, height);
|
|
256
|
-
this.setupStepInteractions(stepBox);
|
|
257
|
-
|
|
258
|
-
// Force the jsvgTextBox to respect our sizing
|
|
259
|
-
stepBox.div.style.height = `${height}px`;
|
|
260
|
-
stepBox.div.style.minHeight = `${height}px`;
|
|
261
|
-
stepBox.div.style.display = 'block';
|
|
262
|
-
|
|
263
|
-
// Add a more aggressive override after a delay to ensure it sticks
|
|
264
|
-
if (stepBox.div) {
|
|
265
|
-
const actualPadding = this.stylingOptions.padding || 6; // Get padding from styling options
|
|
266
|
-
stepBox.div.style.cssText += `
|
|
267
|
-
height: ${height}px !important;
|
|
268
|
-
min-height: ${height}px !important;
|
|
269
|
-
max-height: ${height}px !important;
|
|
270
|
-
padding: ${actualPadding}px ${actualPadding + 2}px !important;
|
|
271
|
-
line-height: 1.3 !important;
|
|
272
|
-
font-size: ${this.fontSize}px !important;
|
|
273
|
-
font-family: Albert Sans, Arial, sans-serif !important;
|
|
274
|
-
box-sizing: border-box !important;
|
|
275
|
-
display: flex !important;
|
|
276
|
-
flex-direction: column !important;
|
|
277
|
-
justify-content: center !important;
|
|
278
|
-
align-items: flex-start !important;
|
|
279
|
-
word-spacing: normal !important;
|
|
280
|
-
letter-spacing: normal !important;
|
|
281
|
-
transition: none !important;
|
|
282
|
-
transform: none !important;
|
|
283
|
-
animation: none !important;
|
|
284
|
-
`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return stepBox;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Formats the content for a step
|
|
294
|
-
* @param {string} message - Raw message
|
|
295
|
-
* @param {number} index - Step index
|
|
296
|
-
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
297
|
-
* @returns {string} Formatted content
|
|
298
|
-
* @private
|
|
299
|
-
*/
|
|
300
|
-
formatStepContent(message, index, isMultiple) {
|
|
301
|
-
const cleanMessage = message.trim();
|
|
302
|
-
let content = '';
|
|
303
|
-
|
|
304
|
-
// Only show step numbers for multiple steps
|
|
305
|
-
if (isMultiple && this.messages.length > 1) {
|
|
306
|
-
content += `<div class="step-number" style="color: #666; font-size: ${this.smallFontSize}px; margin: 0 0 2px 0; font-weight: 500; line-height: 1.2; font-family: Albert Sans, Arial, sans-serif;">Step ${index + 1}</div>`; // Minimal margin
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
content += '<div class="step-content" style="display: flex; align-items: center; gap: 6px; margin: 0; width: 100%; line-height: 1.3; font-family: Albert Sans, Arial, sans-serif;">'; // Center align and minimal spacing
|
|
310
|
-
content += '<span class="bullet" style="color: #666; font-weight: bold; flex-shrink: 0; font-size: 10px;">•</span>'; // Smaller bullet
|
|
311
|
-
content += '<div class="step-text" style="margin: 0; flex: 1; min-width: 0; word-wrap: break-word; overflow-wrap: break-word; line-height: 1.3; padding: 0;">';
|
|
312
|
-
|
|
313
|
-
// Parse operation details
|
|
314
|
-
if (this.isOperationMessage(cleanMessage)) {
|
|
315
|
-
const action = this.extractOperationAction(cleanMessage);
|
|
316
|
-
const value = this.extractOperationValue(cleanMessage);
|
|
317
|
-
const valueNode = this.extractOperationValueNode(cleanMessage);
|
|
318
|
-
|
|
319
|
-
if (action && (value || valueNode)) {
|
|
320
|
-
content += `<span style="font-weight: 600; color: #2c3e50; margin-right: 4px;">${action}</span> `;
|
|
321
|
-
const displayValue = valueNode ? valueNode.toString() : value;
|
|
322
|
-
content += `<span style="background: #f5f5f5; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; color: #d63384; margin: 0 3px;">${displayValue}</span>`;
|
|
323
|
-
content += `<span style="color: #666; font-size: ${this.smallFontSize}px; margin-left: 4px;"> to both sides</span>`;
|
|
324
|
-
} else {
|
|
325
|
-
content += `<span style="padding: 2px 0;">${cleanMessage}</span>`;
|
|
326
|
-
}
|
|
327
|
-
} else {
|
|
328
|
-
content += `<span style="font-weight: 500; padding: 2px 0;">${cleanMessage}</span>`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
content += '</div></div>';
|
|
332
|
-
return content;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Applies styling to a step text box
|
|
337
|
-
* @param {jsvgTextBox} stepBox - The step box
|
|
338
|
-
* @param {string} content - Formatted content
|
|
339
|
-
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
340
|
-
* @param {number} height - The calculated height for the step box
|
|
341
|
-
* @private
|
|
342
|
-
*/
|
|
343
|
-
applyStepStyling(stepBox, content, isMultiple, height) {
|
|
344
|
-
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.white;
|
|
345
|
-
const borderColor = this.stylingOptions.borderColor || omdColor.lightGray;
|
|
346
|
-
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
347
|
-
const borderRadius = this.stylingOptions.borderRadius || 4;
|
|
348
|
-
const padding = this.stylingOptions.padding || 6; // Minimal padding for tight fit
|
|
349
|
-
|
|
350
|
-
const baseStyles = {
|
|
351
|
-
padding: `${padding}px ${padding + 2}px !important`, // Minimal padding for tight fit
|
|
352
|
-
borderRadius: `${borderRadius}px`,
|
|
353
|
-
border: `${borderWidth}px solid ${borderColor}`,
|
|
354
|
-
backgroundColor: backgroundColor,
|
|
355
|
-
cursor: 'pointer',
|
|
356
|
-
transition: 'none !important', // Explicitly disable all transitions
|
|
357
|
-
transform: 'none !important', // Explicitly disable all transforms
|
|
358
|
-
animation: 'none !important', // Explicitly disable all animations
|
|
359
|
-
lineHeight: '1.3 !important', // Tight line height
|
|
360
|
-
margin: '0',
|
|
361
|
-
boxSizing: 'border-box',
|
|
362
|
-
overflow: 'visible',
|
|
363
|
-
minHeight: `${height}px !important`, // Use calculated height
|
|
364
|
-
height: `${height}px !important`, // Fixed height to content
|
|
365
|
-
width: '100%',
|
|
366
|
-
whiteSpace: 'normal',
|
|
367
|
-
wordWrap: 'break-word',
|
|
368
|
-
overflowWrap: 'break-word',
|
|
369
|
-
maxWidth: '100%',
|
|
370
|
-
fontSize: `${this.fontSize}px !important`, // Force font size
|
|
371
|
-
fontFamily: 'Albert Sans, Arial, sans-serif !important', // Albert Sans font
|
|
372
|
-
display: 'flex !important', // Use flex for centering
|
|
373
|
-
flexDirection: 'column !important',
|
|
374
|
-
justifyContent: 'center !important', // Center content vertically
|
|
375
|
-
alignItems: 'flex-start !important'
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
// Add drop shadow if requested - but NOT to individual step boxes
|
|
379
|
-
// The drop shadow should only be on the outer background rectangle
|
|
380
|
-
// Remove any previous drop shadow from individual steps
|
|
381
|
-
if (stepBox.div) {
|
|
382
|
-
stepBox.div.style.boxShadow = 'none';
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Set font family if specified
|
|
386
|
-
if (this.stylingOptions.fontFamily) {
|
|
387
|
-
baseStyles.fontFamily = this.stylingOptions.fontFamily;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
Object.assign(stepBox.div.style, baseStyles);
|
|
391
|
-
stepBox.div.innerHTML = content;
|
|
392
|
-
|
|
393
|
-
// Additional CSS to force proper text spacing
|
|
394
|
-
if (stepBox.div) {
|
|
395
|
-
stepBox.div.style.cssText += `
|
|
396
|
-
padding: ${padding + 6}px ${padding + 10}px !important;
|
|
397
|
-
line-height: 1.7 !important;
|
|
398
|
-
min-height: ${this.baseStepHeight + 20}px !important;
|
|
399
|
-
font-size: ${this.fontSize}px !important;
|
|
400
|
-
display: flex !important;
|
|
401
|
-
flex-direction: column !important;
|
|
402
|
-
`;
|
|
403
|
-
|
|
404
|
-
// Apply proper layout to nested content - DO NOT use position absolute
|
|
405
|
-
const contentElements = stepBox.div.querySelectorAll('.step-content, .step-text');
|
|
406
|
-
contentElements.forEach(el => {
|
|
407
|
-
el.style.lineHeight = '1.3 !important';
|
|
408
|
-
el.style.margin = '0 !important';
|
|
409
|
-
el.style.fontFamily = 'Albert Sans, Arial, sans-serif !important';
|
|
410
|
-
// Remove any position absolute that might be inherited
|
|
411
|
-
el.style.position = 'static !important';
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Ensure bullet points and text spans stay in normal flow
|
|
415
|
-
const allSpans = stepBox.div.querySelectorAll('span');
|
|
416
|
-
allSpans.forEach(span => {
|
|
417
|
-
span.style.position = 'static !important';
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Force a reflow to ensure proper sizing
|
|
422
|
-
stepBox.div.offsetHeight;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Sets up hover and click interactions for a step
|
|
427
|
-
* @param {jsvgTextBox} stepBox - The step box
|
|
428
|
-
* @private
|
|
429
|
-
*/
|
|
430
|
-
setupStepInteractions(stepBox) {
|
|
431
|
-
// Store the original background color to restore on mouseleave
|
|
432
|
-
const originalBackgroundColor = stepBox.div.style.backgroundColor || '';
|
|
433
|
-
|
|
434
|
-
// Hover effects
|
|
435
|
-
stepBox.div.addEventListener('mouseenter', () => {
|
|
436
|
-
stepBox.div.style.backgroundColor = omdColor.mediumGray; // Slightly darker version of explainColor
|
|
437
|
-
|
|
438
|
-
// Call hover callback if provided
|
|
439
|
-
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, true);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
stepBox.div.addEventListener('mouseleave', () => {
|
|
443
|
-
// Restore the original background color instead of setting to transparent
|
|
444
|
-
stepBox.div.style.backgroundColor = originalBackgroundColor;
|
|
445
|
-
|
|
446
|
-
// Call hover callback if provided
|
|
447
|
-
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, false);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// Click interactions
|
|
451
|
-
stepBox.div.addEventListener('click', () => {
|
|
452
|
-
this.onStepClick?.(stepBox.stepIndex, stepBox.stepMessage);
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Calculates the exact height needed for content with minimal padding
|
|
458
|
-
* @param {string} message - Step message
|
|
459
|
-
* @param {number} index - Step index
|
|
460
|
-
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
461
|
-
* @returns {number} Tight-fitting height in pixels
|
|
462
|
-
* @private
|
|
463
|
-
*/
|
|
464
|
-
calculateContentHeight(message, index, isMultiple) {
|
|
465
|
-
// Create a temporary element to measure actual text height
|
|
466
|
-
const tempDiv = document.createElement('div');
|
|
467
|
-
tempDiv.style.position = 'absolute';
|
|
468
|
-
tempDiv.style.visibility = 'hidden';
|
|
469
|
-
tempDiv.style.width = `${this.stepWidth - 16}px`; // Account for minimal padding
|
|
470
|
-
tempDiv.style.fontSize = `${this.fontSize}px`;
|
|
471
|
-
tempDiv.style.lineHeight = '1.3'; // Tight line height
|
|
472
|
-
tempDiv.style.fontFamily = 'Albert Sans, Arial, sans-serif';
|
|
473
|
-
tempDiv.style.whiteSpace = 'normal';
|
|
474
|
-
tempDiv.style.wordWrap = 'break-word';
|
|
475
|
-
tempDiv.style.overflowWrap = 'break-word';
|
|
476
|
-
tempDiv.style.padding = '6px 8px'; // Match the minimal padding
|
|
477
|
-
tempDiv.style.boxSizing = 'border-box';
|
|
478
|
-
tempDiv.style.display = 'flex';
|
|
479
|
-
tempDiv.style.flexDirection = 'column';
|
|
480
|
-
tempDiv.style.justifyContent = 'center';
|
|
481
|
-
|
|
482
|
-
// Use actual formatted content for measurement
|
|
483
|
-
const formattedContent = this.formatStepContent(message, index, isMultiple);
|
|
484
|
-
tempDiv.innerHTML = formattedContent;
|
|
485
|
-
|
|
486
|
-
// Append to document to measure
|
|
487
|
-
document.body.appendChild(tempDiv);
|
|
488
|
-
const measuredHeight = tempDiv.offsetHeight;
|
|
489
|
-
document.body.removeChild(tempDiv);
|
|
490
|
-
|
|
491
|
-
// Return exact measured height with minimal buffer
|
|
492
|
-
return Math.max(this.baseStepHeight, measuredHeight + 2); // Just 2px buffer
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Calculates the height needed for a step box (legacy method, kept for compatibility)
|
|
497
|
-
* @param {string} message - Step message
|
|
498
|
-
* @returns {number} Height in pixels
|
|
499
|
-
* @private
|
|
500
|
-
*/
|
|
501
|
-
calculateStepHeight(message) {
|
|
502
|
-
// Use the new tight-fitting calculation
|
|
503
|
-
return this.calculateContentHeight(message, 0, false);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Updates the background rectangle size after layout
|
|
508
|
-
* @private
|
|
509
|
-
*/
|
|
510
|
-
updateBackgroundSize() {
|
|
511
|
-
if (this.backgroundRect && this.contentGroup) {
|
|
512
|
-
const totalHeight = this.contentGroup.height + 16; // Minimal padding for tight fit
|
|
513
|
-
const totalWidth = this.stepWidth + 16; // Minimal padding for tight fit
|
|
514
|
-
this.backgroundRect.setWidthAndHeight(totalWidth, totalHeight);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Applies drop shadow to the background container if SVG element exists
|
|
520
|
-
* @private
|
|
521
|
-
*/
|
|
522
|
-
applyDropShadowIfNeeded() {
|
|
523
|
-
if (this.stylingOptions.dropShadow && this.backgroundRect && this.backgroundRect.svgObject) {
|
|
524
|
-
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Sets callback for step hover events
|
|
530
|
-
* @param {Function} callback - Function called with (stepIndex, message, isEntering)
|
|
531
|
-
*/
|
|
532
|
-
setOnStepHover(callback) {
|
|
533
|
-
this.onStepHover = callback;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Sets callback for step click events
|
|
538
|
-
* @param {Function} callback - Function called with (stepIndex, message)
|
|
539
|
-
*/
|
|
540
|
-
setOnStepClick(callback) {
|
|
541
|
-
this.onStepClick = callback;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Gets the main layout group for adding to parent containers
|
|
546
|
-
* @returns {jsvgLayoutGroup} The layout group
|
|
547
|
-
*/
|
|
548
|
-
getLayoutGroup() {
|
|
549
|
-
return this.layoutGroup;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Sets the position of the entire step group
|
|
554
|
-
* @param {number} x - X position
|
|
555
|
-
* @param {number} y - Y position
|
|
556
|
-
*/
|
|
557
|
-
setPosition(x, y) {
|
|
558
|
-
this.layoutGroup.setPosition(x, y);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Gets the dimensions of the step group
|
|
563
|
-
* @returns {Object} Width and height
|
|
564
|
-
*/
|
|
565
|
-
getDimensions() {
|
|
566
|
-
return {
|
|
567
|
-
width: this.backgroundRect ? this.backgroundRect.width : this.stepWidth + 16,
|
|
568
|
-
height: this.backgroundRect ? this.backgroundRect.height : 100
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Updates the styling options and re-applies them to existing elements
|
|
574
|
-
* @param {Object} newStylingOptions - New styling options
|
|
575
|
-
*/
|
|
576
|
-
updateStyling(newStylingOptions = {}) {
|
|
577
|
-
this.stylingOptions = { ...this.stylingOptions, ...newStylingOptions };
|
|
578
|
-
|
|
579
|
-
// Update width if maxWidth changed
|
|
580
|
-
if (newStylingOptions.maxWidth) {
|
|
581
|
-
this.stepWidth = newStylingOptions.maxWidth;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// Update font size if changed
|
|
585
|
-
if (newStylingOptions.fontSize) {
|
|
586
|
-
this.fontSize = newStylingOptions.fontSize;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Update background container styling
|
|
590
|
-
if (this.backgroundRect) {
|
|
591
|
-
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
|
|
592
|
-
const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
|
|
593
|
-
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
594
|
-
const borderRadius = this.stylingOptions.borderRadius || 6;
|
|
595
|
-
|
|
596
|
-
this.backgroundRect.setFillColor(backgroundColor);
|
|
597
|
-
this.backgroundRect.setStrokeColor(borderColor);
|
|
598
|
-
this.backgroundRect.setStrokeWidth(borderWidth);
|
|
599
|
-
this.backgroundRect.setCornerRadius(borderRadius);
|
|
600
|
-
|
|
601
|
-
// Apply or remove drop shadow
|
|
602
|
-
if (this.backgroundRect.svgObject) {
|
|
603
|
-
if (this.stylingOptions.dropShadow) {
|
|
604
|
-
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
605
|
-
} else {
|
|
606
|
-
this.backgroundRect.svgObject.style.filter = '';
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Re-apply styling to all existing step elements
|
|
612
|
-
this.stepElements.forEach((stepBox, index) => {
|
|
613
|
-
if (stepBox.div) {
|
|
614
|
-
const content = stepBox.div.innerHTML;
|
|
615
|
-
// Calculate new height for the step
|
|
616
|
-
const height = this.calculateContentHeight(stepBox.stepMessage, index, stepBox.isMultiple);
|
|
617
|
-
this.applyStepStyling(stepBox, content, stepBox.isMultiple, height);
|
|
618
|
-
|
|
619
|
-
// Update font size
|
|
620
|
-
stepBox.setFontSize(this.fontSize);
|
|
621
|
-
|
|
622
|
-
// Update dimensions if needed
|
|
623
|
-
stepBox.setWidthAndHeight(this.stepWidth, height);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Update background size
|
|
628
|
-
this.updateBackgroundSize();
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Gets the current styling options
|
|
633
|
-
* @returns {Object} Current styling options
|
|
634
|
-
*/
|
|
635
|
-
getStyling() {
|
|
636
|
-
return { ...this.stylingOptions };
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Helper methods for message parsing (same as in formatter)
|
|
640
|
-
|
|
641
|
-
isOperationMessage(message) {
|
|
642
|
-
const operationKeywords = ['Applied', 'added', 'subtracted', 'multiplied', 'divided', 'both sides'];
|
|
643
|
-
return operationKeywords.some(keyword =>
|
|
644
|
-
message.toLowerCase().includes(keyword.toLowerCase())
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
extractOperationAction(message) {
|
|
649
|
-
const match = message.match(/^(Added|Subtracted|Multiplied|Divided)/i);
|
|
650
|
-
return match ? match[0] : null;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
extractOperationValue(message) {
|
|
654
|
-
// Updated regex to handle simple values and expressions
|
|
655
|
-
const match = message.match(/(?:Added|Subtracted|Multiplied|Divided)\s(.*?)\s(?:to|by)/i);
|
|
656
|
-
if (match && match[1]) {
|
|
657
|
-
// Avoid returning "[object Object]"
|
|
658
|
-
if (match[1].includes('[object Object]')) {
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
return match[1];
|
|
662
|
-
}
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
extractOperationValueNode(message) {
|
|
667
|
-
if (this.simplificationData && this.simplificationData.operationValueNode) {
|
|
668
|
-
return this.simplificationData.operationValueNode;
|
|
669
|
-
}
|
|
670
|
-
return null;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Destroys the step group and cleans up resources
|
|
675
|
-
*/
|
|
676
|
-
destroy() {
|
|
677
|
-
this.stepElements = [];
|
|
678
|
-
if (this.contentGroup) {
|
|
679
|
-
this.contentGroup.removeAllChildren();
|
|
680
|
-
}
|
|
681
|
-
this.layoutGroup.removeAllChildren();
|
|
682
|
-
this.onStepHover = null;
|
|
683
|
-
this.onStepClick = null;
|
|
684
|
-
}
|
|
1
|
+
import { omdColor } from '../../src/omdColor.js';
|
|
2
|
+
import { jsvgLayoutGroup, jsvgTextBox, jsvgRect } 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, stylingOptions = {}) {
|
|
9
|
+
this.stepVisualizer = stepVisualizer;
|
|
10
|
+
this.simplificationData = simplificationData || {};
|
|
11
|
+
this.stylingOptions = stylingOptions || {};
|
|
12
|
+
this.messages = this._extractMessages(simplificationData);
|
|
13
|
+
this.ruleNames = this._extractRuleNames(simplificationData);
|
|
14
|
+
this.stepElements = [];
|
|
15
|
+
this.layoutGroup = new jsvgLayoutGroup();
|
|
16
|
+
this.layoutGroup.setSpacer(4); // Minimal spacing for tight layout
|
|
17
|
+
|
|
18
|
+
// Styling configuration with defaults that can be overridden
|
|
19
|
+
this.stepWidth = this.stylingOptions.maxWidth || 300; // Use maxWidth from styling options
|
|
20
|
+
this.baseStepHeight = 30; // Minimal height for tight fit
|
|
21
|
+
this.headerHeight = 28; // Minimal header height
|
|
22
|
+
this.fontSize = this.stylingOptions.fontSize || 14;
|
|
23
|
+
this.smallFontSize = 12;
|
|
24
|
+
|
|
25
|
+
this.setupLayoutGroup();
|
|
26
|
+
this.createStepElements();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts messages from simplification data
|
|
31
|
+
* @param {Object} data - Simplification data
|
|
32
|
+
* @returns {Array} Array of clean messages
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
_extractMessages(data) {
|
|
36
|
+
if (!data) return [];
|
|
37
|
+
|
|
38
|
+
let messages = [];
|
|
39
|
+
if (data.rawMessages && Array.isArray(data.rawMessages)) {
|
|
40
|
+
messages = data.rawMessages;
|
|
41
|
+
} else if (data.message) {
|
|
42
|
+
messages = [data.message];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Clean up messages - remove HTML tags and bullet points
|
|
46
|
+
return messages.map(msg => {
|
|
47
|
+
let clean = msg.replace(/<[^>]*>/g, ''); // Strip HTML tags
|
|
48
|
+
clean = clean.replace(/^[•·◦▪▫‣⁃]\s*/, ''); // Strip bullet points
|
|
49
|
+
return clean.trim();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extracts rule names from simplification data
|
|
55
|
+
* @param {Object} data - Simplification data
|
|
56
|
+
* @returns {Array} Array of rule names
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
_extractRuleNames(data) {
|
|
60
|
+
if (!data) return ['Operation'];
|
|
61
|
+
|
|
62
|
+
if (data.ruleNames && Array.isArray(data.ruleNames)) {
|
|
63
|
+
return data.ruleNames;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Default based on data type
|
|
67
|
+
if (data.multipleSimplifications) {
|
|
68
|
+
return ['Multiple Rules'];
|
|
69
|
+
} else {
|
|
70
|
+
return ['Operation'];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Sets up the main layout group properties
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
setupLayoutGroup() {
|
|
79
|
+
// Add background using styling options for the entire step group
|
|
80
|
+
this.backgroundRect = new jsvgRect();
|
|
81
|
+
this.backgroundRect.setWidthAndHeight(this.stepWidth + 16, 60); // Minimal padding and height for tight fit
|
|
82
|
+
|
|
83
|
+
// Apply styling options to the background container
|
|
84
|
+
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
|
|
85
|
+
const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
|
|
86
|
+
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
87
|
+
const borderRadius = this.stylingOptions.borderRadius || 6;
|
|
88
|
+
|
|
89
|
+
this.backgroundRect.setFillColor(backgroundColor);
|
|
90
|
+
this.backgroundRect.setStrokeColor(borderColor);
|
|
91
|
+
this.backgroundRect.setStrokeWidth(borderWidth);
|
|
92
|
+
this.backgroundRect.setCornerRadius(borderRadius);
|
|
93
|
+
this.backgroundRect.setPosition(0, 0); // Start at origin, not negative offset
|
|
94
|
+
|
|
95
|
+
// Apply drop shadow to the SVG element if requested
|
|
96
|
+
if (this.stylingOptions.dropShadow && this.backgroundRect.svgObject) {
|
|
97
|
+
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.layoutGroup.addChild(this.backgroundRect);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates individual step elements from the messages array
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
createStepElements() {
|
|
108
|
+
if (!this.messages || this.messages.length === 0) return;
|
|
109
|
+
|
|
110
|
+
// Create content container to separate from background
|
|
111
|
+
this.contentGroup = new jsvgLayoutGroup();
|
|
112
|
+
this.contentGroup.setSpacer(2); // Minimal spacing between elements
|
|
113
|
+
this.contentGroup.setPosition(8, 8); // Minimal offset for tight fit
|
|
114
|
+
|
|
115
|
+
if (this.messages.length === 1) {
|
|
116
|
+
|
|
117
|
+
this.createSingleStepElement(this.messages[0], 0);
|
|
118
|
+
} else {
|
|
119
|
+
|
|
120
|
+
this.createMultipleStepElements();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.contentGroup.doVerticalLayout();
|
|
124
|
+
this.layoutGroup.addChild(this.contentGroup);
|
|
125
|
+
this.updateBackgroundSize();
|
|
126
|
+
|
|
127
|
+
// Apply drop shadow after SVG element is created
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
this.applyDropShadowIfNeeded();
|
|
130
|
+
}, 10);
|
|
131
|
+
|
|
132
|
+
// Debug logging
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a single step element with header
|
|
138
|
+
* @param {string} message - The step message
|
|
139
|
+
* @param {number} index - Step index
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
createSingleStepElement(message, index) {
|
|
143
|
+
// Create header for single step using rule name
|
|
144
|
+
const ruleName = this.ruleNames[0] || 'Operation';
|
|
145
|
+
const headerBox = this.createHeaderBox(ruleName + ':');
|
|
146
|
+
this.contentGroup.addChild(headerBox);
|
|
147
|
+
|
|
148
|
+
// Create the step box
|
|
149
|
+
const stepBox = this.createStepTextBox(message, index, false);
|
|
150
|
+
this.stepElements.push(stepBox);
|
|
151
|
+
this.contentGroup.addChild(stepBox);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates multiple step elements with header
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
createMultipleStepElements() {
|
|
159
|
+
// Only create header for truly multiple steps (more than 1)
|
|
160
|
+
if (this.messages.length > 1) {
|
|
161
|
+
// Create header showing rule names
|
|
162
|
+
let headerText;
|
|
163
|
+
if (this.ruleNames.length === 1) {
|
|
164
|
+
headerText = this.ruleNames[0] + ':';
|
|
165
|
+
} else if (this.ruleNames.length <= 3) {
|
|
166
|
+
headerText = this.ruleNames.join(' + ') + ':';
|
|
167
|
+
} else {
|
|
168
|
+
headerText = `${this.ruleNames.length} Rules Applied:`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const headerBox = this.createHeaderBox(headerText);
|
|
172
|
+
this.contentGroup.addChild(headerBox);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Create individual step elements
|
|
176
|
+
this.messages.forEach((message, index) => {
|
|
177
|
+
const stepBox = this.createStepTextBox(message, index, this.messages.length > 1);
|
|
178
|
+
this.stepElements.push(stepBox);
|
|
179
|
+
this.contentGroup.addChild(stepBox);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a header box with custom text
|
|
185
|
+
* @param {string} headerText - Text to display in header
|
|
186
|
+
* @returns {jsvgTextBox} Header text box
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
createHeaderBox(headerText = 'Operation:') {
|
|
190
|
+
const headerBox = new jsvgTextBox();
|
|
191
|
+
const headerHeight = Math.max(this.headerHeight, 50); // Increased minimum height
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
headerBox.setWidthAndHeight(this.stepWidth, headerHeight);
|
|
195
|
+
headerBox.setText(headerText);
|
|
196
|
+
headerBox.setFontSize(this.fontSize);
|
|
197
|
+
headerBox.setFontWeight('600');
|
|
198
|
+
headerBox.setFontColor('#2c3e50');
|
|
199
|
+
|
|
200
|
+
// Style the header with border and minimal spacing
|
|
201
|
+
if (headerBox.div) {
|
|
202
|
+
Object.assign(headerBox.div.style, {
|
|
203
|
+
borderBottom: '1px solid #e0e0e0',
|
|
204
|
+
padding: '6px 8px 4px 8px', // Minimal padding for tight fit
|
|
205
|
+
margin: '0',
|
|
206
|
+
boxSizing: 'border-box',
|
|
207
|
+
minHeight: `${headerHeight}px`,
|
|
208
|
+
overflow: 'visible',
|
|
209
|
+
whiteSpace: 'normal',
|
|
210
|
+
wordWrap: 'break-word',
|
|
211
|
+
overflowWrap: 'break-word',
|
|
212
|
+
width: '100%',
|
|
213
|
+
lineHeight: '1.2', // Tight line height
|
|
214
|
+
fontFamily: 'Albert Sans, Arial, sans-serif',
|
|
215
|
+
display: 'flex',
|
|
216
|
+
alignItems: 'center', // Center text vertically
|
|
217
|
+
justifyContent: 'flex-start'
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
return headerBox;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Creates an individual step text box
|
|
227
|
+
* @param {string} message - Step message
|
|
228
|
+
* @param {number} index - Step index
|
|
229
|
+
* @param {boolean} isMultiple - Whether this is part of multiple steps
|
|
230
|
+
* @returns {jsvgTextBox} Step text box
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
createStepTextBox(message, index, isMultiple) {
|
|
234
|
+
const stepBox = new jsvgTextBox();
|
|
235
|
+
// Calculate actual height needed for content with minimal padding
|
|
236
|
+
const contentHeight = this.calculateContentHeight(message, index, isMultiple);
|
|
237
|
+
const height = Math.max(contentHeight, this.baseStepHeight); // Use calculated height
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
stepBox.setWidthAndHeight(this.stepWidth, height);
|
|
242
|
+
stepBox.setFontSize(this.fontSize);
|
|
243
|
+
stepBox.setFontColor('#2c3e50');
|
|
244
|
+
|
|
245
|
+
// Store step data for interactions
|
|
246
|
+
stepBox.stepIndex = index;
|
|
247
|
+
stepBox.stepMessage = message;
|
|
248
|
+
stepBox.isMultiple = isMultiple;
|
|
249
|
+
|
|
250
|
+
// Format the step content
|
|
251
|
+
const formattedContent = this.formatStepContent(message, index, isMultiple);
|
|
252
|
+
|
|
253
|
+
// Apply styling and content
|
|
254
|
+
if (stepBox.div) {
|
|
255
|
+
this.applyStepStyling(stepBox, formattedContent, isMultiple, height);
|
|
256
|
+
this.setupStepInteractions(stepBox);
|
|
257
|
+
|
|
258
|
+
// Force the jsvgTextBox to respect our sizing
|
|
259
|
+
stepBox.div.style.height = `${height}px`;
|
|
260
|
+
stepBox.div.style.minHeight = `${height}px`;
|
|
261
|
+
stepBox.div.style.display = 'block';
|
|
262
|
+
|
|
263
|
+
// Add a more aggressive override after a delay to ensure it sticks
|
|
264
|
+
if (stepBox.div) {
|
|
265
|
+
const actualPadding = this.stylingOptions.padding || 6; // Get padding from styling options
|
|
266
|
+
stepBox.div.style.cssText += `
|
|
267
|
+
height: ${height}px !important;
|
|
268
|
+
min-height: ${height}px !important;
|
|
269
|
+
max-height: ${height}px !important;
|
|
270
|
+
padding: ${actualPadding}px ${actualPadding + 2}px !important;
|
|
271
|
+
line-height: 1.3 !important;
|
|
272
|
+
font-size: ${this.fontSize}px !important;
|
|
273
|
+
font-family: Albert Sans, Arial, sans-serif !important;
|
|
274
|
+
box-sizing: border-box !important;
|
|
275
|
+
display: flex !important;
|
|
276
|
+
flex-direction: column !important;
|
|
277
|
+
justify-content: center !important;
|
|
278
|
+
align-items: flex-start !important;
|
|
279
|
+
word-spacing: normal !important;
|
|
280
|
+
letter-spacing: normal !important;
|
|
281
|
+
transition: none !important;
|
|
282
|
+
transform: none !important;
|
|
283
|
+
animation: none !important;
|
|
284
|
+
`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return stepBox;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Formats the content for a step
|
|
294
|
+
* @param {string} message - Raw message
|
|
295
|
+
* @param {number} index - Step index
|
|
296
|
+
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
297
|
+
* @returns {string} Formatted content
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
formatStepContent(message, index, isMultiple) {
|
|
301
|
+
const cleanMessage = message.trim();
|
|
302
|
+
let content = '';
|
|
303
|
+
|
|
304
|
+
// Only show step numbers for multiple steps
|
|
305
|
+
if (isMultiple && this.messages.length > 1) {
|
|
306
|
+
content += `<div class="step-number" style="color: #666; font-size: ${this.smallFontSize}px; margin: 0 0 2px 0; font-weight: 500; line-height: 1.2; font-family: Albert Sans, Arial, sans-serif;">Step ${index + 1}</div>`; // Minimal margin
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
content += '<div class="step-content" style="display: flex; align-items: center; gap: 6px; margin: 0; width: 100%; line-height: 1.3; font-family: Albert Sans, Arial, sans-serif;">'; // Center align and minimal spacing
|
|
310
|
+
content += '<span class="bullet" style="color: #666; font-weight: bold; flex-shrink: 0; font-size: 10px;">•</span>'; // Smaller bullet
|
|
311
|
+
content += '<div class="step-text" style="margin: 0; flex: 1; min-width: 0; word-wrap: break-word; overflow-wrap: break-word; line-height: 1.3; padding: 0;">';
|
|
312
|
+
|
|
313
|
+
// Parse operation details
|
|
314
|
+
if (this.isOperationMessage(cleanMessage)) {
|
|
315
|
+
const action = this.extractOperationAction(cleanMessage);
|
|
316
|
+
const value = this.extractOperationValue(cleanMessage);
|
|
317
|
+
const valueNode = this.extractOperationValueNode(cleanMessage);
|
|
318
|
+
|
|
319
|
+
if (action && (value || valueNode)) {
|
|
320
|
+
content += `<span style="font-weight: 600; color: #2c3e50; margin-right: 4px;">${action}</span> `;
|
|
321
|
+
const displayValue = valueNode ? valueNode.toString() : value;
|
|
322
|
+
content += `<span style="background: #f5f5f5; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; color: #d63384; margin: 0 3px;">${displayValue}</span>`;
|
|
323
|
+
content += `<span style="color: #666; font-size: ${this.smallFontSize}px; margin-left: 4px;"> to both sides</span>`;
|
|
324
|
+
} else {
|
|
325
|
+
content += `<span style="padding: 2px 0;">${cleanMessage}</span>`;
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
content += `<span style="font-weight: 500; padding: 2px 0;">${cleanMessage}</span>`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
content += '</div></div>';
|
|
332
|
+
return content;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Applies styling to a step text box
|
|
337
|
+
* @param {jsvgTextBox} stepBox - The step box
|
|
338
|
+
* @param {string} content - Formatted content
|
|
339
|
+
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
340
|
+
* @param {number} height - The calculated height for the step box
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
applyStepStyling(stepBox, content, isMultiple, height) {
|
|
344
|
+
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.white;
|
|
345
|
+
const borderColor = this.stylingOptions.borderColor || omdColor.lightGray;
|
|
346
|
+
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
347
|
+
const borderRadius = this.stylingOptions.borderRadius || 4;
|
|
348
|
+
const padding = this.stylingOptions.padding || 6; // Minimal padding for tight fit
|
|
349
|
+
|
|
350
|
+
const baseStyles = {
|
|
351
|
+
padding: `${padding}px ${padding + 2}px !important`, // Minimal padding for tight fit
|
|
352
|
+
borderRadius: `${borderRadius}px`,
|
|
353
|
+
border: `${borderWidth}px solid ${borderColor}`,
|
|
354
|
+
backgroundColor: backgroundColor,
|
|
355
|
+
cursor: 'pointer',
|
|
356
|
+
transition: 'none !important', // Explicitly disable all transitions
|
|
357
|
+
transform: 'none !important', // Explicitly disable all transforms
|
|
358
|
+
animation: 'none !important', // Explicitly disable all animations
|
|
359
|
+
lineHeight: '1.3 !important', // Tight line height
|
|
360
|
+
margin: '0',
|
|
361
|
+
boxSizing: 'border-box',
|
|
362
|
+
overflow: 'visible',
|
|
363
|
+
minHeight: `${height}px !important`, // Use calculated height
|
|
364
|
+
height: `${height}px !important`, // Fixed height to content
|
|
365
|
+
width: '100%',
|
|
366
|
+
whiteSpace: 'normal',
|
|
367
|
+
wordWrap: 'break-word',
|
|
368
|
+
overflowWrap: 'break-word',
|
|
369
|
+
maxWidth: '100%',
|
|
370
|
+
fontSize: `${this.fontSize}px !important`, // Force font size
|
|
371
|
+
fontFamily: 'Albert Sans, Arial, sans-serif !important', // Albert Sans font
|
|
372
|
+
display: 'flex !important', // Use flex for centering
|
|
373
|
+
flexDirection: 'column !important',
|
|
374
|
+
justifyContent: 'center !important', // Center content vertically
|
|
375
|
+
alignItems: 'flex-start !important'
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Add drop shadow if requested - but NOT to individual step boxes
|
|
379
|
+
// The drop shadow should only be on the outer background rectangle
|
|
380
|
+
// Remove any previous drop shadow from individual steps
|
|
381
|
+
if (stepBox.div) {
|
|
382
|
+
stepBox.div.style.boxShadow = 'none';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Set font family if specified
|
|
386
|
+
if (this.stylingOptions.fontFamily) {
|
|
387
|
+
baseStyles.fontFamily = this.stylingOptions.fontFamily;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
Object.assign(stepBox.div.style, baseStyles);
|
|
391
|
+
stepBox.div.innerHTML = content;
|
|
392
|
+
|
|
393
|
+
// Additional CSS to force proper text spacing
|
|
394
|
+
if (stepBox.div) {
|
|
395
|
+
stepBox.div.style.cssText += `
|
|
396
|
+
padding: ${padding + 6}px ${padding + 10}px !important;
|
|
397
|
+
line-height: 1.7 !important;
|
|
398
|
+
min-height: ${this.baseStepHeight + 20}px !important;
|
|
399
|
+
font-size: ${this.fontSize}px !important;
|
|
400
|
+
display: flex !important;
|
|
401
|
+
flex-direction: column !important;
|
|
402
|
+
`;
|
|
403
|
+
|
|
404
|
+
// Apply proper layout to nested content - DO NOT use position absolute
|
|
405
|
+
const contentElements = stepBox.div.querySelectorAll('.step-content, .step-text');
|
|
406
|
+
contentElements.forEach(el => {
|
|
407
|
+
el.style.lineHeight = '1.3 !important';
|
|
408
|
+
el.style.margin = '0 !important';
|
|
409
|
+
el.style.fontFamily = 'Albert Sans, Arial, sans-serif !important';
|
|
410
|
+
// Remove any position absolute that might be inherited
|
|
411
|
+
el.style.position = 'static !important';
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Ensure bullet points and text spans stay in normal flow
|
|
415
|
+
const allSpans = stepBox.div.querySelectorAll('span');
|
|
416
|
+
allSpans.forEach(span => {
|
|
417
|
+
span.style.position = 'static !important';
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Force a reflow to ensure proper sizing
|
|
422
|
+
stepBox.div.offsetHeight;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Sets up hover and click interactions for a step
|
|
427
|
+
* @param {jsvgTextBox} stepBox - The step box
|
|
428
|
+
* @private
|
|
429
|
+
*/
|
|
430
|
+
setupStepInteractions(stepBox) {
|
|
431
|
+
// Store the original background color to restore on mouseleave
|
|
432
|
+
const originalBackgroundColor = stepBox.div.style.backgroundColor || '';
|
|
433
|
+
|
|
434
|
+
// Hover effects
|
|
435
|
+
stepBox.div.addEventListener('mouseenter', () => {
|
|
436
|
+
stepBox.div.style.backgroundColor = omdColor.mediumGray; // Slightly darker version of explainColor
|
|
437
|
+
|
|
438
|
+
// Call hover callback if provided
|
|
439
|
+
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, true);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
stepBox.div.addEventListener('mouseleave', () => {
|
|
443
|
+
// Restore the original background color instead of setting to transparent
|
|
444
|
+
stepBox.div.style.backgroundColor = originalBackgroundColor;
|
|
445
|
+
|
|
446
|
+
// Call hover callback if provided
|
|
447
|
+
this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, false);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Click interactions
|
|
451
|
+
stepBox.div.addEventListener('click', () => {
|
|
452
|
+
this.onStepClick?.(stepBox.stepIndex, stepBox.stepMessage);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Calculates the exact height needed for content with minimal padding
|
|
458
|
+
* @param {string} message - Step message
|
|
459
|
+
* @param {number} index - Step index
|
|
460
|
+
* @param {boolean} isMultiple - Whether part of multiple steps
|
|
461
|
+
* @returns {number} Tight-fitting height in pixels
|
|
462
|
+
* @private
|
|
463
|
+
*/
|
|
464
|
+
calculateContentHeight(message, index, isMultiple) {
|
|
465
|
+
// Create a temporary element to measure actual text height
|
|
466
|
+
const tempDiv = document.createElement('div');
|
|
467
|
+
tempDiv.style.position = 'absolute';
|
|
468
|
+
tempDiv.style.visibility = 'hidden';
|
|
469
|
+
tempDiv.style.width = `${this.stepWidth - 16}px`; // Account for minimal padding
|
|
470
|
+
tempDiv.style.fontSize = `${this.fontSize}px`;
|
|
471
|
+
tempDiv.style.lineHeight = '1.3'; // Tight line height
|
|
472
|
+
tempDiv.style.fontFamily = 'Albert Sans, Arial, sans-serif';
|
|
473
|
+
tempDiv.style.whiteSpace = 'normal';
|
|
474
|
+
tempDiv.style.wordWrap = 'break-word';
|
|
475
|
+
tempDiv.style.overflowWrap = 'break-word';
|
|
476
|
+
tempDiv.style.padding = '6px 8px'; // Match the minimal padding
|
|
477
|
+
tempDiv.style.boxSizing = 'border-box';
|
|
478
|
+
tempDiv.style.display = 'flex';
|
|
479
|
+
tempDiv.style.flexDirection = 'column';
|
|
480
|
+
tempDiv.style.justifyContent = 'center';
|
|
481
|
+
|
|
482
|
+
// Use actual formatted content for measurement
|
|
483
|
+
const formattedContent = this.formatStepContent(message, index, isMultiple);
|
|
484
|
+
tempDiv.innerHTML = formattedContent;
|
|
485
|
+
|
|
486
|
+
// Append to document to measure
|
|
487
|
+
document.body.appendChild(tempDiv);
|
|
488
|
+
const measuredHeight = tempDiv.offsetHeight;
|
|
489
|
+
document.body.removeChild(tempDiv);
|
|
490
|
+
|
|
491
|
+
// Return exact measured height with minimal buffer
|
|
492
|
+
return Math.max(this.baseStepHeight, measuredHeight + 2); // Just 2px buffer
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Calculates the height needed for a step box (legacy method, kept for compatibility)
|
|
497
|
+
* @param {string} message - Step message
|
|
498
|
+
* @returns {number} Height in pixels
|
|
499
|
+
* @private
|
|
500
|
+
*/
|
|
501
|
+
calculateStepHeight(message) {
|
|
502
|
+
// Use the new tight-fitting calculation
|
|
503
|
+
return this.calculateContentHeight(message, 0, false);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Updates the background rectangle size after layout
|
|
508
|
+
* @private
|
|
509
|
+
*/
|
|
510
|
+
updateBackgroundSize() {
|
|
511
|
+
if (this.backgroundRect && this.contentGroup) {
|
|
512
|
+
const totalHeight = this.contentGroup.height + 16; // Minimal padding for tight fit
|
|
513
|
+
const totalWidth = this.stepWidth + 16; // Minimal padding for tight fit
|
|
514
|
+
this.backgroundRect.setWidthAndHeight(totalWidth, totalHeight);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Applies drop shadow to the background container if SVG element exists
|
|
520
|
+
* @private
|
|
521
|
+
*/
|
|
522
|
+
applyDropShadowIfNeeded() {
|
|
523
|
+
if (this.stylingOptions.dropShadow && this.backgroundRect && this.backgroundRect.svgObject) {
|
|
524
|
+
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Sets callback for step hover events
|
|
530
|
+
* @param {Function} callback - Function called with (stepIndex, message, isEntering)
|
|
531
|
+
*/
|
|
532
|
+
setOnStepHover(callback) {
|
|
533
|
+
this.onStepHover = callback;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Sets callback for step click events
|
|
538
|
+
* @param {Function} callback - Function called with (stepIndex, message)
|
|
539
|
+
*/
|
|
540
|
+
setOnStepClick(callback) {
|
|
541
|
+
this.onStepClick = callback;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Gets the main layout group for adding to parent containers
|
|
546
|
+
* @returns {jsvgLayoutGroup} The layout group
|
|
547
|
+
*/
|
|
548
|
+
getLayoutGroup() {
|
|
549
|
+
return this.layoutGroup;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Sets the position of the entire step group
|
|
554
|
+
* @param {number} x - X position
|
|
555
|
+
* @param {number} y - Y position
|
|
556
|
+
*/
|
|
557
|
+
setPosition(x, y) {
|
|
558
|
+
this.layoutGroup.setPosition(x, y);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Gets the dimensions of the step group
|
|
563
|
+
* @returns {Object} Width and height
|
|
564
|
+
*/
|
|
565
|
+
getDimensions() {
|
|
566
|
+
return {
|
|
567
|
+
width: this.backgroundRect ? this.backgroundRect.width : this.stepWidth + 16,
|
|
568
|
+
height: this.backgroundRect ? this.backgroundRect.height : 100
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Updates the styling options and re-applies them to existing elements
|
|
574
|
+
* @param {Object} newStylingOptions - New styling options
|
|
575
|
+
*/
|
|
576
|
+
updateStyling(newStylingOptions = {}) {
|
|
577
|
+
this.stylingOptions = { ...this.stylingOptions, ...newStylingOptions };
|
|
578
|
+
|
|
579
|
+
// Update width if maxWidth changed
|
|
580
|
+
if (newStylingOptions.maxWidth) {
|
|
581
|
+
this.stepWidth = newStylingOptions.maxWidth;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Update font size if changed
|
|
585
|
+
if (newStylingOptions.fontSize) {
|
|
586
|
+
this.fontSize = newStylingOptions.fontSize;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Update background container styling
|
|
590
|
+
if (this.backgroundRect) {
|
|
591
|
+
const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
|
|
592
|
+
const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
|
|
593
|
+
const borderWidth = this.stylingOptions.borderWidth || 1;
|
|
594
|
+
const borderRadius = this.stylingOptions.borderRadius || 6;
|
|
595
|
+
|
|
596
|
+
this.backgroundRect.setFillColor(backgroundColor);
|
|
597
|
+
this.backgroundRect.setStrokeColor(borderColor);
|
|
598
|
+
this.backgroundRect.setStrokeWidth(borderWidth);
|
|
599
|
+
this.backgroundRect.setCornerRadius(borderRadius);
|
|
600
|
+
|
|
601
|
+
// Apply or remove drop shadow
|
|
602
|
+
if (this.backgroundRect.svgObject) {
|
|
603
|
+
if (this.stylingOptions.dropShadow) {
|
|
604
|
+
this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
|
|
605
|
+
} else {
|
|
606
|
+
this.backgroundRect.svgObject.style.filter = '';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Re-apply styling to all existing step elements
|
|
612
|
+
this.stepElements.forEach((stepBox, index) => {
|
|
613
|
+
if (stepBox.div) {
|
|
614
|
+
const content = stepBox.div.innerHTML;
|
|
615
|
+
// Calculate new height for the step
|
|
616
|
+
const height = this.calculateContentHeight(stepBox.stepMessage, index, stepBox.isMultiple);
|
|
617
|
+
this.applyStepStyling(stepBox, content, stepBox.isMultiple, height);
|
|
618
|
+
|
|
619
|
+
// Update font size
|
|
620
|
+
stepBox.setFontSize(this.fontSize);
|
|
621
|
+
|
|
622
|
+
// Update dimensions if needed
|
|
623
|
+
stepBox.setWidthAndHeight(this.stepWidth, height);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Update background size
|
|
628
|
+
this.updateBackgroundSize();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Gets the current styling options
|
|
633
|
+
* @returns {Object} Current styling options
|
|
634
|
+
*/
|
|
635
|
+
getStyling() {
|
|
636
|
+
return { ...this.stylingOptions };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Helper methods for message parsing (same as in formatter)
|
|
640
|
+
|
|
641
|
+
isOperationMessage(message) {
|
|
642
|
+
const operationKeywords = ['Applied', 'added', 'subtracted', 'multiplied', 'divided', 'both sides'];
|
|
643
|
+
return operationKeywords.some(keyword =>
|
|
644
|
+
message.toLowerCase().includes(keyword.toLowerCase())
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
extractOperationAction(message) {
|
|
649
|
+
const match = message.match(/^(Added|Subtracted|Multiplied|Divided)/i);
|
|
650
|
+
return match ? match[0] : null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
extractOperationValue(message) {
|
|
654
|
+
// Updated regex to handle simple values and expressions
|
|
655
|
+
const match = message.match(/(?:Added|Subtracted|Multiplied|Divided)\s(.*?)\s(?:to|by)/i);
|
|
656
|
+
if (match && match[1]) {
|
|
657
|
+
// Avoid returning "[object Object]"
|
|
658
|
+
if (match[1].includes('[object Object]')) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
return match[1];
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
extractOperationValueNode(message) {
|
|
667
|
+
if (this.simplificationData && this.simplificationData.operationValueNode) {
|
|
668
|
+
return this.simplificationData.operationValueNode;
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Destroys the step group and cleans up resources
|
|
675
|
+
*/
|
|
676
|
+
destroy() {
|
|
677
|
+
this.stepElements = [];
|
|
678
|
+
if (this.contentGroup) {
|
|
679
|
+
this.contentGroup.removeAllChildren();
|
|
680
|
+
}
|
|
681
|
+
this.layoutGroup.removeAllChildren();
|
|
682
|
+
this.onStepHover = null;
|
|
683
|
+
this.onStepClick = null;
|
|
684
|
+
}
|
|
685
685
|
}
|