@teachinglab/omd 0.6.1 → 0.6.3
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 +606 -547
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +1045 -963
- 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 -57
- 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
package/src/omdProblem.js
CHANGED
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
|
|
2
|
-
import { omdColor } from "./omdColor.js";
|
|
3
|
-
import { jsvgGroup, jsvgTextBox } from "@teachinglab/jsvg";
|
|
4
|
-
|
|
5
|
-
export class omdProblem extends jsvgGroup
|
|
6
|
-
{
|
|
7
|
-
constructor()
|
|
8
|
-
{
|
|
9
|
-
// initialization
|
|
10
|
-
super();
|
|
11
|
-
this.type = "omdProblem";
|
|
12
|
-
this.theText = "";
|
|
13
|
-
|
|
14
|
-
this.problemText = new jsvgTextBox();
|
|
15
|
-
this.problemText.setWidthAndHeight( 250,30 );
|
|
16
|
-
this.problemText.setText ( "this it the problem text" );
|
|
17
|
-
this.problemText.setFontFamily( "Albert Sans" );
|
|
18
|
-
this.problemText.setFontColor( "black" );
|
|
19
|
-
this.problemText.setFontSize( 18 );
|
|
20
|
-
this.addChild( this.problemText );
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
loadFromJSON( data, handleAIResponse = null )
|
|
24
|
-
{
|
|
25
|
-
console.log(data)
|
|
26
|
-
if ( typeof data.problemText != "undefined" )
|
|
27
|
-
this.theText = data.problemText;
|
|
28
|
-
|
|
29
|
-
if ( typeof data.visualization != "undefined" )
|
|
30
|
-
{
|
|
31
|
-
console.log(data)
|
|
32
|
-
this.visualJSON = data.visualiation;
|
|
33
|
-
console.log( this.visualJSON );
|
|
34
|
-
console.log('testing 1 ')
|
|
35
|
-
|
|
36
|
-
// // Fast-path: if the caller supplied an already-rendered SVG DOM element
|
|
37
|
-
// // (for example, captured from the canvas), use it directly instead of
|
|
38
|
-
// // trying to regenerate the graphic. This avoids constructing a temp
|
|
39
|
-
// // omd container and the related initialization issues.
|
|
40
|
-
if (data.svgElement) {
|
|
41
|
-
try {
|
|
42
|
-
// Ensure the problem text is set so measurement is accurate
|
|
43
|
-
this.problemText.setText(this.theText);
|
|
44
|
-
|
|
45
|
-
const padding = 10;
|
|
46
|
-
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
47
|
-
|
|
48
|
-
// utility: hidden measuring svg for getBBox when needed
|
|
49
|
-
function getMeasuringSVG() {
|
|
50
|
-
let m = document.getElementById('_omd_measuring_svg');
|
|
51
|
-
if (!m) {
|
|
52
|
-
m = document.createElementNS(SVG_NS, 'svg');
|
|
53
|
-
m.setAttribute('id', '_omd_measuring_svg');
|
|
54
|
-
m.style.position = 'absolute';
|
|
55
|
-
m.style.left = '-9999px';
|
|
56
|
-
m.style.top = '-9999px';
|
|
57
|
-
m.style.width = '1px';
|
|
58
|
-
m.style.height = '1px';
|
|
59
|
-
m.style.visibility = 'hidden';
|
|
60
|
-
document.body.appendChild(m);
|
|
61
|
-
}
|
|
62
|
-
return m;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const incoming = data.svgElement;
|
|
66
|
-
const cloneRoot = incoming.cloneNode(true);
|
|
67
|
-
|
|
68
|
-
// Attempt 1: find a clip rect inside (common pattern)
|
|
69
|
-
let crop = null; // {x,y,width,height}
|
|
70
|
-
try {
|
|
71
|
-
const rect = cloneRoot.querySelector && cloneRoot.querySelector('clipPath rect');
|
|
72
|
-
if (rect) {
|
|
73
|
-
const x = parseFloat(rect.getAttribute('x') || '0');
|
|
74
|
-
const y = parseFloat(rect.getAttribute('y') || '0');
|
|
75
|
-
const w = parseFloat(rect.getAttribute('width') || '0');
|
|
76
|
-
const h = parseFloat(rect.getAttribute('height') || '0');
|
|
77
|
-
if (w > 0 && h > 0) crop = { x, y, width: w, height: h };
|
|
78
|
-
}
|
|
79
|
-
} catch (e) { /* ignore */ }
|
|
80
|
-
|
|
81
|
-
// Attempt 2: viewBox on root or nested svg
|
|
82
|
-
if (!crop) {
|
|
83
|
-
try {
|
|
84
|
-
let vb = null;
|
|
85
|
-
if (cloneRoot.getAttribute) vb = cloneRoot.getAttribute('viewBox');
|
|
86
|
-
if (!vb) {
|
|
87
|
-
const nested = cloneRoot.querySelector && cloneRoot.querySelector('svg');
|
|
88
|
-
if (nested && nested.getAttribute) vb = nested.getAttribute('viewBox');
|
|
89
|
-
}
|
|
90
|
-
if (vb) {
|
|
91
|
-
const parts = vb.trim().split(/\s+/).map(Number);
|
|
92
|
-
if (parts.length === 4) crop = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
|
|
93
|
-
}
|
|
94
|
-
} catch (e) { /* ignore */ }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Attempt 3: measure bounding box by attaching to hidden SVG
|
|
98
|
-
if (!crop) {
|
|
99
|
-
const meas = getMeasuringSVG();
|
|
100
|
-
const wrapper = document.createElementNS(SVG_NS, 'g');
|
|
101
|
-
wrapper.appendChild(cloneRoot);
|
|
102
|
-
meas.appendChild(wrapper);
|
|
103
|
-
try {
|
|
104
|
-
const bb = wrapper.getBBox();
|
|
105
|
-
console.debug('omdProblem: measured bbox from wrapper', bb);
|
|
106
|
-
if (bb && bb.width > 0 && bb.height > 0) crop = { x: bb.x, y: bb.y, width: bb.width, height: bb.height };
|
|
107
|
-
} catch (e) {
|
|
108
|
-
// ignore
|
|
109
|
-
}
|
|
110
|
-
try { meas.removeChild(wrapper); } catch (_) {}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!crop) crop = { x: 0, y: 0, width: 250, height: 250 };
|
|
115
|
-
|
|
116
|
-
// Allow caller to request a small margin around the crop to avoid clipping
|
|
117
|
-
const cropMargin = (data && typeof data.cropMargin === 'number') ? data.cropMargin : 6;
|
|
118
|
-
// expand crop safely
|
|
119
|
-
const expandedCrop = {
|
|
120
|
-
x: Math.max(0, crop.x - cropMargin),
|
|
121
|
-
y: Math.max(0, crop.y - cropMargin),
|
|
122
|
-
width: crop.width + cropMargin * 2,
|
|
123
|
-
height: crop.height + cropMargin * 2
|
|
124
|
-
};
|
|
125
|
-
crop = expandedCrop;
|
|
126
|
-
console.debug('omdProblem: final crop chosen (expanded)', crop);
|
|
127
|
-
|
|
128
|
-
// Build compact svg sized to crop area
|
|
129
|
-
const compact = document.createElementNS(SVG_NS, 'svg');
|
|
130
|
-
compact.setAttribute('width', String(crop.width));
|
|
131
|
-
compact.setAttribute('height', String(crop.height));
|
|
132
|
-
compact.setAttribute('viewBox', `0 0 ${crop.width} ${crop.height}`);
|
|
133
|
-
|
|
134
|
-
const contentWrapper = document.createElementNS(SVG_NS, 'g');
|
|
135
|
-
// don't apply arbitrary offsets; translate content so crop.x/y maps to 0,0
|
|
136
|
-
contentWrapper.setAttribute('transform', `translate(-55, -80)`);
|
|
137
|
-
contentWrapper.appendChild(cloneRoot);
|
|
138
|
-
compact.appendChild(contentWrapper);
|
|
139
|
-
|
|
140
|
-
// Position compact svg under the problem text
|
|
141
|
-
let textBoxHeight = 0;
|
|
142
|
-
try {
|
|
143
|
-
if (this.problemText.height) textBoxHeight = this.problemText.height;
|
|
144
|
-
else if (this.problemText.svgObject && this.problemText.svgObject.getBBox) textBoxHeight = this.problemText.svgObject.getBBox().height;
|
|
145
|
-
else textBoxHeight = 30;
|
|
146
|
-
} catch (e) { textBoxHeight = 30; }
|
|
147
|
-
|
|
148
|
-
// Center horizontally inside the problem box. Determine available width
|
|
149
|
-
// Caller can provide containerInfo to indicate the rounded-rect container dimensions
|
|
150
|
-
let containerWidth = 300;
|
|
151
|
-
let containerOffsetY = null;
|
|
152
|
-
let containerInnerPadding = 8;
|
|
153
|
-
try {
|
|
154
|
-
if (data && data.containerInfo) {
|
|
155
|
-
if (typeof data.containerInfo.width === 'number') containerWidth = data.containerInfo.width;
|
|
156
|
-
if (typeof data.containerInfo.offsetY === 'number') containerOffsetY = data.containerInfo.offsetY;
|
|
157
|
-
if (typeof data.containerInfo.innerPadding === 'number') containerInnerPadding = data.containerInfo.innerPadding;
|
|
158
|
-
} else if (this.width) containerWidth = this.width;
|
|
159
|
-
else if (this.problemText && this.problemText.width) containerWidth = Math.max(300, this.problemText.width);
|
|
160
|
-
} catch (e) { containerWidth = 300; }
|
|
161
|
-
|
|
162
|
-
const desiredY = Math.round((containerOffsetY !== null) ? (containerOffsetY + padding) : (textBoxHeight + padding));
|
|
163
|
-
|
|
164
|
-
// Ensure the compact SVG never extends outside the container: clamp and scale if needed
|
|
165
|
-
const innerPadding = containerInnerPadding; // keep some breathing room inside the rounded rectangle
|
|
166
|
-
const maxAllowedWidth = Math.max(20, containerWidth - innerPadding * 2);
|
|
167
|
-
let scale = 1;
|
|
168
|
-
if (crop.width > maxAllowedWidth) scale = maxAllowedWidth / crop.width;
|
|
169
|
-
const scaledWidth = Math.round(crop.width * scale);
|
|
170
|
-
const scaledHeight = Math.round(crop.height * scale);
|
|
171
|
-
|
|
172
|
-
// Apply scaled pixel dimensions to compact so it renders at the clamped size
|
|
173
|
-
compact.setAttribute('width', String(scaledWidth));
|
|
174
|
-
compact.setAttribute('height', String(scaledHeight));
|
|
175
|
-
|
|
176
|
-
// center: (containerWidth - scaledWidth) / 2, but don't go negative
|
|
177
|
-
let desiredX = Math.max(innerPadding, Math.round((containerWidth - scaledWidth) / 2));
|
|
178
|
-
|
|
179
|
-
console.debug('omdProblem: containerWidth, desiredX, desiredY', { containerWidth, desiredX, desiredY, cropWidth: crop.width, cropHeight: crop.height, scaledWidth, scaledHeight, scale });
|
|
180
|
-
|
|
181
|
-
const outerWrapper = document.createElementNS(SVG_NS, 'g');
|
|
182
|
-
outerWrapper.appendChild(compact);
|
|
183
|
-
outerWrapper.setAttribute('transform', `translate(${desiredX}, ${desiredY})`);
|
|
184
|
-
this.svgObject.appendChild(outerWrapper);
|
|
185
|
-
|
|
186
|
-
// Optional visual debug overlay: outlines the compact area if requested
|
|
187
|
-
if (data && data.debugPlacement) {
|
|
188
|
-
try {
|
|
189
|
-
const debugRect = document.createElementNS(SVG_NS, 'rect');
|
|
190
|
-
debugRect.setAttribute('x', '0');
|
|
191
|
-
debugRect.setAttribute('y', '0');
|
|
192
|
-
// debug rect shows the scaled layout area
|
|
193
|
-
debugRect.setAttribute('width', String(scaledWidth));
|
|
194
|
-
debugRect.setAttribute('height', String(scaledHeight));
|
|
195
|
-
debugRect.setAttribute('fill', 'none');
|
|
196
|
-
debugRect.setAttribute('stroke', 'rgba(255,0,0,0.9)');
|
|
197
|
-
debugRect.setAttribute('stroke-width', '2');
|
|
198
|
-
debugRect.setAttribute('pointer-events', 'none');
|
|
199
|
-
outerWrapper.appendChild(debugRect);
|
|
200
|
-
} catch (e) { console.warn('omdProblem: failed to add debugPlacement rect', e); }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
this.updateLayout();
|
|
204
|
-
return;
|
|
205
|
-
} catch (e) {
|
|
206
|
-
console.warn('omdProblem: svgElement fast-path failed, falling back to regenerate:', e);
|
|
207
|
-
// fall through to regeneration attempt
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.updateLayout();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
setName( newName )
|
|
216
|
-
{
|
|
217
|
-
this.name = newName;
|
|
218
|
-
this.updateLayout();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
updateLayout()
|
|
222
|
-
{
|
|
223
|
-
// Update text content and size the problem container to fit the text and any child visualization
|
|
224
|
-
this.problemText.setText( this.theText );
|
|
225
|
-
|
|
226
|
-
// Measure the text box (use properties or fall back to getBBox)
|
|
227
|
-
let textBoxWidth = 400;
|
|
228
|
-
let textBoxHeight = 130;
|
|
229
|
-
try {
|
|
230
|
-
if (this.problemText.width) textBoxWidth = this.problemText.width;
|
|
231
|
-
if (this.problemText.height) textBoxHeight = this.problemText.height;
|
|
232
|
-
else if (this.problemText.svgObject && this.problemText.svgObject.getBBox) {
|
|
233
|
-
const bb = this.problemText.svgObject.getBBox();
|
|
234
|
-
if (bb.width) textBoxWidth = bb.width;
|
|
235
|
-
if (bb.height) textBoxHeight = bb.height;
|
|
236
|
-
}
|
|
237
|
-
} catch (e) {
|
|
238
|
-
// keep defaults
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Compute extra height from any visualization child we added
|
|
242
|
-
let extraHeight = 0;
|
|
243
|
-
try {
|
|
244
|
-
// Find a child that isn't the problemText (likely the visualization)
|
|
245
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
246
|
-
const child = this.children[i];
|
|
247
|
-
if (child !== this.problemText && child && child.svgObject && child.svgObject.getBBox) {
|
|
248
|
-
const bb = child.svgObject.getBBox();
|
|
249
|
-
extraHeight = Math.max(extraHeight, bb.height + 20); // include padding
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
} catch (e) {
|
|
253
|
-
extraHeight = 0;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const totalWidth = Math.max(300, textBoxWidth);
|
|
257
|
-
const totalHeight = Math.max(200, textBoxHeight + extraHeight + 20);
|
|
258
|
-
this.setWidthAndHeight( totalWidth, totalHeight );
|
|
259
|
-
}
|
|
1
|
+
|
|
2
|
+
import { omdColor } from "./omdColor.js";
|
|
3
|
+
import { jsvgGroup, jsvgTextBox } from "@teachinglab/jsvg";
|
|
4
|
+
|
|
5
|
+
export class omdProblem extends jsvgGroup
|
|
6
|
+
{
|
|
7
|
+
constructor()
|
|
8
|
+
{
|
|
9
|
+
// initialization
|
|
10
|
+
super();
|
|
11
|
+
this.type = "omdProblem";
|
|
12
|
+
this.theText = "";
|
|
13
|
+
|
|
14
|
+
this.problemText = new jsvgTextBox();
|
|
15
|
+
this.problemText.setWidthAndHeight( 250,30 );
|
|
16
|
+
this.problemText.setText ( "this it the problem text" );
|
|
17
|
+
this.problemText.setFontFamily( "Albert Sans" );
|
|
18
|
+
this.problemText.setFontColor( "black" );
|
|
19
|
+
this.problemText.setFontSize( 18 );
|
|
20
|
+
this.addChild( this.problemText );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
loadFromJSON( data, handleAIResponse = null )
|
|
24
|
+
{
|
|
25
|
+
console.log(data)
|
|
26
|
+
if ( typeof data.problemText != "undefined" )
|
|
27
|
+
this.theText = data.problemText;
|
|
28
|
+
|
|
29
|
+
if ( typeof data.visualization != "undefined" )
|
|
30
|
+
{
|
|
31
|
+
console.log(data)
|
|
32
|
+
this.visualJSON = data.visualiation;
|
|
33
|
+
console.log( this.visualJSON );
|
|
34
|
+
console.log('testing 1 ')
|
|
35
|
+
|
|
36
|
+
// // Fast-path: if the caller supplied an already-rendered SVG DOM element
|
|
37
|
+
// // (for example, captured from the canvas), use it directly instead of
|
|
38
|
+
// // trying to regenerate the graphic. This avoids constructing a temp
|
|
39
|
+
// // omd container and the related initialization issues.
|
|
40
|
+
if (data.svgElement) {
|
|
41
|
+
try {
|
|
42
|
+
// Ensure the problem text is set so measurement is accurate
|
|
43
|
+
this.problemText.setText(this.theText);
|
|
44
|
+
|
|
45
|
+
const padding = 10;
|
|
46
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
47
|
+
|
|
48
|
+
// utility: hidden measuring svg for getBBox when needed
|
|
49
|
+
function getMeasuringSVG() {
|
|
50
|
+
let m = document.getElementById('_omd_measuring_svg');
|
|
51
|
+
if (!m) {
|
|
52
|
+
m = document.createElementNS(SVG_NS, 'svg');
|
|
53
|
+
m.setAttribute('id', '_omd_measuring_svg');
|
|
54
|
+
m.style.position = 'absolute';
|
|
55
|
+
m.style.left = '-9999px';
|
|
56
|
+
m.style.top = '-9999px';
|
|
57
|
+
m.style.width = '1px';
|
|
58
|
+
m.style.height = '1px';
|
|
59
|
+
m.style.visibility = 'hidden';
|
|
60
|
+
document.body.appendChild(m);
|
|
61
|
+
}
|
|
62
|
+
return m;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const incoming = data.svgElement;
|
|
66
|
+
const cloneRoot = incoming.cloneNode(true);
|
|
67
|
+
|
|
68
|
+
// Attempt 1: find a clip rect inside (common pattern)
|
|
69
|
+
let crop = null; // {x,y,width,height}
|
|
70
|
+
try {
|
|
71
|
+
const rect = cloneRoot.querySelector && cloneRoot.querySelector('clipPath rect');
|
|
72
|
+
if (rect) {
|
|
73
|
+
const x = parseFloat(rect.getAttribute('x') || '0');
|
|
74
|
+
const y = parseFloat(rect.getAttribute('y') || '0');
|
|
75
|
+
const w = parseFloat(rect.getAttribute('width') || '0');
|
|
76
|
+
const h = parseFloat(rect.getAttribute('height') || '0');
|
|
77
|
+
if (w > 0 && h > 0) crop = { x, y, width: w, height: h };
|
|
78
|
+
}
|
|
79
|
+
} catch (e) { /* ignore */ }
|
|
80
|
+
|
|
81
|
+
// Attempt 2: viewBox on root or nested svg
|
|
82
|
+
if (!crop) {
|
|
83
|
+
try {
|
|
84
|
+
let vb = null;
|
|
85
|
+
if (cloneRoot.getAttribute) vb = cloneRoot.getAttribute('viewBox');
|
|
86
|
+
if (!vb) {
|
|
87
|
+
const nested = cloneRoot.querySelector && cloneRoot.querySelector('svg');
|
|
88
|
+
if (nested && nested.getAttribute) vb = nested.getAttribute('viewBox');
|
|
89
|
+
}
|
|
90
|
+
if (vb) {
|
|
91
|
+
const parts = vb.trim().split(/\s+/).map(Number);
|
|
92
|
+
if (parts.length === 4) crop = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
|
|
93
|
+
}
|
|
94
|
+
} catch (e) { /* ignore */ }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Attempt 3: measure bounding box by attaching to hidden SVG
|
|
98
|
+
if (!crop) {
|
|
99
|
+
const meas = getMeasuringSVG();
|
|
100
|
+
const wrapper = document.createElementNS(SVG_NS, 'g');
|
|
101
|
+
wrapper.appendChild(cloneRoot);
|
|
102
|
+
meas.appendChild(wrapper);
|
|
103
|
+
try {
|
|
104
|
+
const bb = wrapper.getBBox();
|
|
105
|
+
console.debug('omdProblem: measured bbox from wrapper', bb);
|
|
106
|
+
if (bb && bb.width > 0 && bb.height > 0) crop = { x: bb.x, y: bb.y, width: bb.width, height: bb.height };
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
try { meas.removeChild(wrapper); } catch (_) {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if (!crop) crop = { x: 0, y: 0, width: 250, height: 250 };
|
|
115
|
+
|
|
116
|
+
// Allow caller to request a small margin around the crop to avoid clipping
|
|
117
|
+
const cropMargin = (data && typeof data.cropMargin === 'number') ? data.cropMargin : 6;
|
|
118
|
+
// expand crop safely
|
|
119
|
+
const expandedCrop = {
|
|
120
|
+
x: Math.max(0, crop.x - cropMargin),
|
|
121
|
+
y: Math.max(0, crop.y - cropMargin),
|
|
122
|
+
width: crop.width + cropMargin * 2,
|
|
123
|
+
height: crop.height + cropMargin * 2
|
|
124
|
+
};
|
|
125
|
+
crop = expandedCrop;
|
|
126
|
+
console.debug('omdProblem: final crop chosen (expanded)', crop);
|
|
127
|
+
|
|
128
|
+
// Build compact svg sized to crop area
|
|
129
|
+
const compact = document.createElementNS(SVG_NS, 'svg');
|
|
130
|
+
compact.setAttribute('width', String(crop.width));
|
|
131
|
+
compact.setAttribute('height', String(crop.height));
|
|
132
|
+
compact.setAttribute('viewBox', `0 0 ${crop.width} ${crop.height}`);
|
|
133
|
+
|
|
134
|
+
const contentWrapper = document.createElementNS(SVG_NS, 'g');
|
|
135
|
+
// don't apply arbitrary offsets; translate content so crop.x/y maps to 0,0
|
|
136
|
+
contentWrapper.setAttribute('transform', `translate(-55, -80)`);
|
|
137
|
+
contentWrapper.appendChild(cloneRoot);
|
|
138
|
+
compact.appendChild(contentWrapper);
|
|
139
|
+
|
|
140
|
+
// Position compact svg under the problem text
|
|
141
|
+
let textBoxHeight = 0;
|
|
142
|
+
try {
|
|
143
|
+
if (this.problemText.height) textBoxHeight = this.problemText.height;
|
|
144
|
+
else if (this.problemText.svgObject && this.problemText.svgObject.getBBox) textBoxHeight = this.problemText.svgObject.getBBox().height;
|
|
145
|
+
else textBoxHeight = 30;
|
|
146
|
+
} catch (e) { textBoxHeight = 30; }
|
|
147
|
+
|
|
148
|
+
// Center horizontally inside the problem box. Determine available width
|
|
149
|
+
// Caller can provide containerInfo to indicate the rounded-rect container dimensions
|
|
150
|
+
let containerWidth = 300;
|
|
151
|
+
let containerOffsetY = null;
|
|
152
|
+
let containerInnerPadding = 8;
|
|
153
|
+
try {
|
|
154
|
+
if (data && data.containerInfo) {
|
|
155
|
+
if (typeof data.containerInfo.width === 'number') containerWidth = data.containerInfo.width;
|
|
156
|
+
if (typeof data.containerInfo.offsetY === 'number') containerOffsetY = data.containerInfo.offsetY;
|
|
157
|
+
if (typeof data.containerInfo.innerPadding === 'number') containerInnerPadding = data.containerInfo.innerPadding;
|
|
158
|
+
} else if (this.width) containerWidth = this.width;
|
|
159
|
+
else if (this.problemText && this.problemText.width) containerWidth = Math.max(300, this.problemText.width);
|
|
160
|
+
} catch (e) { containerWidth = 300; }
|
|
161
|
+
|
|
162
|
+
const desiredY = Math.round((containerOffsetY !== null) ? (containerOffsetY + padding) : (textBoxHeight + padding));
|
|
163
|
+
|
|
164
|
+
// Ensure the compact SVG never extends outside the container: clamp and scale if needed
|
|
165
|
+
const innerPadding = containerInnerPadding; // keep some breathing room inside the rounded rectangle
|
|
166
|
+
const maxAllowedWidth = Math.max(20, containerWidth - innerPadding * 2);
|
|
167
|
+
let scale = 1;
|
|
168
|
+
if (crop.width > maxAllowedWidth) scale = maxAllowedWidth / crop.width;
|
|
169
|
+
const scaledWidth = Math.round(crop.width * scale);
|
|
170
|
+
const scaledHeight = Math.round(crop.height * scale);
|
|
171
|
+
|
|
172
|
+
// Apply scaled pixel dimensions to compact so it renders at the clamped size
|
|
173
|
+
compact.setAttribute('width', String(scaledWidth));
|
|
174
|
+
compact.setAttribute('height', String(scaledHeight));
|
|
175
|
+
|
|
176
|
+
// center: (containerWidth - scaledWidth) / 2, but don't go negative
|
|
177
|
+
let desiredX = Math.max(innerPadding, Math.round((containerWidth - scaledWidth) / 2));
|
|
178
|
+
|
|
179
|
+
console.debug('omdProblem: containerWidth, desiredX, desiredY', { containerWidth, desiredX, desiredY, cropWidth: crop.width, cropHeight: crop.height, scaledWidth, scaledHeight, scale });
|
|
180
|
+
|
|
181
|
+
const outerWrapper = document.createElementNS(SVG_NS, 'g');
|
|
182
|
+
outerWrapper.appendChild(compact);
|
|
183
|
+
outerWrapper.setAttribute('transform', `translate(${desiredX}, ${desiredY})`);
|
|
184
|
+
this.svgObject.appendChild(outerWrapper);
|
|
185
|
+
|
|
186
|
+
// Optional visual debug overlay: outlines the compact area if requested
|
|
187
|
+
if (data && data.debugPlacement) {
|
|
188
|
+
try {
|
|
189
|
+
const debugRect = document.createElementNS(SVG_NS, 'rect');
|
|
190
|
+
debugRect.setAttribute('x', '0');
|
|
191
|
+
debugRect.setAttribute('y', '0');
|
|
192
|
+
// debug rect shows the scaled layout area
|
|
193
|
+
debugRect.setAttribute('width', String(scaledWidth));
|
|
194
|
+
debugRect.setAttribute('height', String(scaledHeight));
|
|
195
|
+
debugRect.setAttribute('fill', 'none');
|
|
196
|
+
debugRect.setAttribute('stroke', 'rgba(255,0,0,0.9)');
|
|
197
|
+
debugRect.setAttribute('stroke-width', '2');
|
|
198
|
+
debugRect.setAttribute('pointer-events', 'none');
|
|
199
|
+
outerWrapper.appendChild(debugRect);
|
|
200
|
+
} catch (e) { console.warn('omdProblem: failed to add debugPlacement rect', e); }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.updateLayout();
|
|
204
|
+
return;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.warn('omdProblem: svgElement fast-path failed, falling back to regenerate:', e);
|
|
207
|
+
// fall through to regeneration attempt
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.updateLayout();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setName( newName )
|
|
216
|
+
{
|
|
217
|
+
this.name = newName;
|
|
218
|
+
this.updateLayout();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
updateLayout()
|
|
222
|
+
{
|
|
223
|
+
// Update text content and size the problem container to fit the text and any child visualization
|
|
224
|
+
this.problemText.setText( this.theText );
|
|
225
|
+
|
|
226
|
+
// Measure the text box (use properties or fall back to getBBox)
|
|
227
|
+
let textBoxWidth = 400;
|
|
228
|
+
let textBoxHeight = 130;
|
|
229
|
+
try {
|
|
230
|
+
if (this.problemText.width) textBoxWidth = this.problemText.width;
|
|
231
|
+
if (this.problemText.height) textBoxHeight = this.problemText.height;
|
|
232
|
+
else if (this.problemText.svgObject && this.problemText.svgObject.getBBox) {
|
|
233
|
+
const bb = this.problemText.svgObject.getBBox();
|
|
234
|
+
if (bb.width) textBoxWidth = bb.width;
|
|
235
|
+
if (bb.height) textBoxHeight = bb.height;
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// keep defaults
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Compute extra height from any visualization child we added
|
|
242
|
+
let extraHeight = 0;
|
|
243
|
+
try {
|
|
244
|
+
// Find a child that isn't the problemText (likely the visualization)
|
|
245
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
246
|
+
const child = this.children[i];
|
|
247
|
+
if (child !== this.problemText && child && child.svgObject && child.svgObject.getBBox) {
|
|
248
|
+
const bb = child.svgObject.getBBox();
|
|
249
|
+
extraHeight = Math.max(extraHeight, bb.height + 20); // include padding
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
extraHeight = 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const totalWidth = Math.max(300, textBoxWidth);
|
|
257
|
+
const totalHeight = Math.max(200, textBoxHeight + extraHeight + 20);
|
|
258
|
+
this.setWidthAndHeight( totalWidth, totalHeight );
|
|
259
|
+
}
|
|
260
260
|
}
|