@xom11/whiteboard 0.11.0 → 0.24.0
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 +67 -0
- package/dist/{ExcalidrawWithMenus-EAVPOPJZ.mjs → ExcalidrawWithMenus-KBLDWPM2.mjs} +2 -3
- package/dist/ExcalidrawWithMenus-KBLDWPM2.mjs.map +1 -0
- package/dist/catalog.json +57 -0
- package/dist/{chunk-PWIMZIB6.mjs → chunk-2SKXRBGS.mjs} +7 -8
- package/dist/chunk-2SKXRBGS.mjs.map +1 -0
- package/dist/chunk-33PEN2WC.mjs +57 -0
- package/dist/chunk-33PEN2WC.mjs.map +1 -0
- package/dist/chunk-3KBL77M6.mjs +127 -0
- package/dist/chunk-3KBL77M6.mjs.map +1 -0
- package/dist/chunk-5UTGXHLJ.mjs +57 -0
- package/dist/chunk-5UTGXHLJ.mjs.map +1 -0
- package/dist/chunk-6XUPIGVD.mjs +467 -0
- package/dist/chunk-6XUPIGVD.mjs.map +1 -0
- package/dist/chunk-7WG2KDRF.mjs +28 -0
- package/dist/chunk-7WG2KDRF.mjs.map +1 -0
- package/dist/chunk-FZY33J6Z.mjs +95 -0
- package/dist/chunk-FZY33J6Z.mjs.map +1 -0
- package/dist/chunk-HNQLZIEP.mjs +78 -0
- package/dist/chunk-HNQLZIEP.mjs.map +1 -0
- package/dist/chunk-NVJ7K3DK.mjs +29 -0
- package/dist/chunk-NVJ7K3DK.mjs.map +1 -0
- package/dist/chunk-O4WIZFRQ.mjs +11 -0
- package/dist/chunk-O4WIZFRQ.mjs.map +1 -0
- package/dist/{chunk-YVJP7NRG.mjs → chunk-O6QTYAKE.mjs} +7 -9
- package/dist/chunk-O6QTYAKE.mjs.map +1 -0
- package/dist/chunk-R5FL6S7L.mjs +22 -0
- package/dist/chunk-R5FL6S7L.mjs.map +1 -0
- package/dist/chunk-RBUILBX3.mjs +388 -0
- package/dist/chunk-RBUILBX3.mjs.map +1 -0
- package/dist/chunk-RD34F5PM.mjs +57 -0
- package/dist/chunk-RD34F5PM.mjs.map +1 -0
- package/dist/{chunk-7P7SQFOW.mjs → chunk-RXOFO64U.mjs} +3 -3
- package/dist/chunk-RXOFO64U.mjs.map +1 -0
- package/dist/chunk-TOOHCAWP.mjs +1167 -0
- package/dist/chunk-TOOHCAWP.mjs.map +1 -0
- package/dist/{chunk-C6SCVOMC.mjs → chunk-TQYQVXNW.mjs} +5 -41
- package/dist/chunk-TQYQVXNW.mjs.map +1 -0
- package/dist/chunk-VBJLUHCY.mjs +23 -0
- package/dist/chunk-VBJLUHCY.mjs.map +1 -0
- package/dist/chunk-VRWZILTG.mjs +205 -0
- package/dist/chunk-VRWZILTG.mjs.map +1 -0
- package/dist/chunk-XVSO7FBM.mjs +61 -0
- package/dist/chunk-XVSO7FBM.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +3 -6
- package/dist/geometry-2d.d.ts +3 -6
- package/dist/geometry-2d.js +5069 -2651
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +8 -4
- package/dist/geometry-3d.d.mts +4 -7
- package/dist/geometry-3d.d.ts +4 -7
- package/dist/geometry-3d.js +3053 -2150
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +7 -4
- package/dist/graph-2d.d.mts +4 -7
- package/dist/graph-2d.d.ts +4 -7
- package/dist/graph-2d.js +3363 -1670
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +10 -3
- package/dist/host-3N4E4KJH.mjs +1142 -0
- package/dist/host-3N4E4KJH.mjs.map +1 -0
- package/dist/{host-Z3TEJKZA.mjs → host-6SNSZ332.mjs} +4 -4
- package/dist/{host-Z3TEJKZA.mjs.map → host-6SNSZ332.mjs.map} +1 -1
- package/dist/host-EVJT3LIF.mjs +3198 -0
- package/dist/host-EVJT3LIF.mjs.map +1 -0
- package/dist/host-HN4X3TBC.mjs +2374 -0
- package/dist/host-HN4X3TBC.mjs.map +1 -0
- package/dist/index.css +4 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +659 -19
- package/dist/index.d.ts +659 -19
- package/dist/index.js +11741 -9420
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1467 -336
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +3 -4
- package/dist/latex.d.ts +3 -4
- package/dist/latex.js +33 -18
- package/dist/latex.js.map +1 -1
- package/dist/latex.mjs +2 -3
- package/dist/render-OCVGDKK6.mjs +8 -0
- package/dist/render-OCVGDKK6.mjs.map +1 -0
- package/dist/serialize-GKN6OVPM.mjs +6 -0
- package/dist/serialize-GKN6OVPM.mjs.map +1 -0
- package/dist/{types-CinstD7T.d.mts → types-rA4slL08.d.mts} +69 -4
- package/dist/{types-CinstD7T.d.ts → types-rA4slL08.d.ts} +69 -4
- package/package.json +24 -5
- package/dist/ExcalidrawWithMenus-EAVPOPJZ.mjs.map +0 -1
- package/dist/chunk-74VEEZBV.mjs +0 -619
- package/dist/chunk-74VEEZBV.mjs.map +0 -1
- package/dist/chunk-7P7SQFOW.mjs.map +0 -1
- package/dist/chunk-BJTO5JO5.mjs +0 -11
- package/dist/chunk-BJTO5JO5.mjs.map +0 -1
- package/dist/chunk-C6SCVOMC.mjs.map +0 -1
- package/dist/chunk-D257NCQW.mjs +0 -58
- package/dist/chunk-D257NCQW.mjs.map +0 -1
- package/dist/chunk-G7FR3AIV.mjs +0 -193
- package/dist/chunk-G7FR3AIV.mjs.map +0 -1
- package/dist/chunk-HTBLO5JO.mjs +0 -41
- package/dist/chunk-HTBLO5JO.mjs.map +0 -1
- package/dist/chunk-PWIMZIB6.mjs.map +0 -1
- package/dist/chunk-SBDMF4NQ.mjs +0 -212
- package/dist/chunk-SBDMF4NQ.mjs.map +0 -1
- package/dist/chunk-WQOABS6N.mjs +0 -197
- package/dist/chunk-WQOABS6N.mjs.map +0 -1
- package/dist/chunk-YVJP7NRG.mjs.map +0 -1
- package/dist/host-N6ACNJKI.mjs +0 -3226
- package/dist/host-N6ACNJKI.mjs.map +0 -1
- package/dist/host-NKGV6RF2.mjs +0 -1134
- package/dist/host-NKGV6RF2.mjs.map +0 -1
- package/dist/host-XVK7UCRE.mjs +0 -2908
- package/dist/host-XVK7UCRE.mjs.map +0 -1
package/dist/chunk-74VEEZBV.mjs
DELETED
|
@@ -1,619 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
// src/stamps/graph-2d/serialize.ts
|
|
3
|
-
var EMPTY_GRAPH = {
|
|
4
|
-
version: 1,
|
|
5
|
-
view: { xMin: -10, xMax: 10, yMin: -10, yMax: 10, showAxis: true, showGrid: true },
|
|
6
|
-
functions: [],
|
|
7
|
-
parameters: [],
|
|
8
|
-
points: [],
|
|
9
|
-
intersections: [],
|
|
10
|
-
tangents: []
|
|
11
|
-
};
|
|
12
|
-
function stringifySerializedGraph(graph) {
|
|
13
|
-
return JSON.stringify(graph);
|
|
14
|
-
}
|
|
15
|
-
function parseSerializedGraph(jsonState) {
|
|
16
|
-
let raw;
|
|
17
|
-
try {
|
|
18
|
-
raw = JSON.parse(jsonState);
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
23
|
-
const r = raw;
|
|
24
|
-
if (r.version !== 1) return null;
|
|
25
|
-
if (!r.view || typeof r.view !== "object") return null;
|
|
26
|
-
const v = r.view;
|
|
27
|
-
if (typeof v.xMin !== "number" || typeof v.xMax !== "number" || typeof v.yMin !== "number" || typeof v.yMax !== "number" || typeof v.showAxis !== "boolean" || typeof v.showGrid !== "boolean") {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
for (const key of ["functions", "parameters", "points", "intersections", "tangents"]) {
|
|
31
|
-
if (!Array.isArray(r[key])) return null;
|
|
32
|
-
}
|
|
33
|
-
return raw;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/stamps/graph-2d/evaluator.ts
|
|
37
|
-
var ALLOWED_FUNCTIONS = {
|
|
38
|
-
sin: Math.sin,
|
|
39
|
-
cos: Math.cos,
|
|
40
|
-
tan: Math.tan,
|
|
41
|
-
asin: Math.asin,
|
|
42
|
-
acos: Math.acos,
|
|
43
|
-
atan: Math.atan,
|
|
44
|
-
log: Math.log10,
|
|
45
|
-
// log = log10 (khớp với rewriteToJs)
|
|
46
|
-
ln: Math.log,
|
|
47
|
-
// ln = log tự nhiên
|
|
48
|
-
exp: Math.exp,
|
|
49
|
-
sqrt: Math.sqrt,
|
|
50
|
-
abs: Math.abs,
|
|
51
|
-
floor: Math.floor,
|
|
52
|
-
ceil: Math.ceil,
|
|
53
|
-
round: Math.round
|
|
54
|
-
};
|
|
55
|
-
var ALLOWED_CONSTANTS = {
|
|
56
|
-
pi: Math.PI,
|
|
57
|
-
e: Math.E
|
|
58
|
-
};
|
|
59
|
-
var OPERATORS = /* @__PURE__ */ new Set(["+", "-", "*", "/", "^"]);
|
|
60
|
-
function tokenize(src) {
|
|
61
|
-
const tokens = [];
|
|
62
|
-
let i = 0;
|
|
63
|
-
while (i < src.length) {
|
|
64
|
-
const ch = src[i];
|
|
65
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
66
|
-
i++;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (ch >= "0" && ch <= "9" || ch === ".") {
|
|
70
|
-
let j = i;
|
|
71
|
-
let hasDot = false;
|
|
72
|
-
let hasExp = false;
|
|
73
|
-
while (j < src.length) {
|
|
74
|
-
const c = src[j];
|
|
75
|
-
if (c >= "0" && c <= "9") {
|
|
76
|
-
j++;
|
|
77
|
-
} else if (c === "." && !hasDot && !hasExp) {
|
|
78
|
-
hasDot = true;
|
|
79
|
-
j++;
|
|
80
|
-
} else if ((c === "e" || c === "E") && !hasExp) {
|
|
81
|
-
hasExp = true;
|
|
82
|
-
j++;
|
|
83
|
-
if (src[j] === "+" || src[j] === "-") j++;
|
|
84
|
-
} else {
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const raw = src.slice(i, j);
|
|
89
|
-
if (!/[0-9]/.test(raw)) {
|
|
90
|
-
throw new Error(`S\u1ED1 kh\xF4ng h\u1EE3p l\u1EC7 t\u1EA1i v\u1ECB tr\xED ${i}: "${raw}"`);
|
|
91
|
-
}
|
|
92
|
-
tokens.push({ type: "NUMBER", value: raw, pos: i });
|
|
93
|
-
i = j;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z") {
|
|
97
|
-
let j = i;
|
|
98
|
-
while (j < src.length) {
|
|
99
|
-
const c = src[j];
|
|
100
|
-
if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_") {
|
|
101
|
-
j++;
|
|
102
|
-
} else {
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
tokens.push({ type: "IDENT", value: src.slice(i, j), pos: i });
|
|
107
|
-
i = j;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (OPERATORS.has(ch)) {
|
|
111
|
-
tokens.push({ type: "OP", value: ch, pos: i });
|
|
112
|
-
i++;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (ch === "(") {
|
|
116
|
-
tokens.push({ type: "LPAREN", value: ch, pos: i });
|
|
117
|
-
i++;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (ch === ")") {
|
|
121
|
-
tokens.push({ type: "RPAREN", value: ch, pos: i });
|
|
122
|
-
i++;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (ch === ",") {
|
|
126
|
-
tokens.push({ type: "COMMA", value: ch, pos: i });
|
|
127
|
-
i++;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
throw new Error(`K\xFD t\u1EF1 kh\xF4ng h\u1EE3p l\u1EC7 t\u1EA1i v\u1ECB tr\xED ${i}: "${ch}"`);
|
|
131
|
-
}
|
|
132
|
-
return tokens;
|
|
133
|
-
}
|
|
134
|
-
var Parser = class {
|
|
135
|
-
constructor(tokens) {
|
|
136
|
-
this.tokens = tokens;
|
|
137
|
-
this.pos = 0;
|
|
138
|
-
}
|
|
139
|
-
peek() {
|
|
140
|
-
return this.tokens[this.pos];
|
|
141
|
-
}
|
|
142
|
-
consume() {
|
|
143
|
-
const t = this.tokens[this.pos++];
|
|
144
|
-
if (!t) throw new Error("C\xFA ph\xE1p: h\u1EBFt token s\u1EDBm");
|
|
145
|
-
return t;
|
|
146
|
-
}
|
|
147
|
-
parseExpression() {
|
|
148
|
-
const node = this.parseAddSub();
|
|
149
|
-
if (this.pos < this.tokens.length) {
|
|
150
|
-
const t = this.tokens[this.pos];
|
|
151
|
-
throw new Error(`C\xFA ph\xE1p: token th\u1EEBa "${t.value}" t\u1EA1i v\u1ECB tr\xED ${t.pos}`);
|
|
152
|
-
}
|
|
153
|
-
return node;
|
|
154
|
-
}
|
|
155
|
-
// + - (left assoc)
|
|
156
|
-
parseAddSub() {
|
|
157
|
-
let lhs = this.parseMulDiv();
|
|
158
|
-
while (true) {
|
|
159
|
-
const t = this.peek();
|
|
160
|
-
if (t && t.type === "OP" && (t.value === "+" || t.value === "-")) {
|
|
161
|
-
this.consume();
|
|
162
|
-
const rhs = this.parseMulDiv();
|
|
163
|
-
lhs = { kind: "binary", op: t.value, lhs, rhs };
|
|
164
|
-
} else {
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return lhs;
|
|
169
|
-
}
|
|
170
|
-
// * / (left assoc)
|
|
171
|
-
parseMulDiv() {
|
|
172
|
-
let lhs = this.parseUnary();
|
|
173
|
-
while (true) {
|
|
174
|
-
const t = this.peek();
|
|
175
|
-
if (t && t.type === "OP" && (t.value === "*" || t.value === "/")) {
|
|
176
|
-
this.consume();
|
|
177
|
-
const rhs = this.parseUnary();
|
|
178
|
-
lhs = { kind: "binary", op: t.value, lhs, rhs };
|
|
179
|
-
} else {
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return lhs;
|
|
184
|
-
}
|
|
185
|
-
// unary + - (right assoc) sau đó parsePow
|
|
186
|
-
parseUnary() {
|
|
187
|
-
const t = this.peek();
|
|
188
|
-
if (t && t.type === "OP" && (t.value === "+" || t.value === "-")) {
|
|
189
|
-
this.consume();
|
|
190
|
-
const arg = this.parseUnary();
|
|
191
|
-
return { kind: "unary", op: t.value, arg };
|
|
192
|
-
}
|
|
193
|
-
return this.parsePow();
|
|
194
|
-
}
|
|
195
|
-
// ^ (right assoc)
|
|
196
|
-
parsePow() {
|
|
197
|
-
const lhs = this.parsePrimary();
|
|
198
|
-
const t = this.peek();
|
|
199
|
-
if (t && t.type === "OP" && t.value === "^") {
|
|
200
|
-
this.consume();
|
|
201
|
-
const rhs = this.parseUnary();
|
|
202
|
-
return { kind: "binary", op: "^", lhs, rhs };
|
|
203
|
-
}
|
|
204
|
-
return lhs;
|
|
205
|
-
}
|
|
206
|
-
parsePrimary() {
|
|
207
|
-
const t = this.peek();
|
|
208
|
-
if (!t) throw new Error("C\xFA ph\xE1p: thi\u1EBFu bi\u1EC3u th\u1EE9c");
|
|
209
|
-
if (t.type === "NUMBER") {
|
|
210
|
-
this.consume();
|
|
211
|
-
const v = Number(t.value);
|
|
212
|
-
return { kind: "num", value: v };
|
|
213
|
-
}
|
|
214
|
-
if (t.type === "IDENT") {
|
|
215
|
-
this.consume();
|
|
216
|
-
const next = this.peek();
|
|
217
|
-
if (next && next.type === "LPAREN") {
|
|
218
|
-
this.consume();
|
|
219
|
-
const args = [];
|
|
220
|
-
const lookahead = this.peek();
|
|
221
|
-
if (!lookahead || lookahead.type !== "RPAREN") {
|
|
222
|
-
args.push(this.parseAddSub());
|
|
223
|
-
while (true) {
|
|
224
|
-
const nx = this.peek();
|
|
225
|
-
if (nx && nx.type === "COMMA") {
|
|
226
|
-
this.consume();
|
|
227
|
-
args.push(this.parseAddSub());
|
|
228
|
-
} else {
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const close = this.peek();
|
|
234
|
-
if (!close || close.type !== "RPAREN") {
|
|
235
|
-
throw new Error(`C\xFA ph\xE1p: thi\u1EBFu ")" sau h\xE0m "${t.value}"`);
|
|
236
|
-
}
|
|
237
|
-
this.consume();
|
|
238
|
-
return { kind: "call", name: t.value, args };
|
|
239
|
-
}
|
|
240
|
-
return { kind: "ident", name: t.value };
|
|
241
|
-
}
|
|
242
|
-
if (t.type === "LPAREN") {
|
|
243
|
-
this.consume();
|
|
244
|
-
const inner = this.parseAddSub();
|
|
245
|
-
const close = this.peek();
|
|
246
|
-
if (!close || close.type !== "RPAREN") {
|
|
247
|
-
throw new Error('C\xFA ph\xE1p: thi\u1EBFu ")"');
|
|
248
|
-
}
|
|
249
|
-
this.consume();
|
|
250
|
-
return inner;
|
|
251
|
-
}
|
|
252
|
-
throw new Error(`C\xFA ph\xE1p: token b\u1EA5t ng\u1EDD "${t.value}" t\u1EA1i v\u1ECB tr\xED ${t.pos}`);
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
function parseAst(src) {
|
|
256
|
-
const tokens = tokenize(src);
|
|
257
|
-
if (tokens.length === 0) throw new Error("Bi\u1EC3u th\u1EE9c r\u1ED7ng");
|
|
258
|
-
const p = new Parser(tokens);
|
|
259
|
-
return p.parseExpression();
|
|
260
|
-
}
|
|
261
|
-
function evaluate(node, env) {
|
|
262
|
-
switch (node.kind) {
|
|
263
|
-
case "num":
|
|
264
|
-
return node.value;
|
|
265
|
-
case "ident": {
|
|
266
|
-
const name = node.name;
|
|
267
|
-
if (name === "x") return env.x;
|
|
268
|
-
if (Object.prototype.hasOwnProperty.call(ALLOWED_CONSTANTS, name)) {
|
|
269
|
-
return ALLOWED_CONSTANTS[name];
|
|
270
|
-
}
|
|
271
|
-
if (name.length === 1 && Object.prototype.hasOwnProperty.call(env.params, name)) {
|
|
272
|
-
return env.params[name];
|
|
273
|
-
}
|
|
274
|
-
throw new Error(`Identifier kh\xF4ng h\u1EE3p l\u1EC7: "${name}"`);
|
|
275
|
-
}
|
|
276
|
-
case "unary": {
|
|
277
|
-
const v = evaluate(node.arg, env);
|
|
278
|
-
return node.op === "-" ? -v : +v;
|
|
279
|
-
}
|
|
280
|
-
case "binary": {
|
|
281
|
-
const a = evaluate(node.lhs, env);
|
|
282
|
-
const b = evaluate(node.rhs, env);
|
|
283
|
-
switch (node.op) {
|
|
284
|
-
case "+":
|
|
285
|
-
return a + b;
|
|
286
|
-
case "-":
|
|
287
|
-
return a - b;
|
|
288
|
-
case "*":
|
|
289
|
-
return a * b;
|
|
290
|
-
case "/":
|
|
291
|
-
return a / b;
|
|
292
|
-
// có thể trả Infinity/NaN — đúng theo IEEE 754
|
|
293
|
-
case "^":
|
|
294
|
-
return Math.pow(a, b);
|
|
295
|
-
}
|
|
296
|
-
throw new Error(`To\xE1n t\u1EED kh\xF4ng h\u1ED7 tr\u1EE3: "${node.op}"`);
|
|
297
|
-
}
|
|
298
|
-
case "call": {
|
|
299
|
-
const fn = ALLOWED_FUNCTIONS[node.name];
|
|
300
|
-
if (typeof fn !== "function") {
|
|
301
|
-
throw new Error(`H\xE0m kh\xF4ng h\u1EE3p l\u1EC7: "${node.name}"`);
|
|
302
|
-
}
|
|
303
|
-
const args = node.args.map((a) => evaluate(a, env));
|
|
304
|
-
return fn(...args);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
function collectFreeVars(node, out = /* @__PURE__ */ new Set()) {
|
|
309
|
-
switch (node.kind) {
|
|
310
|
-
case "num":
|
|
311
|
-
return out;
|
|
312
|
-
case "ident": {
|
|
313
|
-
const name = node.name;
|
|
314
|
-
if (name === "x") return out;
|
|
315
|
-
if (Object.prototype.hasOwnProperty.call(ALLOWED_CONSTANTS, name)) return out;
|
|
316
|
-
if (name.length === 1) out.add(name);
|
|
317
|
-
return out;
|
|
318
|
-
}
|
|
319
|
-
case "unary":
|
|
320
|
-
return collectFreeVars(node.arg, out);
|
|
321
|
-
case "binary":
|
|
322
|
-
collectFreeVars(node.lhs, out);
|
|
323
|
-
collectFreeVars(node.rhs, out);
|
|
324
|
-
return out;
|
|
325
|
-
case "call":
|
|
326
|
-
for (const a of node.args) collectFreeVars(a, out);
|
|
327
|
-
return out;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
function checkIdentifiers(node) {
|
|
331
|
-
switch (node.kind) {
|
|
332
|
-
case "num":
|
|
333
|
-
return null;
|
|
334
|
-
case "ident": {
|
|
335
|
-
const name = node.name;
|
|
336
|
-
if (name === "x") return null;
|
|
337
|
-
if (Object.prototype.hasOwnProperty.call(ALLOWED_CONSTANTS, name)) return null;
|
|
338
|
-
if (name.length === 1) return null;
|
|
339
|
-
return `T\xEAn kh\xF4ng h\u1EE3p l\u1EC7: "${name}"`;
|
|
340
|
-
}
|
|
341
|
-
case "unary":
|
|
342
|
-
return checkIdentifiers(node.arg);
|
|
343
|
-
case "binary":
|
|
344
|
-
return checkIdentifiers(node.lhs) ?? checkIdentifiers(node.rhs);
|
|
345
|
-
case "call": {
|
|
346
|
-
if (!Object.prototype.hasOwnProperty.call(ALLOWED_FUNCTIONS, node.name)) {
|
|
347
|
-
return `T\xEAn h\xE0m kh\xF4ng h\u1EE3p l\u1EC7: "${node.name}"`;
|
|
348
|
-
}
|
|
349
|
-
for (const a of node.args) {
|
|
350
|
-
const e = checkIdentifiers(a);
|
|
351
|
-
if (e) return e;
|
|
352
|
-
}
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
var ALLOWED_FUNCTION_NAMES = new Set(Object.keys(ALLOWED_FUNCTIONS));
|
|
358
|
-
new Set(Object.keys(ALLOWED_CONSTANTS));
|
|
359
|
-
|
|
360
|
-
// src/stamps/graph-2d/parser.ts
|
|
361
|
-
var ALLOWED_FUNCTIONS2 = ALLOWED_FUNCTION_NAMES;
|
|
362
|
-
var ALLOWED_CHARS = /^[a-zA-Z0-9_.+\-*/^()\s,]+$/;
|
|
363
|
-
var SUGGESTIONS = {
|
|
364
|
-
tg: "tan",
|
|
365
|
-
arcsin: "asin",
|
|
366
|
-
arccos: "acos",
|
|
367
|
-
arctan: "atan"
|
|
368
|
-
};
|
|
369
|
-
function errResult(message) {
|
|
370
|
-
return { ok: false, error: message, freeVars: /* @__PURE__ */ new Set() };
|
|
371
|
-
}
|
|
372
|
-
function validate(expr) {
|
|
373
|
-
const trimmed = expr.trim();
|
|
374
|
-
if (!trimmed) return errResult("Bi\u1EC3u th\u1EE9c r\u1ED7ng");
|
|
375
|
-
if (!ALLOWED_CHARS.test(trimmed)) return errResult("K\xFD t\u1EF1 kh\xF4ng h\u1EE3p l\u1EC7");
|
|
376
|
-
let tokens;
|
|
377
|
-
try {
|
|
378
|
-
tokens = tokenize(trimmed);
|
|
379
|
-
} catch {
|
|
380
|
-
return errResult("L\u1ED7i c\xFA ph\xE1p");
|
|
381
|
-
}
|
|
382
|
-
const earlyFree = /* @__PURE__ */ new Set();
|
|
383
|
-
for (const tok of tokens) {
|
|
384
|
-
if (tok.type !== "IDENT") continue;
|
|
385
|
-
const id = tok.value;
|
|
386
|
-
if (id === "x" || id === "pi" || id === "e") continue;
|
|
387
|
-
if (ALLOWED_FUNCTIONS2.has(id)) continue;
|
|
388
|
-
if (id.length === 1) {
|
|
389
|
-
earlyFree.add(id);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const hint = SUGGESTIONS[id];
|
|
393
|
-
return errResult(
|
|
394
|
-
hint ? `T\xEAn h\xE0m kh\xF4ng h\u1EE3p l\u1EC7: "${id}". B\u1EA1n c\xF3 \xFD l\xE0 "${hint}" kh\xF4ng?` : `T\xEAn kh\xF4ng h\u1EE3p l\u1EC7: "${id}"`
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
let ast;
|
|
398
|
-
try {
|
|
399
|
-
ast = parseAst(trimmed);
|
|
400
|
-
} catch {
|
|
401
|
-
return errResult("L\u1ED7i c\xFA ph\xE1p");
|
|
402
|
-
}
|
|
403
|
-
const idErr = checkIdentifiers(ast);
|
|
404
|
-
if (idErr) return errResult(idErr);
|
|
405
|
-
const freeVars = collectFreeVars(ast);
|
|
406
|
-
for (const v of earlyFree) freeVars.add(v);
|
|
407
|
-
return { ok: true, freeVars };
|
|
408
|
-
}
|
|
409
|
-
function compile(expr, paramValues) {
|
|
410
|
-
const v = validate(expr);
|
|
411
|
-
if (!v.ok) return { error: v.error ?? "Invalid" };
|
|
412
|
-
let ast;
|
|
413
|
-
try {
|
|
414
|
-
ast = parseAst(expr.trim());
|
|
415
|
-
} catch (err) {
|
|
416
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
417
|
-
}
|
|
418
|
-
return (x) => {
|
|
419
|
-
try {
|
|
420
|
-
const y = evaluate(ast, { x, params: paramValues });
|
|
421
|
-
return typeof y === "number" ? y : NaN;
|
|
422
|
-
} catch {
|
|
423
|
-
return NaN;
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// src/stamps/graph-2d/editor/handlers.ts
|
|
429
|
-
function addPointOnCurve(graph, ctx, idFactory) {
|
|
430
|
-
if (!ctx.functionId) return graph;
|
|
431
|
-
const point = {
|
|
432
|
-
id: idFactory(),
|
|
433
|
-
functionId: ctx.functionId,
|
|
434
|
-
x: ctx.x
|
|
435
|
-
};
|
|
436
|
-
return { ...graph, points: [...graph.points, point] };
|
|
437
|
-
}
|
|
438
|
-
function addIntersection(graph, functionIdA, functionIdB, idFactory) {
|
|
439
|
-
if (functionIdA === functionIdB) return graph;
|
|
440
|
-
const exists = graph.intersections.some(
|
|
441
|
-
(i) => i.functionIdA === functionIdA && i.functionIdB === functionIdB || i.functionIdA === functionIdB && i.functionIdB === functionIdA
|
|
442
|
-
);
|
|
443
|
-
if (exists) return graph;
|
|
444
|
-
const intersection = {
|
|
445
|
-
id: idFactory(),
|
|
446
|
-
functionIdA,
|
|
447
|
-
functionIdB
|
|
448
|
-
};
|
|
449
|
-
return { ...graph, intersections: [...graph.intersections, intersection] };
|
|
450
|
-
}
|
|
451
|
-
function numericalDerivative(expression, paramValues, x, h = 1e-4) {
|
|
452
|
-
const fn = compile(expression, paramValues);
|
|
453
|
-
if (typeof fn !== "function") return NaN;
|
|
454
|
-
const y1 = fn(x - h);
|
|
455
|
-
const y2 = fn(x + h);
|
|
456
|
-
return (y2 - y1) / (2 * h);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// src/stamps/graph-2d/renderObjects.ts
|
|
460
|
-
function renderGraphObjects(board, graph) {
|
|
461
|
-
const paramMap = {};
|
|
462
|
-
for (const p of graph.parameters) paramMap[p.name] = p.value;
|
|
463
|
-
for (const f of graph.functions) {
|
|
464
|
-
if (!f.visible) continue;
|
|
465
|
-
const compiled = compile(f.expression, paramMap);
|
|
466
|
-
if (typeof compiled !== "function") continue;
|
|
467
|
-
const domain = f.domain ?? { min: graph.view.xMin, max: graph.view.xMax };
|
|
468
|
-
board.create("functiongraph", [compiled, domain.min, domain.max], {
|
|
469
|
-
strokeColor: f.color,
|
|
470
|
-
strokeWidth: 2,
|
|
471
|
-
name: f.name,
|
|
472
|
-
withLabel: false,
|
|
473
|
-
highlight: false
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
for (const point of graph.points) {
|
|
477
|
-
const fn = graph.functions.find((f) => f.id === point.functionId);
|
|
478
|
-
if (!fn || !fn.visible) continue;
|
|
479
|
-
const compiled = compile(fn.expression, paramMap);
|
|
480
|
-
if (typeof compiled !== "function") continue;
|
|
481
|
-
const y = compiled(point.x);
|
|
482
|
-
board.create("point", [point.x, y], {
|
|
483
|
-
name: point.label ?? "",
|
|
484
|
-
size: 3,
|
|
485
|
-
fillColor: fn.color,
|
|
486
|
-
strokeColor: fn.color,
|
|
487
|
-
withLabel: !!point.label
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
for (const inter of graph.intersections) {
|
|
491
|
-
const fa = graph.functions.find((f) => f.id === inter.functionIdA);
|
|
492
|
-
const fb = graph.functions.find((f) => f.id === inter.functionIdB);
|
|
493
|
-
if (!fa || !fb || !fa.visible || !fb.visible) continue;
|
|
494
|
-
const cfa = compile(fa.expression, paramMap);
|
|
495
|
-
const cfb = compile(fb.expression, paramMap);
|
|
496
|
-
if (typeof cfa !== "function" || typeof cfb !== "function") continue;
|
|
497
|
-
const roots = scanRoots((x) => cfa(x) - cfb(x), graph.view.xMin, graph.view.xMax);
|
|
498
|
-
for (const x of roots) {
|
|
499
|
-
board.create("point", [x, cfa(x)], {
|
|
500
|
-
size: 3,
|
|
501
|
-
fillColor: "#000",
|
|
502
|
-
strokeColor: "#000"
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
for (const tan of graph.tangents) {
|
|
507
|
-
const pt = graph.points.find((p) => p.id === tan.pointId);
|
|
508
|
-
if (!pt) continue;
|
|
509
|
-
const fn = graph.functions.find((f) => f.id === pt.functionId);
|
|
510
|
-
if (!fn || !fn.visible) continue;
|
|
511
|
-
const slope = numericalDerivative(fn.expression, paramMap, pt.x);
|
|
512
|
-
const cfn = compile(fn.expression, paramMap);
|
|
513
|
-
if (typeof cfn !== "function" || !Number.isFinite(slope)) continue;
|
|
514
|
-
const y0 = cfn(pt.x);
|
|
515
|
-
const x1 = graph.view.xMin;
|
|
516
|
-
const x2 = graph.view.xMax;
|
|
517
|
-
board.create(
|
|
518
|
-
"line",
|
|
519
|
-
[
|
|
520
|
-
[x1, slope * (x1 - pt.x) + y0],
|
|
521
|
-
[x2, slope * (x2 - pt.x) + y0]
|
|
522
|
-
],
|
|
523
|
-
{
|
|
524
|
-
strokeColor: fn.color,
|
|
525
|
-
strokeWidth: 1,
|
|
526
|
-
dash: 2,
|
|
527
|
-
straightFirst: false,
|
|
528
|
-
straightLast: false
|
|
529
|
-
}
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
function scanRoots(fn, xMin, xMax, samples = 200) {
|
|
534
|
-
const roots = [];
|
|
535
|
-
const step = (xMax - xMin) / samples;
|
|
536
|
-
let prevX = xMin;
|
|
537
|
-
let prevY = fn(prevX);
|
|
538
|
-
for (let i = 1; i <= samples; i++) {
|
|
539
|
-
const x = xMin + i * step;
|
|
540
|
-
const y = fn(x);
|
|
541
|
-
if (Number.isFinite(prevY) && Number.isFinite(y) && prevY * y < 0) {
|
|
542
|
-
let a = prevX;
|
|
543
|
-
let b = x;
|
|
544
|
-
let ya = prevY;
|
|
545
|
-
for (let j = 0; j < 30; j++) {
|
|
546
|
-
const m = (a + b) / 2;
|
|
547
|
-
const ym = fn(m);
|
|
548
|
-
if (Math.abs(ym) < 1e-6) {
|
|
549
|
-
a = b = m;
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
if (ya * ym < 0) {
|
|
553
|
-
b = m;
|
|
554
|
-
} else {
|
|
555
|
-
a = m;
|
|
556
|
-
ya = ym;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
roots.push((a + b) / 2);
|
|
560
|
-
}
|
|
561
|
-
prevX = x;
|
|
562
|
-
prevY = y;
|
|
563
|
-
}
|
|
564
|
-
return roots;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// src/stamps/graph-2d/render.ts
|
|
568
|
-
async function renderGraph2dSvgFromState(jsonState) {
|
|
569
|
-
const parsed = parseSerializedGraph(jsonState);
|
|
570
|
-
if (!parsed) throw new Error("renderGraph2dSvgFromState: jsonState corrupt");
|
|
571
|
-
const JXG = (await import('jsxgraph')).default;
|
|
572
|
-
const opts = JXG.Options;
|
|
573
|
-
if (opts) {
|
|
574
|
-
opts.text = opts.text || {};
|
|
575
|
-
opts.text.display = "internal";
|
|
576
|
-
opts.text.useASCIIMathML = false;
|
|
577
|
-
opts.text.useMathJax = false;
|
|
578
|
-
opts.text.useKatex = false;
|
|
579
|
-
opts.label = opts.label || {};
|
|
580
|
-
opts.label.display = "internal";
|
|
581
|
-
}
|
|
582
|
-
const container = document.createElement("div");
|
|
583
|
-
container.id = `jxg_graph2d_off_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
584
|
-
container.style.cssText = "position:absolute;top:-99999px;left:-99999px;width:600px;height:400px;visibility:hidden;pointer-events:none;";
|
|
585
|
-
document.body.appendChild(container);
|
|
586
|
-
let board = null;
|
|
587
|
-
try {
|
|
588
|
-
board = JXG.JSXGraph.initBoard(container.id, {
|
|
589
|
-
boundingbox: [parsed.view.xMin, parsed.view.yMax, parsed.view.xMax, parsed.view.yMin],
|
|
590
|
-
axis: parsed.view.showAxis,
|
|
591
|
-
grid: parsed.view.showGrid,
|
|
592
|
-
showCopyright: false,
|
|
593
|
-
showNavigation: false,
|
|
594
|
-
keepAspectRatio: false
|
|
595
|
-
});
|
|
596
|
-
renderGraphObjects(board, parsed);
|
|
597
|
-
board.update();
|
|
598
|
-
const svgEl = container.querySelector("svg");
|
|
599
|
-
if (!svgEl) throw new Error("renderGraph2dSvgFromState: no svg generated");
|
|
600
|
-
return svgEl.outerHTML;
|
|
601
|
-
} finally {
|
|
602
|
-
try {
|
|
603
|
-
if (board) JXG.JSXGraph.freeBoard(board);
|
|
604
|
-
} catch {
|
|
605
|
-
}
|
|
606
|
-
if (container.parentNode) container.parentNode.removeChild(container);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// src/stamps/graph-2d/types.ts
|
|
611
|
-
function isGraph2DCustomData(data) {
|
|
612
|
-
if (!data || typeof data !== "object") return false;
|
|
613
|
-
const d = data;
|
|
614
|
-
return d.kind === "graph2d" && d.version === 1 && typeof d.jsonState === "string";
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
export { EMPTY_GRAPH, addIntersection, addPointOnCurve, compile, isGraph2DCustomData, numericalDerivative, parseSerializedGraph, renderGraph2dSvgFromState, stringifySerializedGraph, validate };
|
|
618
|
-
//# sourceMappingURL=chunk-74VEEZBV.mjs.map
|
|
619
|
-
//# sourceMappingURL=chunk-74VEEZBV.mjs.map
|