|
@@ -84,6 +84,15 @@ class SymjaConsole {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // 全屏面板关闭
|
|
|
|
|
+ document.getElementById('closeFullscreen')?.addEventListener('click', () => {
|
|
|
|
|
+ const fs = document.getElementById('fullscreenPanel');
|
|
|
|
|
+ fs.classList.remove('show');
|
|
|
|
|
+ // 停止动画循环
|
|
|
|
|
+ const api = this.createIJSAPI([]);
|
|
|
|
|
+ if (api) api.canvas.stop();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// 插件面板事件
|
|
// 插件面板事件
|
|
|
document.getElementById('closePluginPanel')?.addEventListener('click', () => {
|
|
document.getElementById('closePluginPanel')?.addEventListener('click', () => {
|
|
|
this.hidePluginPanel();
|
|
this.hidePluginPanel();
|
|
@@ -738,13 +747,10 @@ class SymjaConsole {
|
|
|
|
|
|
|
|
// 用于存储插件的控制台输出
|
|
// 用于存储插件的控制台输出
|
|
|
const pluginOutputs = [];
|
|
const pluginOutputs = [];
|
|
|
-
|
|
|
|
|
- // 创建 Symja API 对象
|
|
|
|
|
- const symjaAPI = this.createSymjaAPI(pluginOutputs);
|
|
|
|
|
-
|
|
|
|
|
- // 执行插件代码
|
|
|
|
|
- const func = new Function('inputs', 'Symja', plugin.jsCode + '\nreturn calculate(inputs, Symja);');
|
|
|
|
|
- const result = func(inputValues, symjaAPI);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const ijsAPI = this.createIJSAPI(pluginOutputs);
|
|
|
|
|
+ const func = new Function('inputs', 'IJS', plugin.jsCode + '\nreturn calculate(inputs, IJS);');
|
|
|
|
|
+ const result = func(inputValues, ijsAPI);
|
|
|
|
|
|
|
|
console.log('Plugin result:', result);
|
|
console.log('Plugin result:', result);
|
|
|
|
|
|
|
@@ -847,222 +853,220 @@ class SymjaConsole {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- createSymjaAPI(pluginOutputs) {
|
|
|
|
|
|
|
+ createIJSAPI(pluginOutputs) {
|
|
|
|
|
+ const self = this;
|
|
|
return {
|
|
return {
|
|
|
- // 控制台输出 API
|
|
|
|
|
- print: function(message) {
|
|
|
|
|
- pluginOutputs.push(String(message));
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 基础计算
|
|
|
|
|
- eval: function(expression) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.symjaEval(expression);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: 无法调用 Symja';
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 微积分
|
|
|
|
|
- differentiate: function(expr, variable) {
|
|
|
|
|
- return this.eval(`D[${expr}, ${variable}]`);
|
|
|
|
|
- },
|
|
|
|
|
- integrate: function(expr, variable) {
|
|
|
|
|
- return this.eval(`Integrate[${expr}, ${variable}]`);
|
|
|
|
|
- },
|
|
|
|
|
- limit: function(expr, variable, point, direction) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.limit(expr, variable, point, direction || '');
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- series: function(expr, variable, point, order) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.series(expr, variable, point, order || 5);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 代数
|
|
|
|
|
- solve: function(equation, variable) {
|
|
|
|
|
- return this.eval(`Solve[${equation}, ${variable}]`);
|
|
|
|
|
- },
|
|
|
|
|
- simplify: function(expr) {
|
|
|
|
|
- return this.eval(`Simplify[${expr}]`);
|
|
|
|
|
- },
|
|
|
|
|
- expand: function(expr) {
|
|
|
|
|
- return this.eval(`Expand[${expr}]`);
|
|
|
|
|
- },
|
|
|
|
|
- factor: function(expr) {
|
|
|
|
|
- return this.eval(`Factor[${expr}]`);
|
|
|
|
|
- },
|
|
|
|
|
- apart: function(expr, variable) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.apart(expr, variable);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- together: function(expr) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.together(expr);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- trigSimplify: function(expr) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.trigSimplify(expr);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 求和与求积
|
|
|
|
|
- sum: function(expr, variable, start, end) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.sum(expr, variable, String(start), String(end));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- product: function(expr, variable, start, end) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.product(expr, variable, String(start), String(end));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // ── IJS.Symja 数学引擎 ─────────────────────────
|
|
|
|
|
+ Symja: {
|
|
|
|
|
+ print: function(message) {
|
|
|
|
|
+ pluginOutputs.push(String(message));
|
|
|
|
|
+ },
|
|
|
|
|
+ eval: function(expression) {
|
|
|
|
|
+ try { return Android.symjaEval(expression); }
|
|
|
|
|
+ catch (e) { return '错误: 无法调用 Symja'; }
|
|
|
|
|
+ },
|
|
|
|
|
+ differentiate: function(expr, variable) { return this.eval(`D[${expr}, ${variable}]`); },
|
|
|
|
|
+ integrate: function(expr, variable) { return this.eval(`Integrate[${expr}, ${variable}]`); },
|
|
|
|
|
+ limit: function(expr, variable, point, direction) {
|
|
|
|
|
+ try { return Android.limit(expr, variable, point, direction || ''); } catch (e) { return '错误:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ series: function(expr, variable, point, order) {
|
|
|
|
|
+ try { return Android.series(expr, variable, point, order || 5); } catch (e) { return '错误:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ solve: function(equation, variable) { return this.eval(`Solve[${equation}, ${variable}]`); },
|
|
|
|
|
+ simplify: function(expr) { return this.eval(`Simplify[${expr}]`); },
|
|
|
|
|
+ expand: function(expr) { return this.eval(`Expand[${expr}]`); },
|
|
|
|
|
+ factor: function(expr) { return this.eval(`Factor[${expr}]`); },
|
|
|
|
|
+ apart: function(expr, variable) { try { return Android.apart(expr, variable); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ together: function(expr) { try { return Android.together(expr); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ trigSimplify: function(expr) { try { return Android.trigSimplify(expr); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ sum: function(expr, variable, start, end) {
|
|
|
|
|
+ try { return Android.sum(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ product: function(expr, variable, start, end) {
|
|
|
|
|
+ try { return Android.product(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ matrix: {
|
|
|
|
|
+ det: function(matrix) { try { return Android.matrixOp('Det', matrix); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ inverse: function(matrix) { try { return Android.matrixOp('Inverse', matrix); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ transpose: function(matrix) { try { return Android.matrixOp('Transpose', matrix); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ eigenvalues: function(matrix) { try { return Android.matrixOp('Eigenvalues', matrix); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ rank: function(matrix) { try { return Android.matrixOp('MatrixRank', matrix); } catch (e) { return '错误:' + e.message; } }
|
|
|
|
|
+ },
|
|
|
|
|
+ gcd: function(a, b) { try { return Android.gcd(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ lcm: function(a, b) { try { return Android.lcm(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ factorInteger: function(n) { try { return Android.factorInteger(String(n)); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ isPrime: function(n) { try { return Android.isPrime(String(n)); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ fibonacci: function(n) { try { return Android.fibonacci(n); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ binomial: function(n, k) { try { return Android.binomial(String(n), String(k)); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ numeric: function(expr, precision) {
|
|
|
|
|
+ try { return Android.numericEval(expr, precision || 16); } catch (e) { return '错误:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ plot: function(func, variable, xMin, xMax) {
|
|
|
|
|
+ try { return Android.evaluatePlot('Plot[' + func + ', {' + variable + ', ' + xMin + ', ' + xMax + '}]'); } catch (e) { return 'Error:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ parametricPlot: function(xf, yf, t, tMin, tMax) {
|
|
|
|
|
+ try { return Android.evaluatePlot('ParametricPlot[{' + xf + ', ' + yf + '}, {' + t + ', ' + tMin + ', ' + tMax + '}]'); } catch (e) { return 'Error:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ listPlot: function(points) {
|
|
|
|
|
+ try { return Android.evaluatePlot('ListPlot[' + points + ']'); } catch (e) { return 'Error:' + e.message; }
|
|
|
|
|
+ },
|
|
|
|
|
+ toTeX: function(expr) { try { return Android.toTeX(expr); } catch (e) { return '错误:' + e.message; } },
|
|
|
|
|
+ renderLatex: function(latex) { symjaConsole.addOutput(latex, 'result', { renderLatex: true }); return '已渲染 LaTeX'; }
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 矩阵运算
|
|
|
|
|
- matrix: {
|
|
|
|
|
- det: function(matrix) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.matrixOp('Det', matrix);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // ── IJS.canvas 绘图 ────────────────────────────
|
|
|
|
|
+ canvas: {
|
|
|
|
|
+ _ctx: null,
|
|
|
|
|
+ _canvas: null,
|
|
|
|
|
+ _frameId: null,
|
|
|
|
|
+ create: function(width, height) {
|
|
|
|
|
+ const panel = document.getElementById('fullscreenContent');
|
|
|
|
|
+ if (!panel) return null;
|
|
|
|
|
+ panel.innerHTML = '';
|
|
|
|
|
+ const c = document.createElement('canvas');
|
|
|
|
|
+ c.width = width || 500;
|
|
|
|
|
+ c.height = height || 500;
|
|
|
|
|
+ this._canvas = c;
|
|
|
|
|
+ this._ctx = c.getContext('2d');
|
|
|
|
|
+ panel.appendChild(c);
|
|
|
|
|
+ return this._ctx;
|
|
|
|
|
+ },
|
|
|
|
|
+ getContext: function() { return this._ctx; },
|
|
|
|
|
+ getSize: function() {
|
|
|
|
|
+ return this._canvas ? { width: this._canvas.width, height: this._canvas.height } : { width: 0, height: 0 };
|
|
|
|
|
+ },
|
|
|
|
|
+ clear: function() {
|
|
|
|
|
+ if (this._ctx && this._canvas) {
|
|
|
|
|
+ this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- inverse: function(matrix) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.matrixOp('Inverse', matrix);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+ fill: function(color) {
|
|
|
|
|
+ if (this._ctx && this._canvas) {
|
|
|
|
|
+ this._ctx.fillStyle = color || '#FFFFFF';
|
|
|
|
|
+ this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- transpose: function(matrix) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.matrixOp('Transpose', matrix);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+ size: function(w, h) {
|
|
|
|
|
+ if (this._canvas) {
|
|
|
|
|
+ this._canvas.width = w;
|
|
|
|
|
+ this._canvas.height = h;
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- eigenvalues: function(matrix) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.matrixOp('Eigenvalues', matrix);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+ // 动画循环
|
|
|
|
|
+ loop: function(callback) {
|
|
|
|
|
+ const selfCanvas = this;
|
|
|
|
|
+ function frame() {
|
|
|
|
|
+ selfCanvas._frameId = requestAnimationFrame(frame);
|
|
|
|
|
+ callback();
|
|
|
}
|
|
}
|
|
|
|
|
+ this.stop();
|
|
|
|
|
+ selfCanvas._frameId = requestAnimationFrame(frame);
|
|
|
},
|
|
},
|
|
|
- rank: function(matrix) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.matrixOp('MatrixRank', matrix);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+ stop: function() {
|
|
|
|
|
+ if (this._frameId) {
|
|
|
|
|
+ cancelAnimationFrame(this._frameId);
|
|
|
|
|
+ this._frameId = null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 数论
|
|
|
|
|
- gcd: function(a, b) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.gcd(String(a), String(b));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- lcm: function(a, b) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.lcm(String(a), String(b));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- factorInteger: function(n) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.factorInteger(String(n));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- isPrime: function(n) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.isPrime(String(n));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 组合数学
|
|
|
|
|
- fibonacci: function(n) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.fibonacci(n);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // ── IJS.input 触摸交互 ──────────────────────────
|
|
|
|
|
+ input: {
|
|
|
|
|
+ _callbacks: {},
|
|
|
|
|
+ onTouchStart: function(cb) { this._callbacks.touchstart = cb; },
|
|
|
|
|
+ onTouchMove: function(cb) { this._callbacks.touchmove = cb; },
|
|
|
|
|
+ onTouchEnd: function(cb) { this._callbacks.touchend = cb; this._bind(); },
|
|
|
|
|
+ _bound: false,
|
|
|
|
|
+ _bind: function() {
|
|
|
|
|
+ if (this._bound) return;
|
|
|
|
|
+ this._bound = true;
|
|
|
|
|
+ const panel = document.getElementById('fullscreenContent');
|
|
|
|
|
+ if (!panel) return;
|
|
|
|
|
+ const selfInput = this;
|
|
|
|
|
+ panel.addEventListener('touchstart', function(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ if (selfInput._callbacks.touchstart) selfInput._callbacks.touchstart(e);
|
|
|
|
|
+ }, { passive: false });
|
|
|
|
|
+ panel.addEventListener('touchmove', function(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ if (selfInput._callbacks.touchmove) selfInput._callbacks.touchmove(e);
|
|
|
|
|
+ }, { passive: false });
|
|
|
|
|
+ panel.addEventListener('touchend', function(e) {
|
|
|
|
|
+ if (selfInput._callbacks.touchend) selfInput._callbacks.touchend(e);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- binomial: function(n, k) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.binomial(String(n), String(k));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // ── IJS.io 文件读写 (应用数据目录) ────────────────
|
|
|
|
|
+ io: {
|
|
|
|
|
+ read: function(filename) {
|
|
|
|
|
+ try { return Android.ioRead(filename); } catch (e) { return null; }
|
|
|
|
|
+ },
|
|
|
|
|
+ write: function(filename, content) {
|
|
|
|
|
+ try { Android.ioWrite(filename, content); return true; } catch (e) { return false; }
|
|
|
|
|
+ },
|
|
|
|
|
+ list: function() {
|
|
|
|
|
+ try { return JSON.parse(Android.ioList()); } catch (e) { return []; }
|
|
|
|
|
+ },
|
|
|
|
|
+ delete: function(filename) {
|
|
|
|
|
+ try { Android.ioDelete(filename); return true; } catch (e) { return false; }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 数值计算
|
|
|
|
|
- numeric: function(expr, precision) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.numericEval(expr, precision || 16);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // ── IJS.audio 音效 ──────────────────────────────
|
|
|
|
|
+ audio: {
|
|
|
|
|
+ _ctx: null,
|
|
|
|
|
+ _getCtx: function() {
|
|
|
|
|
+ if (!this._ctx) this._ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
|
|
|
+ return this._ctx;
|
|
|
|
|
+ },
|
|
|
|
|
+ beep: function(freq, duration, type) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const ctx = this._getCtx();
|
|
|
|
|
+ const osc = ctx.createOscillator();
|
|
|
|
|
+ const gain = ctx.createGain();
|
|
|
|
|
+ osc.type = type || 'square';
|
|
|
|
|
+ osc.frequency.value = freq || 440;
|
|
|
|
|
+ gain.gain.value = 0.15;
|
|
|
|
|
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + (duration || 0.2));
|
|
|
|
|
+ osc.connect(gain);
|
|
|
|
|
+ gain.connect(ctx.destination);
|
|
|
|
|
+ osc.start(ctx.currentTime);
|
|
|
|
|
+ osc.stop(ctx.currentTime + (duration || 0.2));
|
|
|
|
|
+ } catch(e) {}
|
|
|
|
|
+ },
|
|
|
|
|
+ playNote: function(note, octave, duration) {
|
|
|
|
|
+ const notes = { C: 262, D: 294, E: 330, F: 349, G: 392, A: 440, B: 494 };
|
|
|
|
|
+ const freq = (notes[note] || 440) * Math.pow(2, (octave || 4) - 4);
|
|
|
|
|
+ this.beep(freq, duration || 0.2, 'square');
|
|
|
|
|
+ },
|
|
|
|
|
+ playMelody: function(melody, tempo) {
|
|
|
|
|
+ let t = 0;
|
|
|
|
|
+ const beat = (tempo || 120) / 60;
|
|
|
|
|
+ const selfAudio = this;
|
|
|
|
|
+ melody.forEach(function(m) {
|
|
|
|
|
+ if (m.note === 'R') { t += m.dur / beat; return; }
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ selfAudio.playNote(m.note, m.octave || 4, m.dur / beat * 0.9);
|
|
|
|
|
+ }, t * 1000);
|
|
|
|
|
+ t += m.dur / beat;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ playFile: function(filename) {
|
|
|
|
|
+ // 暂不支持,留待扩展
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 绘图
|
|
|
|
|
- plot: function(func, variable, xMin, xMax) {
|
|
|
|
|
- try {
|
|
|
|
|
- const r = Android.evaluatePlot('Plot[' + func + ', {' + variable + ', ' + xMin + ', ' + xMax + '}]');
|
|
|
|
|
- return r;
|
|
|
|
|
- } catch (e) { return 'Error: ' + e.message; }
|
|
|
|
|
- },
|
|
|
|
|
- parametricPlot: function(xf, yf, t, tMin, tMax) {
|
|
|
|
|
- try {
|
|
|
|
|
- const r = Android.evaluatePlot('ParametricPlot[{' + xf + ', ' + yf + '}, {' + t + ', ' + tMin + ', ' + tMax + '}]');
|
|
|
|
|
- return r;
|
|
|
|
|
- } catch (e) { return 'Error: ' + e.message; }
|
|
|
|
|
- },
|
|
|
|
|
- listPlot: function(points) {
|
|
|
|
|
- try {
|
|
|
|
|
- const r = Android.evaluatePlot('ListPlot[' + points + ']');
|
|
|
|
|
- return r;
|
|
|
|
|
- } catch (e) { return 'Error: ' + e.message; }
|
|
|
|
|
- },
|
|
|
|
|
|
|
|
|
|
- // LaTeX 转换和渲染
|
|
|
|
|
- toTeX: function(expr) {
|
|
|
|
|
- try {
|
|
|
|
|
- return Android.toTeX(expr);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '错误: ' + e.message;
|
|
|
|
|
|
|
+ // ── IJS.ui 面板控制 ─────────────────────────────
|
|
|
|
|
+ ui: {
|
|
|
|
|
+ close: function() {
|
|
|
|
|
+ document.getElementById('fullscreenPanel').classList.remove('show');
|
|
|
|
|
+ },
|
|
|
|
|
+ setBackground: function(color) {
|
|
|
|
|
+ const panel = document.getElementById('fullscreenContent');
|
|
|
|
|
+ if (panel) panel.style.background = color;
|
|
|
}
|
|
}
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 渲染 LaTeX(供插件使用)
|
|
|
|
|
- renderLatex: (latex) => {
|
|
|
|
|
- symjaConsole.addOutput(latex, 'result', { renderLatex: true });
|
|
|
|
|
- return '已渲染 LaTeX';
|
|
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -1910,7 +1914,14 @@ class SymjaConsole {
|
|
|
openPlugin(plugin) {
|
|
openPlugin(plugin) {
|
|
|
this.currentPlugin = plugin;
|
|
this.currentPlugin = plugin;
|
|
|
this.hidePluginPanel();
|
|
this.hidePluginPanel();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Fullscreen mode — open the fullscreen panel
|
|
|
|
|
+ if (plugin.mode === 'fullscreen') {
|
|
|
|
|
+ this.openFullscreenPlugin(plugin);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Inline mode — open the standard dialog
|
|
|
const dialog = document.getElementById('pluginDialog');
|
|
const dialog = document.getElementById('pluginDialog');
|
|
|
const title = document.getElementById('pluginDialogTitle');
|
|
const title = document.getElementById('pluginDialogTitle');
|
|
|
const desc = document.getElementById('pluginDialogDesc');
|
|
const desc = document.getElementById('pluginDialogDesc');
|
|
@@ -1944,6 +1955,35 @@ class SymjaConsole {
|
|
|
this.currentPlugin = null;
|
|
this.currentPlugin = null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ openFullscreenPlugin(plugin) {
|
|
|
|
|
+ const panel = document.getElementById('fullscreenPanel');
|
|
|
|
|
+ document.getElementById('fsPluginTitle').textContent = plugin.icon + ' ' + plugin.name;
|
|
|
|
|
+ panel.classList.add('show');
|
|
|
|
|
+
|
|
|
|
|
+ // Run the plugin in fullscreen mode
|
|
|
|
|
+ const pluginOutputs = [];
|
|
|
|
|
+ const ijsAPI = this.createIJSAPI(pluginOutputs);
|
|
|
|
|
+
|
|
|
|
|
+ // Create a default inputs object from plugin defaults
|
|
|
|
|
+ const inputValues = {};
|
|
|
|
|
+ if (plugin.inputs) {
|
|
|
|
|
+ plugin.inputs.forEach(function(inp) {
|
|
|
|
|
+ inputValues[inp.name] = inp.defaultValue || '';
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Fullscreen plugins don't return values — they use IJS.canvas/IJS.input
|
|
|
|
|
+ const func = new Function('inputs', 'IJS', plugin.jsCode + '\ncalculate(inputs, IJS);');
|
|
|
|
|
+ try {
|
|
|
|
|
+ func(inputValues, ijsAPI);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('Fullscreen plugin error:', e);
|
|
|
|
|
+ ijsAPI.ui.close();
|
|
|
|
|
+ this.addOutput('插件错误: ' + e.message, 'error');
|
|
|
|
|
+ this.scrollToBottom();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
executeCurrentPlugin() {
|
|
executeCurrentPlugin() {
|
|
|
if (!this.currentPlugin) return;
|
|
if (!this.currentPlugin) return;
|
|
|
|
|
|
|
@@ -1962,12 +2002,12 @@ class SymjaConsole {
|
|
|
// 用于存储插件的控制台输出
|
|
// 用于存储插件的控制台输出
|
|
|
const pluginOutputs = [];
|
|
const pluginOutputs = [];
|
|
|
|
|
|
|
|
- // 创建 Symja API 对象供插件使用
|
|
|
|
|
- const symjaAPI = this.createSymjaAPI(pluginOutputs);
|
|
|
|
|
-
|
|
|
|
|
- // 执行插件代码,传入 inputs 和 Symja API
|
|
|
|
|
- const func = new Function('inputs', 'Symja', this.currentPlugin.jsCode + '\nreturn calculate(inputs, Symja);');
|
|
|
|
|
- const result = func(inputValues, symjaAPI);
|
|
|
|
|
|
|
+ // 创建 IJS API 对象
|
|
|
|
|
+ const ijsAPI = this.createIJSAPI(pluginOutputs);
|
|
|
|
|
+
|
|
|
|
|
+ // 执行插件代码
|
|
|
|
|
+ const func = new Function('inputs', 'IJS', this.currentPlugin.jsCode + '\nreturn calculate(inputs, IJS);');
|
|
|
|
|
+ const result = func(inputValues, ijsAPI);
|
|
|
|
|
|
|
|
// 验证返回值是否为有效的可运算类型
|
|
// 验证返回值是否为有效的可运算类型
|
|
|
if (typeof result !== 'string' || result.trim() === '') {
|
|
if (typeof result !== 'string' || result.trim() === '') {
|