@teachinglab/omd 0.2.8 → 0.2.10
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 +1 -1
- package/canvas/core/omdCanvas.js +5 -0
- package/canvas/drawing/stroke.js +1 -1
- package/canvas/index.js +1 -1
- package/canvas/tools/PencilTool.js +6 -1
- package/docs/api-reference.md +1 -1
- package/docs/index.md +39 -0
- package/omd/core/omdEquationStack.js +8 -0
- package/omd/display/omdDisplay.js +80 -8
- package/omd/nodes/omdEquationNode.js +14 -0
- package/omd/step-visualizer/omdStepVisualizer.js +26 -3
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +41 -0
- package/omd/step-visualizer/omdStepVisualizerLayout.js +5 -0
- package/omd/utils/omdNodeOverlay.js +1 -1
- package/omd/utils/omdPopup.js +142 -21
- package/omd/utils/omdTranscriptionService.js +10 -6
- package/package.json +13 -4
- package/readme.html +320 -0
- package/src/omdTable.js +1 -1
- package/docs/user-guide.md +0 -9
- /package/canvas/drawing/{Segment.js → segment.js} +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OMD (
|
|
1
|
+
# OMD (Open Math Display)
|
|
2
2
|
|
|
3
3
|
OMD is a JavaScript library for creating interactive mathematical interfaces in web applications. Build everything from simple equation displays to complex step-by-step solution systems with rich visual feedback and user interaction.
|
|
4
4
|
|
package/canvas/core/omdCanvas.js
CHANGED
|
@@ -217,6 +217,8 @@ export class omdCanvas {
|
|
|
217
217
|
const stroke = this.strokes.get(strokeId);
|
|
218
218
|
if (!stroke) return false;
|
|
219
219
|
|
|
220
|
+
console.log('[Canvas Debug] Removing stroke:', strokeId, 'Remaining strokes:', this.strokes.size - 1);
|
|
221
|
+
|
|
220
222
|
if (stroke.element.parentNode) {
|
|
221
223
|
stroke.element.parentNode.removeChild(stroke.element);
|
|
222
224
|
}
|
|
@@ -232,6 +234,8 @@ export class omdCanvas {
|
|
|
232
234
|
* Clear all strokes
|
|
233
235
|
*/
|
|
234
236
|
clear() {
|
|
237
|
+
console.log('[Canvas Debug] Clearing all strokes. Current count:', this.strokes.size);
|
|
238
|
+
|
|
235
239
|
this.strokes.forEach((stroke, id) => {
|
|
236
240
|
if (stroke.element.parentNode) {
|
|
237
241
|
stroke.element.parentNode.removeChild(stroke.element);
|
|
@@ -241,6 +245,7 @@ export class omdCanvas {
|
|
|
241
245
|
this.strokes.clear();
|
|
242
246
|
this.selectedStrokes.clear();
|
|
243
247
|
|
|
248
|
+
console.log('[Canvas Debug] Canvas cleared. Stroke count now:', this.strokes.size);
|
|
244
249
|
this.emit('cleared');
|
|
245
250
|
}
|
|
246
251
|
|
package/canvas/drawing/stroke.js
CHANGED
package/canvas/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { Cursor } from './ui/cursor.js';
|
|
|
19
19
|
|
|
20
20
|
// Drawing objects
|
|
21
21
|
export { Stroke } from './drawing/stroke.js';
|
|
22
|
-
export { segment } from './drawing/
|
|
22
|
+
export { segment } from './drawing/segment.js';
|
|
23
23
|
|
|
24
24
|
// Utilities
|
|
25
25
|
export { BoundingBox } from './utils/boundingBox.js';
|
|
@@ -33,6 +33,8 @@ export class PencilTool extends Tool {
|
|
|
33
33
|
onPointerDown(event) {
|
|
34
34
|
if (!this.canUse()) return;
|
|
35
35
|
|
|
36
|
+
console.log('[Pencil Debug] Starting new stroke at:', event.x, event.y);
|
|
37
|
+
|
|
36
38
|
this.isDrawing = true;
|
|
37
39
|
this.points = [];
|
|
38
40
|
this.lastPoint = { x: event.x, y: event.y };
|
|
@@ -51,7 +53,8 @@ export class PencilTool extends Tool {
|
|
|
51
53
|
this.addPoint(event.x, event.y, event.pressure);
|
|
52
54
|
|
|
53
55
|
// Add stroke to canvas
|
|
54
|
-
this.canvas.addStroke(this.currentStroke);
|
|
56
|
+
const strokeId = this.canvas.addStroke(this.currentStroke);
|
|
57
|
+
console.log('[Pencil Debug] Added stroke to canvas with ID:', strokeId, 'Total strokes:', this.canvas.strokes.size);
|
|
55
58
|
|
|
56
59
|
this.canvas.emit('strokeStarted', {
|
|
57
60
|
stroke: this.currentStroke,
|
|
@@ -99,6 +102,8 @@ export class PencilTool extends Tool {
|
|
|
99
102
|
// Finish the stroke
|
|
100
103
|
this.currentStroke.finish();
|
|
101
104
|
|
|
105
|
+
console.log('[Pencil Debug] Finished stroke with', this.points.length, 'points. Canvas now has', this.canvas.strokes.size, 'strokes');
|
|
106
|
+
|
|
102
107
|
this.canvas.emit('strokeCompleted', {
|
|
103
108
|
stroke: this.currentStroke,
|
|
104
109
|
tool: this.name,
|
package/docs/api-reference.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMD Library API Reference
|
|
2
2
|
|
|
3
|
-
> This is the complete API reference for the OMD (
|
|
3
|
+
> This is the complete API reference for the OMD (Open Math Display) library. Use the table of contents below to navigate to the detailed documentation for each module and class.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# OMD Documentation
|
|
2
|
+
|
|
3
|
+
Welcome to the Open Math Display (OMD) documentation.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
- [User Guide](user-guide.html) - Learn how to use OMD in your projects
|
|
8
|
+
- [API Reference](api-reference.html) - Complete API documentation
|
|
9
|
+
|
|
10
|
+
## API Documentation
|
|
11
|
+
|
|
12
|
+
### Core Classes
|
|
13
|
+
- [omdNode](api/omdNode.html) - Base node class
|
|
14
|
+
- [omdLeafNode](api/omdLeafNode.html) - Leaf nodes (numbers, variables)
|
|
15
|
+
- [omdGroupNode](api/omdGroupNode.html) - Group/container nodes
|
|
16
|
+
- [omdOperatorNode](api/omdOperatorNode.html) - Mathematical operators
|
|
17
|
+
- [omdFunctionNode](api/omdFunctionNode.html) - Function nodes
|
|
18
|
+
- [omdPowerNode](api/omdPowerNode.html) - Exponent/power nodes
|
|
19
|
+
- [omdRationalNode](api/omdRationalNode.html) - Fraction nodes
|
|
20
|
+
- [omdSqrtNode](api/omdSqrtNode.html) - Square root nodes
|
|
21
|
+
|
|
22
|
+
### Display & Visualization
|
|
23
|
+
- [omdStepVisualizer](api/omdStepVisualizer.html) - Step-by-step visualization
|
|
24
|
+
- [omdSimplification](api/omdSimplification.html) - Expression simplification
|
|
25
|
+
- [omdPopup](api/omdPopup.html) - Popup overlay system
|
|
26
|
+
|
|
27
|
+
### Utilities & Helpers
|
|
28
|
+
- [omdHelpers](api/omdHelpers.html) - Utility functions
|
|
29
|
+
- [eventManager](api/eventManager.html) - Event handling
|
|
30
|
+
- [focusFrameManager](api/focusFrameManager.html) - Focus frame management
|
|
31
|
+
- [configuration-options](api/configuration-options.html) - Configuration options
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
Visit the [examples directory](../examples/index.html) to see OMD in action.
|
|
36
|
+
|
|
37
|
+
## Contributing
|
|
38
|
+
|
|
39
|
+
See the main [README](../readme.html) for information about contributing to OMD.
|
|
@@ -467,10 +467,18 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
467
467
|
// If this is a step visualizer, rebuild its dots/lines
|
|
468
468
|
if (typeof seq.rebuildVisualizer === 'function') {
|
|
469
469
|
try {
|
|
470
|
+
// Clear all step visualizer highlights before rebuilding
|
|
471
|
+
if (seq.highlighting && typeof seq.highlighting.clearAllExplainHighlights === 'function') {
|
|
472
|
+
seq.highlighting.clearAllExplainHighlights();
|
|
473
|
+
}
|
|
470
474
|
seq.rebuildVisualizer();
|
|
471
475
|
} catch (_) {}
|
|
472
476
|
} else if (typeof seq._initializeVisualElements === 'function') {
|
|
473
477
|
try {
|
|
478
|
+
// Clear all step visualizer highlights before rebuilding
|
|
479
|
+
if (seq.highlighting && typeof seq.highlighting.clearAllExplainHighlights === 'function') {
|
|
480
|
+
seq.highlighting.clearAllExplainHighlights();
|
|
481
|
+
}
|
|
474
482
|
seq._initializeVisualElements();
|
|
475
483
|
if (typeof seq.computeDimensions === 'function') seq.computeDimensions();
|
|
476
484
|
if (typeof seq.updateLayout === 'function') seq.updateLayout();
|
|
@@ -28,6 +28,13 @@ export class omdDisplay {
|
|
|
28
28
|
this.svg = new jsvgContainer();
|
|
29
29
|
this.node = null;
|
|
30
30
|
|
|
31
|
+
// Internal guards to prevent recursive resize induced growth
|
|
32
|
+
this._suppressResizeObserver = false; // When true, _handleResize is a no-op
|
|
33
|
+
this._lastViewbox = null; // Cache last applied viewBox string
|
|
34
|
+
this._lastContentExtents = null; // Cache last measured content extents to detect real growth
|
|
35
|
+
this._viewboxLocked = false; // When true, suppress micro growth adjustments
|
|
36
|
+
this._viewboxLockThreshold = 8; // Require at least 8px growth once locked
|
|
37
|
+
|
|
31
38
|
// Set up the SVG
|
|
32
39
|
this._setupSVG();
|
|
33
40
|
}
|
|
@@ -65,8 +72,13 @@ export class omdDisplay {
|
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
_handleResize() {
|
|
75
|
+
if (this._suppressResizeObserver) return; // Prevent re-entrant resize loops
|
|
68
76
|
const width = this.container.offsetWidth;
|
|
69
77
|
const height = this.container.offsetHeight;
|
|
78
|
+
// Skip if size unchanged; avoids loops where internal changes trigger observer without real container delta
|
|
79
|
+
if (this._lastContainerWidth === width && this._lastContainerHeight === height) return;
|
|
80
|
+
this._lastContainerWidth = width;
|
|
81
|
+
this._lastContainerHeight = height;
|
|
70
82
|
this.svg.setViewbox(width, height);
|
|
71
83
|
|
|
72
84
|
if (this.options.centerContent && this.node) {
|
|
@@ -132,8 +144,50 @@ export class omdDisplay {
|
|
|
132
144
|
const desiredW = Math.max(curW, desiredRight - desiredX);
|
|
133
145
|
const desiredH = Math.max(curH, desiredBottom - desiredY);
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
// Guard: If the desired size change is negligible (< 0.5px), skip.
|
|
148
|
+
const widthDelta = Math.abs(desiredW - curW);
|
|
149
|
+
const heightDelta = Math.abs(desiredH - curH);
|
|
150
|
+
|
|
151
|
+
// Safety cap to avoid runaway expansion due to logic errors.
|
|
152
|
+
const MAX_DIM = 10000; // arbitrary large but finite limit
|
|
153
|
+
if (desiredW > MAX_DIM || desiredH > MAX_DIM) {
|
|
154
|
+
console.warn('omdDisplay: viewBox growth capped to prevent runaway expansion', desiredW, desiredH);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (widthDelta < 0.5 && heightDelta < 0.5) return;
|
|
159
|
+
|
|
160
|
+
// Detect repeated growth with identical content extents (suggests feedback loop)
|
|
161
|
+
const curExtSignature = `${minX},${minY},${maxX},${maxY}`;
|
|
162
|
+
if (this._lastContentExtents === curExtSignature && heightDelta > 0 && desiredH > curH) {
|
|
163
|
+
return; // content unchanged, skip
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If locked, only allow substantial growth
|
|
167
|
+
if (this._viewboxLocked) {
|
|
168
|
+
const growW = desiredW - curW;
|
|
169
|
+
const growH = desiredH - curH;
|
|
170
|
+
if (growW < this._viewboxLockThreshold && growH < this._viewboxLockThreshold) {
|
|
171
|
+
return; // ignore micro growth attempts
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const newViewBox = `${desiredX} ${desiredY} ${desiredW} ${desiredH}`;
|
|
176
|
+
if (this._lastViewbox === newViewBox) return;
|
|
177
|
+
|
|
178
|
+
this._suppressResizeObserver = true;
|
|
179
|
+
try {
|
|
180
|
+
this.svg.svgObject.setAttribute('viewBox', newViewBox);
|
|
181
|
+
} finally {
|
|
182
|
+
// Allow ResizeObserver events after microtask; use timeout to defer
|
|
183
|
+
setTimeout(() => { this._suppressResizeObserver = false; }, 0);
|
|
184
|
+
}
|
|
185
|
+
this._lastViewbox = newViewBox;
|
|
186
|
+
this._lastContentExtents = curExtSignature;
|
|
187
|
+
|
|
188
|
+
// Lock if the growth applied was small; prevents future tiny increments
|
|
189
|
+
if (heightDelta < 2 && widthDelta < 2 && !this._viewboxLocked) {
|
|
190
|
+
this._viewboxLocked = true;
|
|
137
191
|
}
|
|
138
192
|
}
|
|
139
193
|
|
|
@@ -397,6 +451,12 @@ export class omdDisplay {
|
|
|
397
451
|
|
|
398
452
|
centerNode() {
|
|
399
453
|
if (!this.node) return;
|
|
454
|
+
if (!this._centerCallCount) this._centerCallCount = 0;
|
|
455
|
+
this._centerCallCount++;
|
|
456
|
+
if (this._centerCallCount > 500) {
|
|
457
|
+
console.warn('omdDisplay: excessive centerNode calls detected; halting further centering to prevent loop');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
400
460
|
const containerWidth = this.container.offsetWidth || 0;
|
|
401
461
|
const containerHeight = this.container.offsetHeight || 0;
|
|
402
462
|
|
|
@@ -423,7 +483,6 @@ export class omdDisplay {
|
|
|
423
483
|
// For step visualizers, use sequenceWidth/Height instead of total dimensions to exclude visualizer elements from autoscale
|
|
424
484
|
contentWidth = seq.sequenceWidth || seq.width;
|
|
425
485
|
contentHeight = seq.sequenceHeight || seq.height;
|
|
426
|
-
console.log('omdDisplay: Using sequence dimensions for autoscale - sequenceWidth:', seq.sequenceWidth, 'sequenceHeight:', seq.sequenceHeight, 'width:', seq.width, 'height:', seq.height, 'contentWidth:', contentWidth, 'contentHeight:', contentHeight);
|
|
427
486
|
}
|
|
428
487
|
if (seq.getCurrentStep) {
|
|
429
488
|
const step = seq.getCurrentStep();
|
|
@@ -433,12 +492,10 @@ export class omdDisplay {
|
|
|
433
492
|
const stepHeight = seq.sequenceHeight || step.height;
|
|
434
493
|
contentWidth = Math.max(contentWidth, stepWidth);
|
|
435
494
|
contentHeight = Math.max(contentHeight, stepHeight);
|
|
436
|
-
console.log('omdDisplay: Considering step dimensions - stepWidth:', stepWidth, 'stepHeight:', stepHeight, 'finalContentWidth:', contentWidth, 'finalContentHeight:', contentHeight);
|
|
437
495
|
}
|
|
438
496
|
}
|
|
439
497
|
}
|
|
440
498
|
}
|
|
441
|
-
console.log('omdDisplay: Final content dimensions for autoscale - width:', contentWidth, 'height:', contentHeight);
|
|
442
499
|
|
|
443
500
|
// Compute scale to keep within bounds
|
|
444
501
|
let scale = 1;
|
|
@@ -458,14 +515,12 @@ export class omdDisplay {
|
|
|
458
515
|
if (this.node) {
|
|
459
516
|
const ctorName = this.node.constructor?.name;
|
|
460
517
|
hasStepVisualizer = (ctorName === 'omdStepVisualizer') || this.node.type === 'omdStepVisualizer' || (typeof omdStepVisualizer !== 'undefined' && this.node instanceof omdStepVisualizer);
|
|
461
|
-
console.log('omdDisplay: Step visualizer detection (node) ctor:', ctorName, 'type:', this.node.type, 'detected:', hasStepVisualizer);
|
|
462
518
|
}
|
|
463
519
|
|
|
464
520
|
if (hasStepVisualizer) {
|
|
465
521
|
// Preserve existing scale if already set on node; otherwise lock to 1.
|
|
466
522
|
const existingScale = (this.node && typeof this.node.scale === 'number') ? this.node.scale : undefined;
|
|
467
523
|
scale = (existingScale && existingScale > 0) ? existingScale : 1;
|
|
468
|
-
console.log('omdDisplay: Step visualizer detected - locking scale at', scale);
|
|
469
524
|
} else {
|
|
470
525
|
const hPad = this.options.edgePadding || 0;
|
|
471
526
|
const vPadTop = this.options.topMargin || 0;
|
|
@@ -483,7 +538,6 @@ export class omdDisplay {
|
|
|
483
538
|
const maxScale = (typeof this.options.maxScale === 'number') ? this.options.maxScale : 1;
|
|
484
539
|
scale = Math.min(sx, sy, maxScale);
|
|
485
540
|
if (!isFinite(scale) || scale <= 0) scale = 1;
|
|
486
|
-
console.log('omdDisplay: Autoscale calculation - availW:', availW, 'availH:', availH, 'contentW:', contentWidth, 'contentH:', contentHeight, 'scale:', scale);
|
|
487
541
|
}
|
|
488
542
|
}
|
|
489
543
|
|
|
@@ -511,6 +565,14 @@ export class omdDisplay {
|
|
|
511
565
|
const willOverflowHoriz = scaledWidthFinal > containerWidth;
|
|
512
566
|
const willOverflowVert = totalNeededH > containerHeight;
|
|
513
567
|
|
|
568
|
+
// Avoid looping if content dimension signature hasn't changed
|
|
569
|
+
const contentSig = `${contentWidth}x${contentHeight}x${scale}`;
|
|
570
|
+
if (this._lastCenterSignature === contentSig && !willOverflowHoriz && !willOverflowVert) {
|
|
571
|
+
// Only update position; skip expensive ensureViewboxFits
|
|
572
|
+
if (this.node.setPosition) this.node.setPosition(x, this.options.topMargin);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
514
576
|
if (willOverflowHoriz || willOverflowVert) {
|
|
515
577
|
// Set scale but do NOT reposition node (preserve its absolute positions).
|
|
516
578
|
if (this.node.setScale) this.node.setScale(scale);
|
|
@@ -546,6 +608,8 @@ export class omdDisplay {
|
|
|
546
608
|
}
|
|
547
609
|
if (this.options.debugExtents) this._drawDebugOverlays();
|
|
548
610
|
}
|
|
611
|
+
|
|
612
|
+
this._lastCenterSignature = contentSig;
|
|
549
613
|
}
|
|
550
614
|
|
|
551
615
|
fitToContent() {
|
|
@@ -880,4 +944,12 @@ export class omdDisplay {
|
|
|
880
944
|
node.positionToolbarOverlay(containerWidth, containerHeight, padding);
|
|
881
945
|
}
|
|
882
946
|
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Public API: returns the currently rendered root node (could be a step visualizer, sequence, or plain node)
|
|
950
|
+
* @returns {object|null}
|
|
951
|
+
*/
|
|
952
|
+
getCurrentNode() {
|
|
953
|
+
return this.node;
|
|
954
|
+
}
|
|
883
955
|
}
|
|
@@ -1279,6 +1279,20 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1279
1279
|
if (node.operation === 'add' || node.operation === 'plus') {
|
|
1280
1280
|
values.push(...leftValues, ...rightValues);
|
|
1281
1281
|
}
|
|
1282
|
+
// For subtraction, add left values and negate right values
|
|
1283
|
+
else if (node.operation === 'subtract' || node.operation === 'minus') {
|
|
1284
|
+
values.push(...leftValues);
|
|
1285
|
+
// For subtraction, we need to represent negative values
|
|
1286
|
+
for (const rightValue of rightValues) {
|
|
1287
|
+
if (typeof rightValue === 'number') {
|
|
1288
|
+
// Negate numeric values
|
|
1289
|
+
values.push(-rightValue);
|
|
1290
|
+
} else {
|
|
1291
|
+
// For variables/expressions, prepend with negative sign
|
|
1292
|
+
values.push(`-${rightValue}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1282
1296
|
// For multiplication, handle special cases
|
|
1283
1297
|
else if (node.operation === 'multiply') {
|
|
1284
1298
|
// Check if one operand is a constant (coefficient)
|
|
@@ -86,7 +86,6 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
86
86
|
this.textBoxManager.clearAllTextBoxes();
|
|
87
87
|
}
|
|
88
88
|
const after = this.textBoxManager?.stepTextBoxes?.length || 0;
|
|
89
|
-
if (before !== after) console.log('omdStepVisualizer: closeActiveDot removed text boxes', before, '->', after);
|
|
90
89
|
} catch (e) { /* no-op */ }
|
|
91
90
|
}
|
|
92
91
|
|
|
@@ -136,7 +135,7 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
136
135
|
// Text boxes
|
|
137
136
|
textBoxOptions: {
|
|
138
137
|
backgroundColor: omdColor.white,
|
|
139
|
-
borderColor:
|
|
138
|
+
borderColor: 'none',
|
|
140
139
|
borderWidth: 1,
|
|
141
140
|
borderRadius: 5,
|
|
142
141
|
padding: 8, // Minimal padding for tight fit
|
|
@@ -347,6 +346,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
347
346
|
* Force rebuild visual container (dots/lines) from scratch
|
|
348
347
|
*/
|
|
349
348
|
rebuildVisualizer() {
|
|
349
|
+
// Clear all step visualizer highlights before rebuilding
|
|
350
|
+
if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
|
|
351
|
+
this.highlighting.clearAllExplainHighlights();
|
|
352
|
+
}
|
|
353
|
+
|
|
350
354
|
if (this.visualContainer) {
|
|
351
355
|
this.removeChild(this.visualContainer);
|
|
352
356
|
}
|
|
@@ -456,6 +460,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
456
460
|
* Override addStep to update visual elements when new steps are added
|
|
457
461
|
*/
|
|
458
462
|
addStep(step, options = {}) {
|
|
463
|
+
// Clear all step visualizer highlights when adding new steps (stack expansion)
|
|
464
|
+
if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
|
|
465
|
+
this.highlighting.clearAllExplainHighlights();
|
|
466
|
+
}
|
|
467
|
+
|
|
459
468
|
// Call parent first to add the step properly
|
|
460
469
|
super.addStep(step, options);
|
|
461
470
|
|
|
@@ -547,6 +556,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
547
556
|
* @returns {boolean} Whether an operation was undone
|
|
548
557
|
*/
|
|
549
558
|
undoLastOperation() {
|
|
559
|
+
// Clear all step visualizer highlights before undoing
|
|
560
|
+
if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
|
|
561
|
+
this.highlighting.clearAllExplainHighlights();
|
|
562
|
+
}
|
|
563
|
+
|
|
550
564
|
// Remove bottom-most equation and its preceding operation display
|
|
551
565
|
const beforeCount = this.steps.length;
|
|
552
566
|
const removed = super.undoLastOperation ? super.undoLastOperation() : false;
|
|
@@ -593,6 +607,14 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
593
607
|
*/
|
|
594
608
|
setDotsClickable(enabled) {
|
|
595
609
|
this.dotsClickable = enabled;
|
|
610
|
+
|
|
611
|
+
// If disabling, clear any active highlights and dots
|
|
612
|
+
if (!enabled) {
|
|
613
|
+
this._clearActiveDot();
|
|
614
|
+
// Use the more thorough clearing to ensure no stale highlights remain
|
|
615
|
+
this.highlighting.clearAllExplainHighlights();
|
|
616
|
+
}
|
|
617
|
+
|
|
596
618
|
this.stepDots.forEach(dot => {
|
|
597
619
|
this.layoutManager.updateDotClickability(dot);
|
|
598
620
|
});
|
|
@@ -675,7 +697,8 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
675
697
|
this.setLineAboveColor(this.activeDotIndex, this.styling.lineColor);
|
|
676
698
|
this.textBoxManager.removeTextBoxForDot(this.activeDotIndex);
|
|
677
699
|
|
|
678
|
-
|
|
700
|
+
// Use thorough clearing to ensure no stale highlights remain
|
|
701
|
+
this.highlighting.clearAllExplainHighlights();
|
|
679
702
|
|
|
680
703
|
// Temporarily disable equation repositioning for simple dot state changes
|
|
681
704
|
const originalRepositioning = this.layoutManager.allowEquationRepositioning;
|
|
@@ -101,6 +101,47 @@ export class omdStepVisualizerHighlighting {
|
|
|
101
101
|
this.stepVisualizer.stepVisualizerHighlights.clear();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Clears ALL explain highlights from the entire sequence, not just tracked ones.
|
|
106
|
+
* This is more thorough than clearHighlights() and should be used when
|
|
107
|
+
* the step visualizer is disabled or when we need to ensure no stale highlights remain.
|
|
108
|
+
*/
|
|
109
|
+
clearAllExplainHighlights() {
|
|
110
|
+
// Clear tracked highlights first
|
|
111
|
+
this.clearHighlights();
|
|
112
|
+
|
|
113
|
+
// Also clear any explain highlights from the entire sequence tree
|
|
114
|
+
const rootNode = this.stepVisualizer.getRootNode ? this.stepVisualizer.getRootNode() : null;
|
|
115
|
+
if (rootNode) {
|
|
116
|
+
this._clearExplainHighlightsFromTree(rootNode);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recursively clears explain highlights from an entire tree
|
|
122
|
+
* @param {omdNode} node - The root node to start clearing from
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
_clearExplainHighlightsFromTree(node) {
|
|
126
|
+
if (node && typeof node.setExplainHighlight === 'function') {
|
|
127
|
+
node.setExplainHighlight(false);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Recursively clear from children
|
|
131
|
+
if (node && node.childList && Array.isArray(node.childList)) {
|
|
132
|
+
node.childList.forEach(child => {
|
|
133
|
+
this._clearExplainHighlightsFromTree(child);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Also check argumentNodeList if it exists
|
|
138
|
+
if (node && node.argumentNodeList) {
|
|
139
|
+
Object.values(node.argumentNodeList).forEach(child => {
|
|
140
|
+
this._clearExplainHighlightsFromTree(child);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
104
145
|
/**
|
|
105
146
|
* Highlights nodes in the previous equation based on provenance from changed nodes.
|
|
106
147
|
* This creates a visual connection between the current changes and their origins.
|
|
@@ -723,6 +723,11 @@ export class omdStepVisualizerLayout {
|
|
|
723
723
|
_handleExpansionDotClick(expansionDot) {
|
|
724
724
|
const sv = this.stepVisualizer;
|
|
725
725
|
|
|
726
|
+
// Clear all step visualizer highlights when expanding/contracting
|
|
727
|
+
if (sv.highlighting && typeof sv.highlighting.clearAllExplainHighlights === 'function') {
|
|
728
|
+
sv.highlighting.clearAllExplainHighlights();
|
|
729
|
+
}
|
|
730
|
+
|
|
726
731
|
if (expansionDot.isCollapseDot) {
|
|
727
732
|
// Handle collapse dot click - hide only the specific group of intermediate steps
|
|
728
733
|
|
package/omd/utils/omdPopup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsvgGroup, jsvgRect, jsvgButton,
|
|
1
|
+
import { jsvgGroup, jsvgRect, jsvgButton, jsvgLayoutGroup, jsvgTextInput } from '@teachinglab/jsvg';
|
|
2
2
|
/**
|
|
3
3
|
* omdPopup - Handles popup creation and management for node overlays
|
|
4
4
|
*/
|
|
@@ -73,20 +73,19 @@ export class omdPopup {
|
|
|
73
73
|
|
|
74
74
|
// Store original opacities for animation
|
|
75
75
|
const originalPopupOpacity = this.popup.opacity;
|
|
76
|
-
const originalCanvasOpacity = this.penCanvas?.container?.style.opacity || '1';
|
|
77
76
|
const originalTextInputOpacity = this.popupTextInput?.div?.style.opacity || '1';
|
|
78
77
|
|
|
79
|
-
// Ensure canvas and text input are visible for animation
|
|
78
|
+
// Ensure canvas and text input are visible for animation but preserve canvas drawing
|
|
80
79
|
if (this.penCanvas && this.penCanvas.container) {
|
|
81
80
|
this.penCanvas.container.style.display = 'block';
|
|
82
|
-
|
|
81
|
+
// Don't fade canvas opacity - keep strokes visible during popup hide
|
|
83
82
|
}
|
|
84
83
|
if (this.popupTextInput && this.popupTextInput.div) {
|
|
85
84
|
this.popupTextInput.div.style.display = 'flex';
|
|
86
85
|
this.popupTextInput.div.style.opacity = originalTextInputOpacity;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
// Animate
|
|
88
|
+
// Animate popup and canvas together
|
|
90
89
|
const duration = this.options.animationDuration || 300;
|
|
91
90
|
const startTime = performance.now();
|
|
92
91
|
|
|
@@ -103,7 +102,7 @@ export class omdPopup {
|
|
|
103
102
|
this.popup.setOpacity(currentOpacity);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
// Animate canvas with
|
|
105
|
+
// Animate canvas container with same opacity curve
|
|
107
106
|
if (this.penCanvas && this.penCanvas.container) {
|
|
108
107
|
this.penCanvas.container.style.opacity = currentOpacity;
|
|
109
108
|
}
|
|
@@ -116,9 +115,10 @@ export class omdPopup {
|
|
|
116
115
|
if (progress < 1) {
|
|
117
116
|
this.popupAnimationId = requestAnimationFrame(animate);
|
|
118
117
|
} else {
|
|
119
|
-
// Animation complete - hide and cleanup
|
|
118
|
+
// Animation complete - hide and cleanup both popup and canvas
|
|
120
119
|
if (this.penCanvas && this.penCanvas.container) {
|
|
121
120
|
this.penCanvas.container.style.display = 'none';
|
|
121
|
+
this.penCanvas.container.style.opacity = '1'; // Reset for next show
|
|
122
122
|
}
|
|
123
123
|
if (this.popupTextInput && this.popupTextInput.div) {
|
|
124
124
|
this.popupTextInput.div.style.display = 'none';
|
|
@@ -131,7 +131,7 @@ export class omdPopup {
|
|
|
131
131
|
|
|
132
132
|
return new Promise((resolve) => {
|
|
133
133
|
setTimeout(resolve, duration);
|
|
134
|
-
|
|
134
|
+
});
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
@@ -475,19 +475,22 @@ export class omdPopup {
|
|
|
475
475
|
// Add click debugging
|
|
476
476
|
canvasContainer.addEventListener('click', (e) => {});
|
|
477
477
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
478
|
+
// Store cleanup function
|
|
479
|
+
this.penCanvasCleanup = () => {
|
|
480
|
+
if (this.penCanvas) {
|
|
481
|
+
this.penCanvas.destroy();
|
|
482
|
+
}
|
|
483
|
+
if (foreignObject && foreignObject.parentNode) {
|
|
484
|
+
foreignObject.parentNode.removeChild(foreignObject);
|
|
485
|
+
} else if (canvasContainer && canvasContainer.parentNode) {
|
|
486
|
+
canvasContainer.parentNode.removeChild(canvasContainer);
|
|
487
|
+
}
|
|
488
|
+
// Remove step visualizer listeners
|
|
489
|
+
this._removeStepVisualizerListeners();
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Set up step visualizer change detection
|
|
493
|
+
this._setupStepVisualizerListeners(); // If we're currently in pen mode, show the canvas
|
|
491
494
|
if (this.currentMode === 'pen' && this.popup) {
|
|
492
495
|
this._addCanvasToParent(foreignObject || canvasContainer);
|
|
493
496
|
}
|
|
@@ -510,6 +513,12 @@ export class omdPopup {
|
|
|
510
513
|
if (!this.penCanvas) {
|
|
511
514
|
this._createPenCanvas();
|
|
512
515
|
} else {
|
|
516
|
+
// Show existing canvas and ensure it's fully opaque
|
|
517
|
+
const element = this.penCanvas.foreignObject || this.penCanvas.container;
|
|
518
|
+
if (element) {
|
|
519
|
+
element.style.display = 'block';
|
|
520
|
+
element.style.opacity = '1'; // Ensure full opacity for stroke visibility
|
|
521
|
+
}
|
|
513
522
|
this._addCanvasToParent();
|
|
514
523
|
}
|
|
515
524
|
}
|
|
@@ -679,12 +688,22 @@ export class omdPopup {
|
|
|
679
688
|
const easedProgress = 1 - Math.pow(1 - progress, 3);
|
|
680
689
|
const currentOpacity = fromOpacity + (deltaOpacity * easedProgress);
|
|
681
690
|
|
|
691
|
+
// Animate popup
|
|
682
692
|
this.popup.setOpacity(currentOpacity);
|
|
683
693
|
|
|
694
|
+
// Animate canvas with same opacity if it exists
|
|
695
|
+
if (this.penCanvas && this.penCanvas.container && this.currentMode === 'pen') {
|
|
696
|
+
this.penCanvas.container.style.opacity = currentOpacity;
|
|
697
|
+
}
|
|
698
|
+
|
|
684
699
|
if (progress < 1) {
|
|
685
700
|
this.popupAnimationId = requestAnimationFrame(animate);
|
|
686
701
|
} else {
|
|
687
702
|
this.popupAnimationId = null;
|
|
703
|
+
// Ensure canvas is fully opaque when animation completes
|
|
704
|
+
if (this.penCanvas && this.penCanvas.container && this.currentMode === 'pen') {
|
|
705
|
+
this.penCanvas.container.style.opacity = '1';
|
|
706
|
+
}
|
|
688
707
|
resolve();
|
|
689
708
|
}
|
|
690
709
|
};
|
|
@@ -820,6 +839,9 @@ export class omdPopup {
|
|
|
820
839
|
this.resizeObserver = null;
|
|
821
840
|
}
|
|
822
841
|
|
|
842
|
+
// Clean up step visualizer listeners
|
|
843
|
+
this._removeStepVisualizerListeners();
|
|
844
|
+
|
|
823
845
|
// Clean up animation
|
|
824
846
|
if (this.popupAnimationId) {
|
|
825
847
|
cancelAnimationFrame(this.popupAnimationId);
|
|
@@ -897,6 +919,87 @@ export class omdPopup {
|
|
|
897
919
|
this.resizeObserver.observe(document.body);
|
|
898
920
|
}
|
|
899
921
|
|
|
922
|
+
/**
|
|
923
|
+
* Setup step visualizer listeners to track expand/collapse
|
|
924
|
+
* @private
|
|
925
|
+
*/
|
|
926
|
+
_setupStepVisualizerListeners() {
|
|
927
|
+
if (!this.penCanvas) return;
|
|
928
|
+
|
|
929
|
+
// Listen for step visualizer events that might change layout
|
|
930
|
+
this._stepVisualizerUpdateHandler = () => {
|
|
931
|
+
console.log('[Step Visualizer Debug] Layout change detected, updating canvas position');
|
|
932
|
+
// Small delay to allow layout to settle
|
|
933
|
+
setTimeout(() => this._updateCanvasPosition(), 50);
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// Listen for various events that might indicate step visualizer changes
|
|
937
|
+
document.addEventListener('click', this._stepVisualizerUpdateHandler);
|
|
938
|
+
window.addEventListener('resize', this._stepVisualizerUpdateHandler);
|
|
939
|
+
|
|
940
|
+
// Listen for custom step visualizer events if they exist
|
|
941
|
+
if (this.parentElement && this.parentElement.element) {
|
|
942
|
+
this.parentElement.element.addEventListener('stepVisualizerChanged', this._stepVisualizerUpdateHandler);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Set up mutation observer to detect DOM changes in step visualizer
|
|
946
|
+
if (this.parentElement && this.parentElement.element) {
|
|
947
|
+
this._mutationObserver = new MutationObserver((mutations) => {
|
|
948
|
+
let shouldUpdate = false;
|
|
949
|
+
for (const mutation of mutations) {
|
|
950
|
+
// Check if any changes might affect layout
|
|
951
|
+
if (mutation.type === 'attributes' &&
|
|
952
|
+
(mutation.attributeName === 'style' ||
|
|
953
|
+
mutation.attributeName === 'class' ||
|
|
954
|
+
mutation.attributeName === 'transform')) {
|
|
955
|
+
console.log('[Step Visualizer Debug] Mutation detected:', mutation.attributeName, mutation.target);
|
|
956
|
+
shouldUpdate = true;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
if (mutation.type === 'childList' &&
|
|
960
|
+
(mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
|
|
961
|
+
console.log('[Step Visualizer Debug] Child list mutation detected');
|
|
962
|
+
shouldUpdate = true;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (shouldUpdate) {
|
|
967
|
+
setTimeout(() => this._updateCanvasPosition(), 10);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// Observe the parent element and its children
|
|
972
|
+
this._mutationObserver.observe(this.parentElement.element, {
|
|
973
|
+
childList: true,
|
|
974
|
+
attributes: true,
|
|
975
|
+
subtree: true,
|
|
976
|
+
attributeFilter: ['style', 'class', 'transform', 'viewBox']
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Remove step visualizer listeners
|
|
983
|
+
* @private
|
|
984
|
+
*/
|
|
985
|
+
_removeStepVisualizerListeners() {
|
|
986
|
+
if (this._stepVisualizerUpdateHandler) {
|
|
987
|
+
document.removeEventListener('click', this._stepVisualizerUpdateHandler);
|
|
988
|
+
window.removeEventListener('resize', this._stepVisualizerUpdateHandler);
|
|
989
|
+
|
|
990
|
+
if (this.parentElement && this.parentElement.element) {
|
|
991
|
+
this.parentElement.element.removeEventListener('stepVisualizerChanged', this._stepVisualizerUpdateHandler);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
this._stepVisualizerUpdateHandler = null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (this._mutationObserver) {
|
|
998
|
+
this._mutationObserver.disconnect();
|
|
999
|
+
this._mutationObserver = null;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
900
1003
|
/**
|
|
901
1004
|
* Update canvas position to match popup
|
|
902
1005
|
* @private
|
|
@@ -907,6 +1010,13 @@ export class omdPopup {
|
|
|
907
1010
|
const container = this.penCanvas.container;
|
|
908
1011
|
const popupRect = this.popup.svgObject ? this.popup.svgObject.getBoundingClientRect() : null;
|
|
909
1012
|
|
|
1013
|
+
console.log('[Canvas Position Debug] Update triggered:', {
|
|
1014
|
+
hasCanvas: !!this.penCanvas,
|
|
1015
|
+
hasContainer: !!container,
|
|
1016
|
+
strokeCount: this.penCanvas?.strokes?.size || 0,
|
|
1017
|
+
popupRect: popupRect ? `${popupRect.width}x${popupRect.height}` : 'null'
|
|
1018
|
+
});
|
|
1019
|
+
|
|
910
1020
|
if (popupRect && popupRect.width > 0 && popupRect.height > 0) {
|
|
911
1021
|
// Calculate button areas based on popup dimensions
|
|
912
1022
|
const leftButtonArea = this.buttonSize + (this.margin * 2);
|
|
@@ -927,10 +1037,21 @@ export class omdPopup {
|
|
|
927
1037
|
const finalWidth = Math.min(Math.max(contentWidth, this.canvasMinWidth), maxWidth);
|
|
928
1038
|
const finalHeight = Math.min(Math.max(contentHeight, this.canvasMinHeight), maxHeight);
|
|
929
1039
|
|
|
1040
|
+
console.log('[Canvas Position Debug] Moving canvas:', {
|
|
1041
|
+
from: `${container.style.left}, ${container.style.top}`,
|
|
1042
|
+
to: `${absoluteX}px, ${absoluteY}px`,
|
|
1043
|
+
size: `${finalWidth}x${finalHeight}`
|
|
1044
|
+
});
|
|
1045
|
+
|
|
930
1046
|
container.style.left = `${absoluteX}px`;
|
|
931
1047
|
container.style.top = `${absoluteY}px`;
|
|
932
1048
|
container.style.width = `${finalWidth}px`;
|
|
933
1049
|
container.style.height = `${finalHeight}px`;
|
|
1050
|
+
|
|
1051
|
+
// Check stroke count after move
|
|
1052
|
+
setTimeout(() => {
|
|
1053
|
+
console.log('[Canvas Position Debug] After move stroke count:', this.penCanvas?.strokes?.size || 0);
|
|
1054
|
+
}, 50);
|
|
934
1055
|
}
|
|
935
1056
|
}
|
|
936
1057
|
|
|
@@ -9,7 +9,8 @@ export class omdTranscriptionService {
|
|
|
9
9
|
constructor(options = {}) {
|
|
10
10
|
this.options = {
|
|
11
11
|
endpoint: options.endpoint || this._getDefaultEndpoint(),
|
|
12
|
-
defaultProvider: options.defaultProvider || '
|
|
12
|
+
defaultProvider: options.defaultProvider || 'openai',
|
|
13
|
+
defaultModel: options.defaultModel || 'gpt-4o-mini',
|
|
13
14
|
...options
|
|
14
15
|
};
|
|
15
16
|
}
|
|
@@ -60,7 +61,9 @@ export class omdTranscriptionService {
|
|
|
60
61
|
},
|
|
61
62
|
body: JSON.stringify({
|
|
62
63
|
imageBase64: base64Image,
|
|
63
|
-
prompt: options.prompt || 'Please transcribe the handwritten mathematical expression in this image. Return only the mathematical expression in pure format (no LaTeX, no dollar signs). For powers use ^, for fractions use /, for square roots use sqrt().'
|
|
64
|
+
prompt: options.prompt || 'Please transcribe the handwritten mathematical expression in this image. Return only the mathematical expression in pure format (no LaTeX, no dollar signs). For powers use ^, for fractions use /, for square roots use sqrt().',
|
|
65
|
+
model: options.model || this.options.defaultModel,
|
|
66
|
+
provider: options.provider || this.options.defaultProvider
|
|
64
67
|
})
|
|
65
68
|
});
|
|
66
69
|
|
|
@@ -72,8 +75,9 @@ export class omdTranscriptionService {
|
|
|
72
75
|
|
|
73
76
|
|
|
74
77
|
return {
|
|
75
|
-
text: result.text,
|
|
76
|
-
provider: result.provider ||
|
|
78
|
+
text: (result.text || '').trim(),
|
|
79
|
+
provider: result.provider || this.options.defaultProvider,
|
|
80
|
+
model: result.model || (options.model || this.options.defaultModel),
|
|
77
81
|
confidence: result.confidence || 1.0
|
|
78
82
|
};
|
|
79
83
|
|
|
@@ -106,7 +110,7 @@ export class omdTranscriptionService {
|
|
|
106
110
|
* @returns {Array} List of available providers
|
|
107
111
|
*/
|
|
108
112
|
getAvailableProviders() {
|
|
109
|
-
return ['
|
|
113
|
+
return ['openai']; // Server handles actual model execution
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
|
@@ -115,6 +119,6 @@ export class omdTranscriptionService {
|
|
|
115
119
|
* @returns {boolean} True if provider is available
|
|
116
120
|
*/
|
|
117
121
|
isProviderAvailable(provider) {
|
|
118
|
-
return provider === '
|
|
122
|
+
return provider === 'openai';
|
|
119
123
|
}
|
|
120
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teachinglab/omd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "omd",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -32,17 +32,26 @@
|
|
|
32
32
|
"author": "jaredschiffman",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@netlify/vite-plugin": "^2.5.4",
|
|
36
|
+
"@teachinglab/jsvg": "0.1.1",
|
|
35
37
|
"dotenv": "^17.2.1",
|
|
36
38
|
"express": "^4.18.2",
|
|
37
39
|
"mathjs": "^14.5.2",
|
|
38
40
|
"openai": "^4.28.0",
|
|
39
|
-
"
|
|
41
|
+
"vite": "^5.4.0"
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
+
"dev": "npm run build:docs && vite",
|
|
45
|
+
"build": "vite build && node copy-static.js && node build-docs.js",
|
|
46
|
+
"build:docs": "node build-docs.js",
|
|
47
|
+
"build:static": "node copy-static.js",
|
|
48
|
+
"dev:netlify": "npm run build:docs && netlify dev"
|
|
44
49
|
},
|
|
45
50
|
"publishConfig": {
|
|
46
51
|
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"glob": "^11.0.3",
|
|
55
|
+
"marked": "^16.2.1"
|
|
47
56
|
}
|
|
48
57
|
}
|
package/readme.html
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>OMD (Open Math Display) - OMD Documentation</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
color: #333;
|
|
12
|
+
max-width: 1200px;
|
|
13
|
+
margin: 0 auto;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
background: #fff;
|
|
16
|
+
}
|
|
17
|
+
.header {
|
|
18
|
+
border-bottom: 1px solid #eee;
|
|
19
|
+
margin-bottom: 2rem;
|
|
20
|
+
padding-bottom: 1rem;
|
|
21
|
+
}
|
|
22
|
+
.header h1 {
|
|
23
|
+
margin: 0;
|
|
24
|
+
color: #2c3e50;
|
|
25
|
+
}
|
|
26
|
+
.nav {
|
|
27
|
+
margin: 1rem 0;
|
|
28
|
+
}
|
|
29
|
+
.nav a {
|
|
30
|
+
color: #3498db;
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
margin-right: 1rem;
|
|
33
|
+
padding: 0.5rem 1rem;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
transition: background-color 0.2s;
|
|
36
|
+
}
|
|
37
|
+
.nav a:hover {
|
|
38
|
+
background-color: #f8f9fa;
|
|
39
|
+
text-decoration: none;
|
|
40
|
+
}
|
|
41
|
+
.content h1, .content h2, .content h3, .content h4, .content h5, .content h6 {
|
|
42
|
+
color: #2c3e50;
|
|
43
|
+
margin-top: 2rem;
|
|
44
|
+
margin-bottom: 1rem;
|
|
45
|
+
}
|
|
46
|
+
.content h1 {
|
|
47
|
+
border-bottom: 2px solid #3498db;
|
|
48
|
+
padding-bottom: 0.5rem;
|
|
49
|
+
}
|
|
50
|
+
.content h2 {
|
|
51
|
+
border-bottom: 1px solid #eee;
|
|
52
|
+
padding-bottom: 0.3rem;
|
|
53
|
+
}
|
|
54
|
+
.content pre {
|
|
55
|
+
background: #f8f9fa;
|
|
56
|
+
border: 1px solid #e9ecef;
|
|
57
|
+
border-radius: 4px;
|
|
58
|
+
padding: 1rem;
|
|
59
|
+
overflow-x: auto;
|
|
60
|
+
}
|
|
61
|
+
.content code {
|
|
62
|
+
background: #f8f9fa;
|
|
63
|
+
padding: 0.2rem 0.4rem;
|
|
64
|
+
border-radius: 3px;
|
|
65
|
+
font-family: 'SFMono-Regular', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
66
|
+
}
|
|
67
|
+
.content pre code {
|
|
68
|
+
background: none;
|
|
69
|
+
padding: 0;
|
|
70
|
+
}
|
|
71
|
+
.content blockquote {
|
|
72
|
+
border-left: 4px solid #3498db;
|
|
73
|
+
margin: 1rem 0;
|
|
74
|
+
padding-left: 1rem;
|
|
75
|
+
color: #666;
|
|
76
|
+
}
|
|
77
|
+
.content table {
|
|
78
|
+
border-collapse: collapse;
|
|
79
|
+
width: 100%;
|
|
80
|
+
margin: 1rem 0;
|
|
81
|
+
}
|
|
82
|
+
.content th, .content td {
|
|
83
|
+
border: 1px solid #ddd;
|
|
84
|
+
padding: 0.75rem;
|
|
85
|
+
text-align: left;
|
|
86
|
+
}
|
|
87
|
+
.content th {
|
|
88
|
+
background: #f8f9fa;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
}
|
|
91
|
+
.content ul {
|
|
92
|
+
margin: 1rem 0;
|
|
93
|
+
}
|
|
94
|
+
.content li {
|
|
95
|
+
margin: 0.25rem 0;
|
|
96
|
+
}
|
|
97
|
+
.content a {
|
|
98
|
+
color: #3498db;
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
|
101
|
+
.content a:hover {
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
}
|
|
104
|
+
.back-to-top {
|
|
105
|
+
position: fixed;
|
|
106
|
+
bottom: 20px;
|
|
107
|
+
right: 20px;
|
|
108
|
+
background: #3498db;
|
|
109
|
+
color: white;
|
|
110
|
+
padding: 10px 15px;
|
|
111
|
+
border-radius: 50px;
|
|
112
|
+
text-decoration: none;
|
|
113
|
+
opacity: 0.8;
|
|
114
|
+
transition: opacity 0.2s;
|
|
115
|
+
}
|
|
116
|
+
.back-to-top:hover {
|
|
117
|
+
opacity: 1;
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
color: white;
|
|
120
|
+
}
|
|
121
|
+
@media (max-width: 768px) {
|
|
122
|
+
body {
|
|
123
|
+
padding: 10px;
|
|
124
|
+
}
|
|
125
|
+
.nav {
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
}
|
|
128
|
+
.nav a {
|
|
129
|
+
display: block;
|
|
130
|
+
margin: 0.25rem 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
134
|
+
</head>
|
|
135
|
+
<body>
|
|
136
|
+
<div class="header">
|
|
137
|
+
<h1>OMD Documentation</h1>
|
|
138
|
+
<div class="nav">
|
|
139
|
+
<a href="./../index.html">Home</a>
|
|
140
|
+
<a href="./index.html">Documentation</a>
|
|
141
|
+
<a href="./api-reference.html">API Reference</a>
|
|
142
|
+
<a href="./../examples/index.html">Examples</a>
|
|
143
|
+
<a href="./../readme.html">README</a>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="content">
|
|
148
|
+
<h1 id="omd-open-math-display">OMD (Open Math Display)</h1>
|
|
149
|
+
<p>OMD is a JavaScript library for creating interactive mathematical interfaces in web applications. Build everything from simple equation displays to complex step-by-step solution systems with rich visual feedback and user interaction.</p>
|
|
150
|
+
<p><img src="https://i.imgur.com/CdtEi33.png" alt="OMD Demo"></p>
|
|
151
|
+
<h2 id="features">Features</h2>
|
|
152
|
+
<h3 id="interactive-math-rendering"><strong>Interactive Math Rendering</strong></h3>
|
|
153
|
+
<ul>
|
|
154
|
+
<li>High-quality SVG-based mathematical notation</li>
|
|
155
|
+
<li>Real-time expression manipulation and visualization</li>
|
|
156
|
+
<li>Automatic layout and alignment for complex equations</li>
|
|
157
|
+
</ul>
|
|
158
|
+
<h3 id="step-by-step-solutions"><strong>Step-by-Step Solutions</strong></h3>
|
|
159
|
+
<ul>
|
|
160
|
+
<li>Visual step tracking with detailed explanations</li>
|
|
161
|
+
<li>Simplification engine with rule-based transformations</li>
|
|
162
|
+
<li>Provenance tracking for highlighting related elements</li>
|
|
163
|
+
</ul>
|
|
164
|
+
<h3 id="rich-ui-components"><strong>Rich UI Components</strong></h3>
|
|
165
|
+
<ul>
|
|
166
|
+
<li>Built-in toolbar for common mathematical operations</li>
|
|
167
|
+
<li>Drag & drop interface for intuitive manipulation</li>
|
|
168
|
+
<li>Customizable canvas for multi-expression layouts</li>
|
|
169
|
+
</ul>
|
|
170
|
+
<h3 id="educational-features"><strong>Educational Features</strong></h3>
|
|
171
|
+
<ul>
|
|
172
|
+
<li>Interactive learning experiences</li>
|
|
173
|
+
<li>Progressive step revelation</li>
|
|
174
|
+
<li>Visual operation feedback and highlighting</li>
|
|
175
|
+
</ul>
|
|
176
|
+
<h2 id="installation">Installation</h2>
|
|
177
|
+
<h3 id="npm">npm</h3>
|
|
178
|
+
<pre><code class="language-bash">npm install @teachinglab/omd
|
|
179
|
+
</code></pre>
|
|
180
|
+
<h2 id="basic-usage">Basic Usage</h2>
|
|
181
|
+
<pre><code class="language-javascript">import { omdDisplay } from '@teachinglab/omd';
|
|
182
|
+
|
|
183
|
+
// Create a math display
|
|
184
|
+
const container = document.getElementById('math-container');
|
|
185
|
+
const display = new omdDisplay(container);
|
|
186
|
+
|
|
187
|
+
// Render an equation
|
|
188
|
+
display.render('2x + 3 = 11');
|
|
189
|
+
</code></pre>
|
|
190
|
+
<h3 id="step-by-step-solutions">Step-by-Step Solutions</h3>
|
|
191
|
+
<pre><code class="language-javascript">import { omdEquationStack, omdEquationNode } from '@teachinglab/omd';
|
|
192
|
+
|
|
193
|
+
// Create solution steps
|
|
194
|
+
const steps = [
|
|
195
|
+
omdEquationNode.fromString('2x + 3 = 11'),
|
|
196
|
+
omdEquationNode.fromString('2x = 8'),
|
|
197
|
+
omdEquationNode.fromString('x = 4')
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
// Create interactive equation stack
|
|
201
|
+
const stack = new omdEquationStack(steps, {
|
|
202
|
+
toolbar: true,
|
|
203
|
+
stepVisualizer: true
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
display.render(stack);
|
|
207
|
+
</code></pre>
|
|
208
|
+
<h2 id="core-concepts">Core Concepts</h2>
|
|
209
|
+
<h3 id="nodes-building-blocks"><strong>Nodes</strong> - Building Blocks</h3>
|
|
210
|
+
<p>Every mathematical element is a node in an expression tree:</p>
|
|
211
|
+
<ul>
|
|
212
|
+
<li><code>omdEquationNode</code> - Complete equations (e.g., <code>2x + 3 = 11</code>)</li>
|
|
213
|
+
<li><code>omdConstantNode</code> - Numbers (e.g., <code>5</code>, <code>3.14</code>)</li>
|
|
214
|
+
<li><code>omdVariableNode</code> - Variables (e.g., <code>x</code>, <code>y</code>)</li>
|
|
215
|
+
<li><code>omdBinaryExpressionNode</code> - Operations (e.g., <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>)</li>
|
|
216
|
+
</ul>
|
|
217
|
+
<h3 id="sequences-solution-steps"><strong>Sequences</strong> - Solution Steps</h3>
|
|
218
|
+
<p>Group related equations for step-by-step solving:</p>
|
|
219
|
+
<pre><code class="language-javascript">const sequence = new omdEquationSequenceNode([
|
|
220
|
+
equation1, equation2, equation3
|
|
221
|
+
]);
|
|
222
|
+
</code></pre>
|
|
223
|
+
<h3 id="display-rendering-engine"><strong>Display</strong> - Rendering Engine</h3>
|
|
224
|
+
<p>Handles layout, centering, and visualization:</p>
|
|
225
|
+
<pre><code class="language-javascript">const display = new omdDisplay(container, {
|
|
226
|
+
fontSize: 36,
|
|
227
|
+
centerContent: true
|
|
228
|
+
});
|
|
229
|
+
</code></pre>
|
|
230
|
+
<h2 id="interactive-examples">Interactive Examples</h2>
|
|
231
|
+
<p>Explore OMD's capabilities with our comprehensive examples:</p>
|
|
232
|
+
<table>
|
|
233
|
+
<thead>
|
|
234
|
+
<tr>
|
|
235
|
+
<th>Category</th>
|
|
236
|
+
<th>Example</th>
|
|
237
|
+
<th>Description</th>
|
|
238
|
+
</tr>
|
|
239
|
+
</thead>
|
|
240
|
+
<tbody><tr>
|
|
241
|
+
<td><strong>Getting Started</strong></td>
|
|
242
|
+
<td><a href="examples/minimal.html">Minimal</a></td>
|
|
243
|
+
<td>Basic equation rendering</td>
|
|
244
|
+
</tr>
|
|
245
|
+
<tr>
|
|
246
|
+
<td></td>
|
|
247
|
+
<td><a href="examples/simple-usage.html">Simple Usage</a></td>
|
|
248
|
+
<td>Interactive features</td>
|
|
249
|
+
</tr>
|
|
250
|
+
<tr>
|
|
251
|
+
<td><strong>Advanced</strong></td>
|
|
252
|
+
<td><a href="examples/expression-playground.html">Expression Playground</a></td>
|
|
253
|
+
<td>Full manipulation interface</td>
|
|
254
|
+
</tr>
|
|
255
|
+
<tr>
|
|
256
|
+
<td></td>
|
|
257
|
+
<td><a href="examples/drag-and-drop-playground.html">Drag & Drop</a></td>
|
|
258
|
+
<td>Intuitive interaction</td>
|
|
259
|
+
</tr>
|
|
260
|
+
<tr>
|
|
261
|
+
<td><strong>Educational</strong></td>
|
|
262
|
+
<td><a href="examples/worked-solution.html">Worked Solutions</a></td>
|
|
263
|
+
<td>Step-by-step solving</td>
|
|
264
|
+
</tr>
|
|
265
|
+
<tr>
|
|
266
|
+
<td></td>
|
|
267
|
+
<td><a href="examples/kids-interactive.html">Kids Interactive</a></td>
|
|
268
|
+
<td>Child-friendly interface</td>
|
|
269
|
+
</tr>
|
|
270
|
+
<tr>
|
|
271
|
+
<td><strong>Components</strong></td>
|
|
272
|
+
<td><a href="examples/equation-stack-test.html">Equation Stack</a></td>
|
|
273
|
+
<td>Stacked equations</td>
|
|
274
|
+
</tr>
|
|
275
|
+
<tr>
|
|
276
|
+
<td></td>
|
|
277
|
+
<td><a href="examples/canvas-multiple-nodes.html">Canvas Demo</a></td>
|
|
278
|
+
<td>Multi-expression layouts</td>
|
|
279
|
+
</tr>
|
|
280
|
+
</tbody></table>
|
|
281
|
+
<p><strong><a href="examples/index.html">Browse All Examples</a></strong></p>
|
|
282
|
+
<h2 id="documentation">Documentation</h2>
|
|
283
|
+
<table>
|
|
284
|
+
<thead>
|
|
285
|
+
<tr>
|
|
286
|
+
<th>Resource</th>
|
|
287
|
+
<th>Description</th>
|
|
288
|
+
</tr>
|
|
289
|
+
</thead>
|
|
290
|
+
<tbody><tr>
|
|
291
|
+
<td><strong><a href="docs/api-reference.html">API Reference</a></strong></td>
|
|
292
|
+
<td>Complete component documentation</td>
|
|
293
|
+
</tr>
|
|
294
|
+
<tr>
|
|
295
|
+
<td><strong><a href="docs/user-guide.html">User Guide</a></strong></td>
|
|
296
|
+
<td>Getting started and tutorials</td>
|
|
297
|
+
</tr>
|
|
298
|
+
</tbody></table>
|
|
299
|
+
<h2 id="architecture">Architecture</h2>
|
|
300
|
+
<pre><code>OMD Library Structure
|
|
301
|
+
├── Display Layer (omdDisplay)
|
|
302
|
+
├── Node System (Expression tree components)
|
|
303
|
+
├── UI Components (Toolbar, Step visualizer)
|
|
304
|
+
├── Core Systems (Simplification, Layout)
|
|
305
|
+
└── Utilities (Configuration, Helpers)
|
|
306
|
+
</code></pre>
|
|
307
|
+
<h2 id="dependencies">Dependencies</h2>
|
|
308
|
+
<ul>
|
|
309
|
+
<li><strong>JSVG</strong> - High-performance SVG rendering</li>
|
|
310
|
+
<li><strong>math.js</strong> - Mathematical expression parsing</li>
|
|
311
|
+
<li><strong>Modern Browser</strong> - ES6 modules, SVG support</li>
|
|
312
|
+
</ul>
|
|
313
|
+
<hr>
|
|
314
|
+
<p><strong>Ready to get started?</strong> Check out our <a href="examples/index.html">examples</a> or dive into the <a href="docs/api-reference.html">documentation</a>!</p>
|
|
315
|
+
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<a href="#" class="back-to-top">↑ Top</a>
|
|
319
|
+
</body>
|
|
320
|
+
</html>
|
package/src/omdTable.js
CHANGED
|
@@ -34,7 +34,7 @@ export class omdTable extends jsvgGroup
|
|
|
34
34
|
this.showBackground = true;
|
|
35
35
|
|
|
36
36
|
// Alternating row color properties
|
|
37
|
-
this.alternatingRowColors =
|
|
37
|
+
this.alternatingRowColors = [omdColor.mediumGray, omdColor.lightGray]; // Should be an array of colors or null
|
|
38
38
|
this.headerBackgroundColor = omdColor.lightGray;
|
|
39
39
|
this.cellBackgroundColor = "white";
|
|
40
40
|
|
package/docs/user-guide.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# OMD User Guide
|
|
2
|
-
|
|
3
|
-
This user guide is under construction. For now, please refer to the following resources:
|
|
4
|
-
|
|
5
|
-
- [API Reference](./api-reference.md)
|
|
6
|
-
- [Documentation](../DOCUMENTATION.md)
|
|
7
|
-
- [Examples](../examples/index.html)
|
|
8
|
-
|
|
9
|
-
If you are looking for something specific, please check the API docs or the main documentation file. More detailed guides and tutorials will be added soon.
|
|
File without changes
|