Skip to content

Instantly share code, notes, and snippets.

@xiejiangzhi
Last active May 21, 2022 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xiejiangzhi/ef0f50f29d5dece5e08d37d0dbdccbad to your computer and use it in GitHub Desktop.
Save xiejiangzhi/ef0f50f29d5dece5e08d37d0dbdccbad to your computer and use it in GitHub Desktop.
Support javascript for Desmos
// ==UserScript==
// @name Desmos script
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://*.desmos.com/calculator*
// @icon https://www.google.com/s2/favicons?sz=64&domain=desmos.com
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// ==/UserScript==
/*
Write script in comment
Example:
create latex for string
js: output(`a = ${Date.now()}`)
create table for array
js: output( [ { latex: 'x', values: [1,2,3, 4, 5 ] }, { latex: 'x+2'} ]);
just run script
js: console.log(123);
helper functions:
output(string or array); // string for latex, array for table
exp_val('a + 1', (v) => output(v)); // get exp value
get_remote_data('http://api.ipify.org', (v) => output(v)); // get remote data
run_remote_js('http://xxx.js', () => { }); // run js by a url and also can use helper function to generate data.
import_remote_js('http://xxx.js', () => { }); // import a js library, execute once.
https://www.desmos.com/calculator/fi0tmugjq2
*/
(function() {
'use strict';
var Calc;
var WaitDesmosFunc, StartFunc;
WaitDesmosFunc = function() {
Calc = unsafeWindow.Calc;
if (Calc) {
StartFunc();
} else {
setTimeout(WaitDesmosFunc, 100);
}
}
setTimeout(WaitDesmosFunc, 500);
let Scripts = {};
var TodoJobs = {};
function ProcessJobs() {
try {
for (let id in TodoJobs) {
let script = TodoJobs[id];
if (Scripts[id] != script) {
console.log("Exec script ", id);
Scripts[id] = script;
UpdateExp(id, script);
}
}
TodoJobs = {};
} catch (err) {
console.error(err);
}
setTimeout(ProcessJobs, 500);
}
setTimeout(ProcessJobs, 500);
function OnExpChange(){
for (const c of Calc.getState().expressions.list) {
if (c.type == 'text' && c.text) {
var r = c.text.match(/^js:\s*(.+)$/);
if (r) {
TodoJobs[c.id] = r[1];
}
}
}
}
StartFunc = function(){
console.log("Start custom script");
Calc.observeEvent('change', OnExpChange);
OnExpChange() // init
}
function GetExpValue(latex, cb, script_data) {
let exp = script_data.helper_exps[latex];
if (!exp) {
exp = Calc.HelperExpression({ latex: latex })
script_data.helper_exps[latex] = exp;
}
exp.observe('numericValue', () => { cb(exp.numericValue); });
if (exp.numericValue) { cb(exp.numericValue); }
exp.observe('listValue', () => { cb(exp.listValue); });
if (exp.listValue) { cb(exp.listValue); }
return exp;
}
function BuildScriptFn(code, script_data) {
let script_fn = Function(`
var DesmosScriptExt = arguments[0];
var output = DesmosScriptExt.output;
var exp_val = DesmosScriptExt.exp_val;
var get_remote_data = DesmosScriptExt.get_remote_data;
var run_remote_js = DesmosScriptExt.run_remote_js;
var import_remote_js = DesmosScriptExt.import_remote_js;
console.log('run code', '${script_data.tid}');
${code}
console.log('finish script', '${script_data.tid}');
`);
return function(args) {
script_fn(script_data.export, args)
}
}
function GetRemoteData(url, cb) {
console.log('req', url)
let res = GM_xmlhttpRequest({
method: "GET",
url: url,
synchronous: true,
onload: function(res) {
console.log('http response code', res.status)
if (res.status == 200) {
cb(res.responseText);
} else {
cb();
}
},
});
}
let JSLibCache = {}
function ImportRemoteJS(url, cb, script_data) {
let loaded = JSLibCache[url];
if (loaded) {
if (cb) { cb(); }
} else {
GetRemoteData(url, function(code) {
if (code) {
JSLibCache[url] = true;
Function(`
var define = undefined;
var exports = undefined;
var module = undefined;
var DesmosScriptExt = arguments[0];
var output = DesmosScriptExt.output;
var exp_val = DesmosScriptExt.exp_val;
var get_remote_data = DesmosScriptExt.get_remote_data;
var run_remote_js = DesmosScriptExt.run_remote_js;
var import_remote_js = DesmosScriptExt.import_remote_js;
${code}
`)(script_data.export);
if (cb) { cb(); }
}
})
}
}
function RunRemoteJS(url, userdata, cb, script_data) {
let code = GetRemoteData(url, function(code){
if (code) {
let fn = BuildScriptFn(code, script_data)
fn(userdata)
if (cb) { cb(userdata) }
}
});
}
let ScriptsData = {}
function InitScriptData(tid) {
let script_data = ScriptsData[tid];
if (!script_data) {
script_data = { tid: tid, helper_exps: {} };
ScriptsData[tid] = script_data;
} else {
let helper_exps = script_data.helper_exps;
for (let k in helper_exps) {
helper_exps[k].unobserveAll();
}
}
return script_data;
}
function UpdateExp(id, script) {
var tid = `script_${id}`;
console.log('update exp', tid)
let script_data = InitScriptData(tid);
script_data.export = {
output: (val) => { ProcessResult(val, script_data); },
exp_val: (exp, cb) => { GetExpValue(exp, cb, script_data); },
run_remote_js: (url, userdata, cb) => { RunRemoteJS(url, userdata, cb, script_data); },
import_remote_js: (url, cb) => { ImportRemoteJS(url, cb, script_data); },
get_remote_data: GetRemoteData
}
try {
var script_fn = BuildScriptFn(script, script_data);
script_fn();
} catch (err) {
console.warn(err)
}
}
function ProcessResult(val, script_data) {
if (!val) { return; }
let tid = script_data.tid;
switch (typeof(val)) {
case 'string':
Calc.setExpression({ id: tid, latex: `${val}`});
break;
case 'object':
if (Array.isArray(val)) {
Calc.setExpression({ id: tid, type: 'table', columns: val });
}
break;
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment