765 lines
61 KiB
JavaScript
765 lines
61 KiB
JavaScript
'use strict';
|
|
|
|
var obsidian = require('obsidian');
|
|
|
|
function _interopNamespace(e) {
|
|
if (e && e.__esModule) return e;
|
|
var n = Object.create(null);
|
|
if (e) {
|
|
Object.keys(e).forEach(function (k) {
|
|
if (k !== 'default') {
|
|
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
Object.defineProperty(n, k, d.get ? d : {
|
|
enumerable: true,
|
|
get: function () {
|
|
return e[k];
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
n['default'] = e;
|
|
return Object.freeze(n);
|
|
}
|
|
|
|
/*! *****************************************************************************
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|
***************************************************************************** */
|
|
|
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
}
|
|
|
|
/** Calculate a unique SHA-256 hash for the given object */
|
|
function calculateHash(val) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
const data = new TextEncoder().encode(JSON.stringify(val));
|
|
/* istanbul ignore if */
|
|
if (typeof crypto !== "undefined") {
|
|
const buffer = yield crypto.subtle.digest("SHA-256", data);
|
|
const raw = Array.from(new Uint8Array(buffer));
|
|
// Convery binary hash to hex
|
|
const hash = raw.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
return hash;
|
|
}
|
|
else {
|
|
// Use node `crypto` module as fallback when browser subtle crypto does not exist,
|
|
// this primarily exists to allow tests to generate hashes, and will not function if used in the browser context
|
|
const { createHash } = yield Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('crypto')); });
|
|
return createHash("sha256").update(data).digest("hex");
|
|
}
|
|
});
|
|
}
|
|
/** Unsafe cast method.
|
|
* Will transform the given type `F` into `T`,
|
|
* use only when you know this will be valid. */
|
|
function ucast(o) {
|
|
return o;
|
|
}
|
|
|
|
var DegreeMode;
|
|
(function (DegreeMode) {
|
|
DegreeMode["Radians"] = "RADIANS";
|
|
DegreeMode["Degrees"] = "DEGREES";
|
|
})(DegreeMode || (DegreeMode = {}));
|
|
var LineStyle;
|
|
(function (LineStyle) {
|
|
LineStyle["Solid"] = "SOLID";
|
|
LineStyle["Dashed"] = "DASHED";
|
|
LineStyle["Dotted"] = "DOTTED";
|
|
})(LineStyle || (LineStyle = {}));
|
|
var PointStyle;
|
|
(function (PointStyle) {
|
|
PointStyle["Point"] = "POINT";
|
|
PointStyle["Open"] = "OPEN";
|
|
PointStyle["Cross"] = "CROSS";
|
|
})(PointStyle || (PointStyle = {}));
|
|
var ColorConstant;
|
|
(function (ColorConstant) {
|
|
ColorConstant["Red"] = "#ff0000";
|
|
ColorConstant["Green"] = "#00ff00";
|
|
ColorConstant["Blue"] = "#0000ff";
|
|
ColorConstant["Yellow"] = "#ffff00";
|
|
ColorConstant["Magenta"] = "#ff00ff";
|
|
ColorConstant["Cyan"] = "#00ffff";
|
|
ColorConstant["Purple"] = "#6042a6";
|
|
ColorConstant["Orange"] = "#ffa500";
|
|
ColorConstant["Black"] = "#000000";
|
|
ColorConstant["White"] = "#ffffff";
|
|
})(ColorConstant || (ColorConstant = {}));
|
|
|
|
/** The maximum dimensions of a graph */
|
|
const MAX_SIZE = 99999;
|
|
const DEFAULT_GRAPH_SETTINGS = {
|
|
width: 600,
|
|
height: 400,
|
|
left: -10,
|
|
right: 10,
|
|
bottom: -7,
|
|
top: 7,
|
|
grid: true,
|
|
degreeMode: DegreeMode.Radians,
|
|
hideAxisNumbers: false,
|
|
};
|
|
const DEFAULT_GRAPH_WIDTH = Math.abs(DEFAULT_GRAPH_SETTINGS.left) + Math.abs(DEFAULT_GRAPH_SETTINGS.right);
|
|
const DEFAULT_GRAPH_HEIGHT = Math.abs(DEFAULT_GRAPH_SETTINGS.bottom) + Math.abs(DEFAULT_GRAPH_SETTINGS.top);
|
|
function parseStringToEnum(obj, key) {
|
|
const objKey = Object.keys(obj).find((k) => k.toUpperCase() === key.toUpperCase());
|
|
return objKey ? obj[objKey] : null;
|
|
}
|
|
function parseColor(value) {
|
|
// If the value is a valid hex colour
|
|
if (value.startsWith("#")) {
|
|
// Ensure the rest of the value is a valid alphanumeric string
|
|
if (/^[0-9a-zA-Z]+$/.test(value.slice(1))) {
|
|
return value;
|
|
}
|
|
}
|
|
// If the value is a valid colour constant
|
|
return parseStringToEnum(ColorConstant, value);
|
|
}
|
|
class Graph {
|
|
constructor(equations, settings, potentialErrorHint) {
|
|
this.equations = equations;
|
|
this.potentialErrorHint = potentialErrorHint;
|
|
// Adjust bounds (if needed)
|
|
Graph.adjustBounds(settings);
|
|
// Generate hash on the raw equation and setting data,
|
|
// this means that if we extend the settings with new fields pre-existing graphs will have the same hash
|
|
this._hash = calculateHash({ equations, settings });
|
|
// Apply defaults
|
|
this.settings = Object.assign(Object.assign({}, DEFAULT_GRAPH_SETTINGS), settings);
|
|
// Validate settings
|
|
Graph.validateSettings(this.settings);
|
|
// Apply color override
|
|
if (this.settings.defaultColor) {
|
|
this.equations = this.equations.map((equation) => {
|
|
var _a;
|
|
return (Object.assign({ color: (_a = equation.color) !== null && _a !== void 0 ? _a : this.settings.defaultColor }, equation));
|
|
});
|
|
}
|
|
}
|
|
static parse(source) {
|
|
let potentialErrorHint;
|
|
const split = source.split("---");
|
|
if (split.length > 2) {
|
|
throw new SyntaxError("Too many graph segments, there can only be a singular '---'");
|
|
}
|
|
// Each (non-blank) line of the equation source contains an equation,
|
|
// this will always be the last segment
|
|
const equations = split[split.length - 1]
|
|
.split(/\r?\n/g)
|
|
.filter((equation) => equation.trim() !== "")
|
|
.map(Graph.parseEquation)
|
|
.map((result) => {
|
|
if (result.hint) {
|
|
potentialErrorHint = result.hint;
|
|
}
|
|
return result.data;
|
|
});
|
|
// If there is more than one segment then the first one will contain the settings
|
|
const settings = split.length > 1 ? Graph.parseSettings(split[0]) : {};
|
|
return new Graph(equations, settings, potentialErrorHint);
|
|
}
|
|
hash() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return this._hash;
|
|
});
|
|
}
|
|
static validateSettings(settings) {
|
|
// Check graph is within maximum size
|
|
if ((settings.width && settings.width > MAX_SIZE) || (settings.height && settings.height > MAX_SIZE)) {
|
|
throw new SyntaxError(`Graph size outside of accepted bounds (must be <${MAX_SIZE}x${MAX_SIZE})`);
|
|
}
|
|
// Ensure boundaries are correct
|
|
if (settings.left >= settings.right) {
|
|
throw new SyntaxError(`Right boundary (${settings.right}) must be greater than left boundary (${settings.left})`);
|
|
}
|
|
if (settings.bottom >= settings.top) {
|
|
throw new SyntaxError(`
|
|
Top boundary (${settings.top}) must be greater than bottom boundary (${settings.bottom})
|
|
`);
|
|
}
|
|
}
|
|
static parseEquation(eq) {
|
|
var _a;
|
|
let hint;
|
|
const segments = eq
|
|
.split("|")
|
|
.map((segment) => segment.trim())
|
|
.filter((segment) => segment !== "");
|
|
// First segment is always the equation
|
|
const equation = { equation: ucast(segments.shift()) };
|
|
// The rest of the segments can either be the restriction, style, or color
|
|
// whilst we recommend putting the restriction first, we accept these in any order.
|
|
for (const segment of segments) {
|
|
const segmentUpperCase = segment.toUpperCase();
|
|
// If this is a `hidden` tag
|
|
if (segmentUpperCase === "HIDDEN") {
|
|
equation.hidden = true;
|
|
continue;
|
|
}
|
|
// If this is a valid style constant
|
|
const style = (_a = parseStringToEnum(LineStyle, segmentUpperCase)) !== null && _a !== void 0 ? _a : parseStringToEnum(PointStyle, segmentUpperCase);
|
|
if (style) {
|
|
if (!equation.style) {
|
|
equation.style = style;
|
|
}
|
|
else {
|
|
throw new SyntaxError(`Duplicate style identifiers detected: ${equation.style}, ${segment}`);
|
|
}
|
|
continue;
|
|
}
|
|
// If this is a valid color constant or hex code
|
|
const color = parseColor(segment);
|
|
if (color) {
|
|
if (!equation.color) {
|
|
equation.color = color;
|
|
}
|
|
else {
|
|
throw new SyntaxError(`Duplicate color identifiers detected, each equation may only contain a single color code.`);
|
|
}
|
|
continue;
|
|
}
|
|
// If this is a valid label string
|
|
if (segmentUpperCase.startsWith("LABEL:")) {
|
|
const label = segment.split(":").slice(1).join(":").trim();
|
|
if (equation.label === undefined) {
|
|
if (label === "") {
|
|
throw new SyntaxError(`Equation label must have a value`);
|
|
}
|
|
else {
|
|
equation.label = label;
|
|
}
|
|
}
|
|
else {
|
|
throw new SyntaxError(`Duplicate equation labels detected, each equation may only contain a single label.`);
|
|
}
|
|
continue;
|
|
}
|
|
// If this is a valid defult label string
|
|
if (segmentUpperCase === "LABEL") {
|
|
// If we pass an empty string as the label,
|
|
// Desmos will use the source equation of the point as the label
|
|
equation.label = "";
|
|
continue;
|
|
}
|
|
// If none of the above, assume it is a graph restriction
|
|
if (segment.includes("\\")) {
|
|
// If the restriction included a `\` (the LaTeX control character) then the user may have tried to use the LaTeX syntax in the graph restriction (e.g `\frac{1}{2}`)
|
|
// Desmos does not allow this but returns a fairly archaic error - "A piecewise expression must have at least one condition."
|
|
const view = document.createElement("span");
|
|
const pre = document.createElement("span");
|
|
pre.innerHTML = "You may have tried to use the LaTeX syntax in the graph restriction (";
|
|
const inner = document.createElement("code");
|
|
inner.innerText = segment;
|
|
const post = document.createElement("span");
|
|
post.innerHTML =
|
|
"), please use some sort of an alternative (e.g <code>\\frac{1}{2}</code> => <code>1/2</code>) as this is not supported by Desmos.";
|
|
view.appendChild(pre);
|
|
view.appendChild(inner);
|
|
view.appendChild(post);
|
|
hint = { view };
|
|
}
|
|
if (!equation.restrictions) {
|
|
equation.restrictions = [];
|
|
}
|
|
equation.restrictions.push(segment);
|
|
}
|
|
return { data: equation, hint };
|
|
}
|
|
static parseSettings(settings) {
|
|
const graphSettings = {};
|
|
// Settings may be separated by either a newline or semicolon
|
|
settings
|
|
.split(/[;\n]/g)
|
|
.map((setting) => setting.trim())
|
|
.filter((setting) => setting !== "")
|
|
// Extract key-value pairs by splitting on the `=` in each property
|
|
.map((setting) => setting.split("="))
|
|
.forEach((setting) => {
|
|
if (setting.length > 2) {
|
|
throw new SyntaxError(`Too many segments, eaching setting must only contain a maximum of one '=' sign`);
|
|
}
|
|
const key = setting[0].trim();
|
|
const value = setting.length > 1 ? setting[1].trim() : undefined;
|
|
// Prevent duplicate keys
|
|
if (key in graphSettings) {
|
|
throw new SyntaxError(`Duplicate key '${key}' not allowed`);
|
|
}
|
|
const requiresValue = () => {
|
|
if (value === undefined) {
|
|
throw new SyntaxError(`Field '${key}' must have a value`);
|
|
}
|
|
};
|
|
switch (key) {
|
|
// Boolean fields
|
|
case "hideAxisNumbers":
|
|
case "grid": {
|
|
if (!value) {
|
|
graphSettings[key] = true;
|
|
}
|
|
else {
|
|
const lower = value.toLowerCase();
|
|
if (lower !== "true" && lower !== "false") {
|
|
throw new SyntaxError(`Field '${key}' requres a boolean value 'true'/'false' (omit a value to default to 'true')`);
|
|
}
|
|
graphSettings[key] = value === "true" ? true : false;
|
|
}
|
|
break;
|
|
}
|
|
// Integer fields
|
|
case "top":
|
|
case "bottom":
|
|
case "left":
|
|
case "right":
|
|
case "width":
|
|
case "height": {
|
|
requiresValue();
|
|
const num = parseFloat(value);
|
|
if (Number.isNaN(num)) {
|
|
throw new SyntaxError(`Field '${key}' must have an integer (or decimal) value`);
|
|
}
|
|
graphSettings[key] = num;
|
|
break;
|
|
}
|
|
// DegreeMode field
|
|
case "degreeMode": {
|
|
requiresValue();
|
|
const mode = parseStringToEnum(DegreeMode, value);
|
|
if (mode) {
|
|
graphSettings.degreeMode = mode;
|
|
}
|
|
else {
|
|
throw new SyntaxError(`Field 'degreeMode' must be either 'radians' or 'degrees'`);
|
|
}
|
|
break;
|
|
}
|
|
// Color field
|
|
case "defaultColor": {
|
|
requiresValue();
|
|
const color = parseColor(value);
|
|
if (color) {
|
|
graphSettings.defaultColor = color;
|
|
}
|
|
else {
|
|
throw new SyntaxError(`Field 'defaultColor' must be either a valid hex code or one of: ${Object.keys(ColorConstant).join(", ")}`);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
throw new SyntaxError(`Unrecognised field: ${key}`);
|
|
}
|
|
}
|
|
});
|
|
return graphSettings;
|
|
}
|
|
/** Dynamically adjust graph boundary if the defaults would cause an invalid graph with the settings supplied by the user,
|
|
* this will not do anything if the adjustment is not required.
|
|
*/
|
|
static adjustBounds(settings) {
|
|
if (settings.left !== undefined &&
|
|
settings.right === undefined &&
|
|
settings.left >= DEFAULT_GRAPH_SETTINGS.right) {
|
|
settings.right = settings.left + DEFAULT_GRAPH_WIDTH;
|
|
}
|
|
if (settings.left === undefined &&
|
|
settings.right !== undefined &&
|
|
settings.right <= DEFAULT_GRAPH_SETTINGS.left) {
|
|
settings.left = settings.right - DEFAULT_GRAPH_WIDTH;
|
|
}
|
|
if (settings.bottom !== undefined &&
|
|
settings.top === undefined &&
|
|
settings.bottom >= DEFAULT_GRAPH_SETTINGS.top) {
|
|
settings.top = settings.bottom + DEFAULT_GRAPH_HEIGHT;
|
|
}
|
|
if (settings.bottom === undefined &&
|
|
settings.top !== undefined &&
|
|
settings.top <= DEFAULT_GRAPH_SETTINGS.bottom) {
|
|
settings.bottom = settings.top - DEFAULT_GRAPH_HEIGHT;
|
|
}
|
|
return settings;
|
|
}
|
|
}
|
|
|
|
function renderError(err, el, extra) {
|
|
const wrapper = document.createElement("div");
|
|
const message = document.createElement("strong");
|
|
message.innerText = "Desmos Graph Error: ";
|
|
wrapper.appendChild(message);
|
|
const ctx = document.createElement("span");
|
|
ctx.innerText = err;
|
|
wrapper.appendChild(ctx);
|
|
if (extra) {
|
|
const messageExtra = document.createElement("strong");
|
|
messageExtra.innerHTML = "<br>Note: ";
|
|
wrapper.appendChild(messageExtra);
|
|
wrapper.appendChild(extra);
|
|
}
|
|
const container = document.createElement("div");
|
|
container.style.padding = "20px";
|
|
container.style.backgroundColor = "#f44336";
|
|
container.style.color = "white";
|
|
container.appendChild(wrapper);
|
|
el.empty();
|
|
el.appendChild(container);
|
|
}
|
|
|
|
var CacheLocation;
|
|
(function (CacheLocation) {
|
|
CacheLocation["Memory"] = "Memory";
|
|
CacheLocation["Filesystem"] = "Filesystem";
|
|
})(CacheLocation || (CacheLocation = {}));
|
|
const DEFAULT_SETTINGS_STATIC = {
|
|
// debounce: 500,
|
|
cache: {
|
|
enabled: true,
|
|
location: CacheLocation.Memory,
|
|
},
|
|
};
|
|
/** Get the default settings for the given plugin. This simply uses `DEFAULT_SETTINGS_STATIC` and patches the version from the manifest. */
|
|
function DEFAULT_SETTINGS(plugin) {
|
|
return Object.assign({ version: plugin.manifest.version }, DEFAULT_SETTINGS_STATIC);
|
|
}
|
|
/** Attempt to migrate the given settings object to the current structure */
|
|
function migrateSettings(plugin, settings) {
|
|
// todo (there is currently only one version of the settings interface)
|
|
return settings;
|
|
}
|
|
class SettingsTab extends obsidian.PluginSettingTab {
|
|
constructor(app, plugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
display() {
|
|
const { containerEl } = this;
|
|
containerEl.empty();
|
|
// new Setting(containerEl)
|
|
// .setName("Debounce Time (ms)")
|
|
// .setDesc(
|
|
// "How long to wait after a keypress to render the graph (set to 0 to disable, requires restart to take effect)"
|
|
// )
|
|
// .addText((text) =>
|
|
// text.setValue(this.plugin.settings.debounce.toString()).onChange(async (value) => {
|
|
// const val = parseInt(value);
|
|
// this.plugin.settings.debounce =
|
|
// Number.isNaN(val) || val < 0 ? DEFAULT_SETTINGS_STATIC.debounce : val;
|
|
// await this.plugin.saveSettings();
|
|
// })
|
|
// );
|
|
new obsidian.Setting(containerEl)
|
|
.setName("Cache")
|
|
.setDesc("Whether to cache the rendered graphs")
|
|
.addToggle((toggle) => toggle.setValue(this.plugin.settings.cache.enabled).onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.cache.enabled = value;
|
|
yield this.plugin.saveSettings();
|
|
// Reset the display so the new state can render
|
|
this.display();
|
|
})));
|
|
if (this.plugin.settings.cache.enabled) {
|
|
new obsidian.Setting(containerEl)
|
|
.setName("Cache location")
|
|
.setDesc("Set the location to cache rendered graphs (note that memory caching is not persistent)")
|
|
.addDropdown((dropdown) => dropdown
|
|
.addOption(CacheLocation.Memory, "Memory")
|
|
.addOption(CacheLocation.Filesystem, "Filesystem")
|
|
.setValue(this.plugin.settings.cache.location)
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.cache.location = value;
|
|
yield this.plugin.saveSettings();
|
|
// Reset the display so the new state can render
|
|
this.display();
|
|
})));
|
|
if (this.plugin.settings.cache.location === CacheLocation.Filesystem) {
|
|
new obsidian.Setting(containerEl)
|
|
.setName("Cache Directory")
|
|
.setDesc(`The directory to save cached graphs in, relative to the vault root (technical note: the graphs will be saved as \`desmos-graph-<hash>.svg\` where the name is a SHA-256 hash of the graph source). Also note that a lot of junk will be saved to this folder, you have been warned.`)
|
|
.addText((text) => {
|
|
var _a;
|
|
text.setValue((_a = this.plugin.settings.cache.directory) !== null && _a !== void 0 ? _a : "").onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.cache.directory = value;
|
|
yield this.plugin.saveSettings();
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Parse an SVG into a DOM element */
|
|
function parseSVG(svg) {
|
|
return new DOMParser().parseFromString(svg, "image/svg+xml").documentElement;
|
|
}
|
|
class Renderer {
|
|
constructor(plugin) {
|
|
/** The set of graphs we are currently rendering, mapped by their hash */
|
|
this.rendering = new Map();
|
|
this.plugin = plugin;
|
|
this.active = false;
|
|
}
|
|
activate() {
|
|
if (!this.active) {
|
|
window.addEventListener("message", this.handler.bind(this));
|
|
this.active = true;
|
|
}
|
|
}
|
|
deactivate() {
|
|
if (this.active) {
|
|
window.removeEventListener("message", this.handler.bind(this));
|
|
this.active = false;
|
|
}
|
|
}
|
|
render(graph, el) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
const plugin = this.plugin;
|
|
const settings = plugin.settings;
|
|
const equations = graph.equations;
|
|
const graphSettings = graph.settings;
|
|
const hash = yield graph.hash();
|
|
let cacheFile;
|
|
// If this graph is in the cache then fetch it
|
|
if (settings.cache.enabled) {
|
|
if (settings.cache.location === CacheLocation.Memory && hash in plugin.graphCache) {
|
|
const data = plugin.graphCache[hash];
|
|
el.appendChild(parseSVG(data));
|
|
return;
|
|
}
|
|
else if (settings.cache.location === CacheLocation.Filesystem && settings.cache.directory) {
|
|
const adapter = plugin.app.vault.adapter;
|
|
cacheFile = obsidian.normalizePath(`${settings.cache.directory}/desmos-graph-${hash}.svg`);
|
|
// If this graph is in the cache
|
|
if (yield adapter.exists(cacheFile)) {
|
|
const data = yield adapter.read(cacheFile);
|
|
el.appendChild(parseSVG(data));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// Parse equations into a series of Desmos expressions
|
|
const expressions = [];
|
|
for (const equation of equations) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const expression = {
|
|
color: equation.color,
|
|
label: equation.label,
|
|
showLabel: equation.label !== undefined,
|
|
};
|
|
if (equation.restrictions) {
|
|
const restriction = equation.restrictions
|
|
.map((restriction) => `{${restriction}}`
|
|
// Escape chars
|
|
.replaceAll("{", String.raw `\{`)
|
|
.replaceAll("}", String.raw `\}`)
|
|
.replaceAll("<=", String.raw `\leq `)
|
|
.replaceAll(">=", String.raw `\geq `)
|
|
.replaceAll("<", String.raw `\le `)
|
|
.replaceAll(">", String.raw `\ge `))
|
|
.join("");
|
|
expression.latex = `${equation.equation}${restriction}`;
|
|
}
|
|
else {
|
|
expression.latex = equation.equation;
|
|
}
|
|
if (equation.style) {
|
|
if (Object.values(LineStyle).includes(ucast(equation.style))) {
|
|
expression.lineStyle = equation.style;
|
|
}
|
|
else if (Object.values(PointStyle).includes(ucast(equation.style))) {
|
|
expression.pointStyle = equation.style;
|
|
}
|
|
}
|
|
// Calling JSON.stringify twice allows us to escape the strings as well,
|
|
// meaning we can embed it directly into the calculator to undo the first stringification without parsing
|
|
expressions.push(`calculator.setExpression(JSON.parse(${JSON.stringify(JSON.stringify(expression))}));`);
|
|
}
|
|
// Because of the electron sandboxing we have to do this inside an iframe (and regardless this is safer),
|
|
// otherwise we can't include the desmos API (although it would be nice if they had a REST API of some sort)
|
|
// Interestingly enough, this script functions perfectly fine fully offline - so we could include a vendored copy if need be
|
|
// (the script gets cached by electron the first time it's used so this isn't a particularly high priority)
|
|
const htmlHead = `<script src="https://www.desmos.com/api/v1.6/calculator.js?apiKey=dcb31709b452b1cf9dc26972add0fda6"></script>`;
|
|
const htmlBody = `
|
|
<div id="calculator-${hash}" style="width: ${graphSettings.width}px; height: ${graphSettings.height}px;"></div>
|
|
<script>
|
|
const options = {
|
|
settingsMenu: false,
|
|
expressions: false,
|
|
lockViewPort: true,
|
|
zoomButtons: false,
|
|
trace: false,
|
|
xAxisNumbers: ${!graphSettings.hideAxisNumbers},
|
|
yAxisNumbers: ${!graphSettings.hideAxisNumbers},
|
|
showGrid: ${graphSettings.grid},
|
|
// Desmos takes a value of 'false' for radians and 'true' for degrees
|
|
degreeMode: ${graphSettings.degreeMode === DegreeMode.Degrees},
|
|
};
|
|
|
|
const calculator = Desmos.GraphingCalculator(document.getElementById("calculator-${hash}"), options);
|
|
calculator.setMathBounds({
|
|
left: ${graphSettings.left},
|
|
right: ${graphSettings.right},
|
|
top: ${graphSettings.top},
|
|
bottom: ${graphSettings.bottom},
|
|
});
|
|
|
|
${expressions.join("\n")}
|
|
|
|
// Desmos returns an error if we try to observe the expressions without any defined
|
|
if (${expressions.length > 0}) {
|
|
calculator.observe("expressionAnalysis", () => {
|
|
for (const id in calculator.expressionAnalysis) {
|
|
const analysis = calculator.expressionAnalysis[id];
|
|
if (analysis.isError) {
|
|
parent.postMessage({ t: "desmos-graph", d: "error", o: "${window.origin}", data: analysis.errorMessage, hash: "${hash}" }, "${window.origin}");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
calculator.asyncScreenshot({ showLabels: true, format: "svg" }, (data) => {
|
|
document.body.innerHTML = "";
|
|
parent.postMessage({ t: "desmos-graph", d: "render", o: "${window.origin}", data, hash: "${hash}" }, "${window.origin}");
|
|
});
|
|
</script>
|
|
`;
|
|
const htmlSrc = `<html><head>${htmlHead}</head><body>${htmlBody}</body>`;
|
|
const iframe = document.createElement("iframe");
|
|
iframe.sandbox.add("allow-scripts"); // enable sandbox mode - this prevents any xss exploits from an untrusted source in the frame (and prevents it from accessing the parent)
|
|
iframe.width = graphSettings.width.toString();
|
|
iframe.height = graphSettings.height.toString();
|
|
iframe.className = "desmos-graph";
|
|
iframe.style.border = "none";
|
|
iframe.scrolling = "no"; // fixme use a non-depreciated function
|
|
iframe.srcdoc = htmlSrc;
|
|
// iframe.style.display = "none"; // fixme hiding the iframe breaks the positioning
|
|
el.appendChild(iframe);
|
|
return new Promise((resolve) => this.rendering.set(hash, { graph, el, resolve, cacheFile }));
|
|
});
|
|
}
|
|
handler(message) {
|
|
var _a;
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (message.data.o === window.origin && message.data.t === "desmos-graph") {
|
|
const state = this.rendering.get(message.data.hash);
|
|
if (state) {
|
|
const { graph, el, resolve, cacheFile } = state;
|
|
el.empty();
|
|
if (message.data.d === "error") {
|
|
renderError(message.data.data, el, (_a = graph.potentialErrorHint) === null || _a === void 0 ? void 0 : _a.view);
|
|
resolve(); // let caller know we are done rendering
|
|
}
|
|
else if (message.data.d === "render") {
|
|
const { data } = message.data;
|
|
const node = parseSVG(data);
|
|
node.setAttribute("class", "desmos-graph");
|
|
el.appendChild(node);
|
|
resolve(); // let caller know we are done rendering
|
|
const plugin = this.plugin;
|
|
const settings = plugin.settings;
|
|
const hash = yield graph.hash();
|
|
if (settings.cache.enabled) {
|
|
if (settings.cache.location === CacheLocation.Memory) {
|
|
plugin.graphCache[hash] = data;
|
|
}
|
|
else if (settings.cache.location === CacheLocation.Filesystem) {
|
|
const adapter = plugin.app.vault.adapter;
|
|
if (cacheFile && settings.cache.directory) {
|
|
if (yield adapter.exists(settings.cache.directory)) {
|
|
yield adapter.write(cacheFile, data);
|
|
}
|
|
else {
|
|
new obsidian.Notice(`desmos-graph: target cache directory '${settings.cache.directory}' does not exist, skipping cache`, 10000);
|
|
}
|
|
}
|
|
else {
|
|
new obsidian.Notice(`desmos-graph: filesystem caching enabled but no cache directory set, skipping cache`, 10000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.rendering.delete(message.data.hash);
|
|
}
|
|
else {
|
|
// do nothing if graph is not in render list (this should not happen)
|
|
console.warn(`Got graph not in render list, this is probably a bug - ${JSON.stringify(this.rendering)}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class Desmos extends obsidian.Plugin {
|
|
constructor() {
|
|
super(...arguments);
|
|
/** Helper for in-memory graph caching */
|
|
this.graphCache = {};
|
|
}
|
|
onload() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
yield this.loadSettings();
|
|
this.renderer = new Renderer(this);
|
|
this.renderer.activate();
|
|
this.addSettingTab(new SettingsTab(this.app, this));
|
|
this.registerMarkdownCodeBlockProcessor("desmos-graph", (source, el) => __awaiter(this, void 0, void 0, function* () {
|
|
try {
|
|
const graph = Graph.parse(source);
|
|
yield this.renderer.render(graph, el);
|
|
}
|
|
catch (err) {
|
|
if (err instanceof Error) {
|
|
renderError(err.message, el);
|
|
}
|
|
else if (typeof err === "string") {
|
|
renderError(err, el);
|
|
}
|
|
else {
|
|
renderError("Unexpected error - see console for debug log", el);
|
|
console.error(err);
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
}
|
|
unload() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.renderer.deactivate();
|
|
});
|
|
}
|
|
loadSettings() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
let settings = yield this.loadData();
|
|
if (!settings) {
|
|
settings = DEFAULT_SETTINGS(this);
|
|
}
|
|
if (settings.version !== this.manifest.version) {
|
|
settings = migrateSettings(this, settings);
|
|
}
|
|
this.settings = settings;
|
|
});
|
|
}
|
|
saveSettings() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
yield this.saveData(this.settings);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = Desmos;
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy91dGlscy50cyIsInNyYy9ncmFwaC9pbnRlcmZhY2UudHMiLCJzcmMvZ3JhcGgvcGFyc2VyLnRzIiwic3JjL2Vycm9yLnRzIiwic3JjL3NldHRpbmdzLnRzIiwic3JjL3JlbmRlcmVyLnRzIiwic3JjL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOm51bGwsIm5hbWVzIjpbIlBsdWdpblNldHRpbmdUYWIiLCJTZXR0aW5nIiwibm9ybWFsaXplUGF0aCIsIk5vdGljZSIsIlBsdWdpbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQXVEQTtBQUNPLFNBQVMsU0FBUyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLFNBQVMsRUFBRTtBQUM3RCxJQUFJLFNBQVMsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLE9BQU8sS0FBSyxZQUFZLENBQUMsR0FBRyxLQUFLLEdBQUcsSUFBSSxDQUFDLENBQUMsVUFBVSxPQUFPLEVBQUUsRUFBRSxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRTtBQUNoSCxJQUFJLE9BQU8sS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLE9BQU8sQ0FBQyxFQUFFLFVBQVUsT0FBTyxFQUFFLE1BQU0sRUFBRTtBQUMvRCxRQUFRLFNBQVMsU0FBUyxDQUFDLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUU7QUFDbkcsUUFBUSxTQUFTLFFBQVEsQ0FBQyxL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
|