@basthon/console-react 0.1.1 → 0.1.2

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.
Files changed (41) hide show
  1. package/lib/components/app.d.ts +15 -0
  2. package/lib/components/app.js +44 -0
  3. package/lib/components/basthon.svg +62 -0
  4. package/lib/components/button.d.ts +7 -0
  5. package/lib/components/button.js +9 -0
  6. package/lib/components/editor.d.ts +11 -0
  7. package/lib/components/editor.js +60 -0
  8. package/lib/components/footer.d.ts +7 -0
  9. package/lib/components/footer.js +8 -0
  10. package/lib/components/graphics.d.ts +6 -0
  11. package/lib/components/graphics.js +51 -0
  12. package/lib/components/header.d.ts +9 -0
  13. package/lib/components/header.js +15 -0
  14. package/lib/components/main-container.d.ts +14 -0
  15. package/lib/components/main-container.js +25 -0
  16. package/lib/components/styles.module.scss +394 -0
  17. package/lib/components/terminal.d.ts +5 -0
  18. package/lib/components/terminal.js +41 -0
  19. package/lib/gui/ace.d.ts +4 -0
  20. package/lib/gui/ace.js +17 -0
  21. package/lib/gui/editor.d.ts +35 -0
  22. package/lib/gui/editor.js +113 -0
  23. package/lib/gui/graphics.d.ts +44 -0
  24. package/lib/gui/graphics.js +185 -0
  25. package/lib/gui/gui.d.ts +87 -0
  26. package/lib/gui/gui.js +188 -0
  27. package/lib/gui/terminal.d.ts +67 -0
  28. package/lib/gui/terminal.js +242 -0
  29. package/lib/hooks/async-effect.d.ts +7 -0
  30. package/lib/hooks/async-effect.js +17 -0
  31. package/lib/hooks/basthon.d.ts +16 -0
  32. package/lib/hooks/basthon.js +55 -0
  33. package/lib/hooks/dispatch.d.ts +4 -0
  34. package/lib/hooks/dispatch.js +4 -0
  35. package/lib/hooks/gui.d.ts +3 -0
  36. package/lib/hooks/gui.js +3 -0
  37. package/lib/index.d.ts +2 -0
  38. package/lib/index.js +2 -0
  39. package/lib/state-manager.d.ts +38 -0
  40. package/lib/state-manager.js +101 -0
  41. package/package.json +1 -1
@@ -0,0 +1,394 @@
1
+ :root {
2
+ --bg-color: #eee;
3
+ --term-bg-color: #272822;
4
+ --term-fg-color: #fff;
5
+ --base-unit: 1vw;
6
+ --corner-radius: calc(0.6 * var(--base-unit));
7
+ --half-corner-radius: calc(var(--corner-radius) / 2);
8
+ --main-font-size: calc(1.2 * var(--base-unit));
9
+ --main-font: "Open Sans", sans-serif;
10
+ --shadow-logo-size: calc(0.3 * var(--base-unit));
11
+ --logo-height: 12vh;
12
+ --term-height: 60vh;
13
+ --term-font-size: 12pt;
14
+ --term-font: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro",
15
+ monospace;
16
+ --term-script-color: var(--python-grey);
17
+ --main-container-width: 90%;
18
+ --main-container-height: 75vh;
19
+ --left-container-width: 50%;
20
+ --right-container-width: calc(100% - var(--left-container-width) - 2%);
21
+ --shadow-color: rgba(0, 0, 0, 0.5);
22
+ --link-color: #888;
23
+ --subtitle-color: var(--link-color);
24
+ --python-yellow: #ffc938;
25
+ --python-blue: #366f9e;
26
+ --python-grey: #aaa;
27
+ --notification-font-size: 1.1rem;
28
+ }
29
+
30
+ [data-theme="light"] {
31
+ --term-bg-color: #fff;
32
+ --term-fg-color: #272822;
33
+ }
34
+
35
+ .body {
36
+ margin: 0px;
37
+ padding: 0px;
38
+ /* useful for kernel-loader */
39
+ font-family: var(--main-font);
40
+ font-size: var(--main-font-size);
41
+ }
42
+
43
+ .root {
44
+ background-color: var(--bg-color);
45
+ font-family: var(--main-font);
46
+ font-size: var(--main-font-size);
47
+ height: 100%;
48
+ min-height: 100vh;
49
+ overflow: auto;
50
+
51
+ // font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
52
+ // line-height: 1.5;
53
+ // font-weight: 400;
54
+ // color: rgba(255, 255, 255, 0.87);
55
+ // background-color: #242424;
56
+ // font-synthesis: none;
57
+ // text-rendering: optimizeLegibility;
58
+ // -webkit-font-smoothing: antialiased;
59
+ // -moz-osx-font-smoothing: grayscale;
60
+ }
61
+
62
+ .header {
63
+ font-size: 0;
64
+
65
+ &[data-compact="true"] {
66
+ display: block;
67
+ position: absolute;
68
+ right: 0;
69
+ bottom: 0;
70
+ overflow: hidden;
71
+ transform: rotate(-90deg) translateX(80%) translateY(200%);
72
+
73
+ .title {
74
+ font-size: var(--main-font-size);
75
+ }
76
+
77
+ .subtitle {
78
+ display: none;
79
+ }
80
+
81
+ .basthon-logo {
82
+ filter: drop-shadow(0.1vw 0.1vw 0.1vw rgba(0, 0, 0, 0.5));
83
+ vertical-align: sub;
84
+ width: calc(var(--base-unit) * 7);
85
+ height: unset;
86
+ }
87
+ }
88
+ }
89
+
90
+ .title {
91
+ text-align: center;
92
+ }
93
+
94
+ .subtitle {
95
+ text-align: center;
96
+ font-size: var(--main-font-size);
97
+ margin-bottom: calc(0.5 * var(--base-unit));
98
+ color: var(--subtitle-color);
99
+ }
100
+
101
+ textarea {
102
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
103
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
104
+ box-sizing: border-box; /* Opera/IE 8+ */
105
+ }
106
+
107
+ textarea:focus {
108
+ outline: none !important;
109
+ }
110
+
111
+ .basthon-logo {
112
+ height: var(--logo-height);
113
+ filter: drop-shadow(
114
+ var(--shadow-logo-size) var(--shadow-logo-size) var(--shadow-logo-size)
115
+ var(--shadow-color)
116
+ );
117
+ margin: 0px;
118
+ }
119
+
120
+ .container {
121
+ width: var(--main-container-width);
122
+ height: var(--main-container-height);
123
+ position: relative;
124
+ margin: auto;
125
+
126
+ &[data-compact-header] {
127
+ margin-top: 2vw;
128
+ }
129
+ }
130
+
131
+ .left-panel {
132
+ width: var(--left-container-width);
133
+ margin: auto;
134
+ float: left;
135
+
136
+ &[data-view-mode="reversed"] {
137
+ float: right;
138
+ }
139
+
140
+ &[data-view-mode="script-only"] {
141
+ width: 100%;
142
+ }
143
+
144
+ &[data-view-mode="terminal-only"] {
145
+ display: none;
146
+ }
147
+ }
148
+
149
+ .right-panel {
150
+ width: var(--right-container-width);
151
+ position: relative;
152
+ margin: auto;
153
+ float: right;
154
+ font-size: 0;
155
+
156
+ &[data-view-mode="reversed"] {
157
+ float: left;
158
+ }
159
+
160
+ &[data-view-mode="terminal-only"] {
161
+ width: 100%;
162
+ }
163
+
164
+ &[data-view-mode="script-only"] {
165
+ display: none;
166
+ }
167
+ }
168
+
169
+ %darklighted {
170
+ background-color: var(--term-bg-color);
171
+ --background: var(--term-bg-color);
172
+ color: var(--term-fg-color);
173
+ --color: var(--term-fg-color);
174
+ }
175
+
176
+ .editor {
177
+ @extend %darklighted;
178
+
179
+ float: none;
180
+ width: 100%;
181
+ height: var(--term-height);
182
+ border-radius: var(--corner-radius);
183
+ margin: 0px;
184
+ padding-top: 0px;
185
+ }
186
+
187
+ .terminal {
188
+ @extend %darklighted;
189
+
190
+ position: relative;
191
+ width: 100%;
192
+ height: var(--term-height);
193
+ float: none;
194
+ resize: none;
195
+ font-family: var(--term-font);
196
+ font-size: var(--term-font-size);
197
+ margin: 0px;
198
+ border-radius: var(--corner-radius);
199
+ border-width: 0px;
200
+
201
+ &:not([data-right-panel="terminal"]) {
202
+ display: none;
203
+ }
204
+ }
205
+
206
+ .graphics {
207
+ display: flex;
208
+ justify-content: center;
209
+ align-items: center;
210
+ position: relative;
211
+ width: 100%;
212
+ height: var(--term-height);
213
+ float: none;
214
+ flex: initial;
215
+ background-color: var(--bg-color);
216
+ resize: none;
217
+ box-sizing: border-box;
218
+ margin-bottom: 0px;
219
+ padding: 0px;
220
+ border-style: dotted;
221
+ border-color: var(--term-bg-color);
222
+ border-radius: var(--corner-radius);
223
+ border-width: calc(0.25 * var(--base-unit));
224
+ font-size: var(--term-font-size);
225
+
226
+ &:not([data-right-panel="graphics"]) {
227
+ display: none;
228
+ }
229
+ }
230
+
231
+ .graphics > :is(img, svg) {
232
+ display: block;
233
+ width: 100%;
234
+ height: 100%;
235
+ }
236
+
237
+ .graphics > :is(img) {
238
+ object-fit: contain;
239
+ }
240
+
241
+ .graphics > .animation > .anim-controls > .anim-buttons > button {
242
+ padding: revert;
243
+ width: unset;
244
+ }
245
+
246
+ .buttons-container {
247
+ display: flex;
248
+ float: left;
249
+ padding: var(--base-unit) 0px 0px 0px;
250
+ }
251
+
252
+ @keyframes btn-eye-catcher-animation {
253
+ 50% {
254
+ box-shadow:
255
+ 0px 0px calc(0.5 * var(--base-unit)) calc(1 * var(--base-unit)) #ffc938,
256
+ 0 var(--corner-radius) #999;
257
+ }
258
+ }
259
+
260
+ .btn-eye-catcher {
261
+ animation: btn-eye-catcher-animation 0.5s ease infinite;
262
+ }
263
+
264
+ .btn {
265
+ background-color: var(--python-blue);
266
+ border: none;
267
+ border-radius: var(--corner-radius);
268
+ color: white;
269
+ width: calc(5.5 * var(--base-unit));
270
+ height: calc(3.8 * var(--base-unit));
271
+ text-align: center;
272
+ text-decoration: none;
273
+ display: block;
274
+ font-size: calc(1.5 * var(--base-unit));
275
+ box-shadow: 0 var(--corner-radius) #999;
276
+ margin-right: calc(1 * var(--base-unit));
277
+
278
+ &[data-titled="true"] {
279
+ width: unset;
280
+ font-size: var(--main-font-size);
281
+ padding: 0 calc(1.8 * var(--base-unit));
282
+
283
+ & > svg {
284
+ font-size: calc(1.5 * var(--base-unit));
285
+ padding-right: calc(0.7 * var(--base-unit));
286
+ }
287
+ }
288
+
289
+ &:hover {
290
+ background-color: var(--python-yellow);
291
+ cursor: pointer;
292
+ }
293
+
294
+ &:enabled:active {
295
+ box-shadow: 0 calc(0.2 * var(--base-unit)) #666;
296
+ transform: translateY(calc(0.3 * var(--base-unit)));
297
+ }
298
+ }
299
+
300
+ .btn-logo {
301
+ /*display: block*/
302
+ }
303
+
304
+ a {
305
+ color: var(--link-color);
306
+ text-decoration: underline;
307
+
308
+ &:hover {
309
+ text-decoration: none;
310
+ }
311
+ }
312
+
313
+ .footer {
314
+ position: absolute;
315
+ right: 0;
316
+ bottom: 0;
317
+ overflow: hidden;
318
+ }
319
+
320
+ .help {
321
+ font-size: calc(3 * var(--base-unit));
322
+
323
+ > svg {
324
+ display: block;
325
+ padding: 1vh;
326
+ }
327
+ }
328
+
329
+ :global {
330
+ .basthon-loader-root.basthon-loader-foot {
331
+ border-radius: var(--corner-radius) var(--corner-radius) 0px 0px;
332
+ }
333
+
334
+ .basthon-dynamic-iframe {
335
+ flex: 1 0 auto;
336
+ }
337
+
338
+ .matplotlib-toolbar-button {
339
+ display: inline-block;
340
+ font-style: normal;
341
+ font-variant: normal;
342
+ line-height: 1;
343
+ text-rendering: auto;
344
+ font-weight: 900;
345
+ }
346
+
347
+ .darklighted {
348
+ background-color: var(--term-bg-color);
349
+ --background: var(--term-bg-color);
350
+ color: var(--term-fg-color);
351
+ --color: var(--term-fg-color);
352
+ }
353
+
354
+ // increase specificity to bypass .terminal { height: auto; }
355
+ div > div > .terminal {
356
+ height: var(--term-height);
357
+ }
358
+
359
+ .terminal {
360
+ --size: 1.333;
361
+ }
362
+
363
+ /* clickable script content */
364
+ .terminal-output > :not(.raw) div.exec-script-header:hover {
365
+ background-color: var(--term-script-color);
366
+ border-radius: var(--half-corner-radius);
367
+ cursor: pointer;
368
+ }
369
+
370
+ /* to force x-scroll */
371
+ .terminal-output div[style*="100%;"] {
372
+ overflow: unset;
373
+ }
374
+
375
+ .notie-container {
376
+ font-size: var(--notification-font-size);
377
+ }
378
+
379
+ .notie-background-success {
380
+ background-color: var(--python-yellow);
381
+ }
382
+
383
+ .notie-background-warning {
384
+ background-color: var(--python-blue);
385
+ }
386
+
387
+ .notie-background-error {
388
+ background-color: var(--python-blue);
389
+ }
390
+
391
+ .notie-background-info {
392
+ background-color: var(--python-grey);
393
+ }
394
+ }
@@ -0,0 +1,5 @@
1
+ import { FC, type HTMLAttributes } from "react";
2
+ interface TerminalProps extends HTMLAttributes<HTMLDivElement> {
3
+ }
4
+ declare const Terminal: FC<TerminalProps>;
5
+ export { Terminal };
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useRef } from "react";
3
+ import { useAsyncEffect } from "../hooks/async-effect";
4
+ import { useGUI } from "../hooks/gui";
5
+ import TerminalGUI from "../gui/terminal";
6
+ import * as styles from "./styles.module.scss";
7
+ /**
8
+ * Custom hook used in terminal component.
9
+ */
10
+ const useTerminal = () => {
11
+ const elementRef = useRef(null);
12
+ const termRef = useRef(null);
13
+ const gui = useGUI();
14
+ // update ref on element
15
+ const ref = useCallback((element) => {
16
+ elementRef.current = element;
17
+ }, []);
18
+ // build terminal on mount and destroy on unmount
19
+ useAsyncEffect(async () => {
20
+ const element = elementRef.current;
21
+ if (element == null)
22
+ return;
23
+ if (termRef.current == null) {
24
+ const kernel = await gui.kernelLoader.kernelAvailable();
25
+ const term = new TerminalGUI(element, kernel);
26
+ gui.setTerminal(term);
27
+ termRef.current = term;
28
+ // FIXME: needed by jquery.terminal to work with react strict mode
29
+ await new Promise((r) => setTimeout(r, 0));
30
+ }
31
+ }, async () => {
32
+ termRef.current?.destroy();
33
+ termRef.current = null;
34
+ }, []);
35
+ return ref;
36
+ };
37
+ const Terminal = (props) => {
38
+ const containerRef = useTerminal();
39
+ return _jsx("div", { ref: containerRef, className: styles.terminal, ...props });
40
+ };
41
+ export { Terminal };
@@ -0,0 +1,4 @@
1
+ import ace, { Ace } from "ace-builds";
2
+ import "ace-builds/src-noconflict/ext-language_tools";
3
+ export { Ace };
4
+ export default ace;
package/lib/gui/ace.js ADDED
@@ -0,0 +1,17 @@
1
+ import ace, { Ace } from "ace-builds";
2
+ import "ace-builds/src-noconflict/ext-language_tools";
3
+ // importing ace-builds/esm-resolver.js bundle all languages/themes etc...
4
+ // we explicitly load what is needed.
5
+ /* languages */
6
+ ace.config.setModuleLoader("ace/mode/python", () => import("ace-builds/src-noconflict/mode-python.js"));
7
+ ace.config.setModuleLoader("ace/mode/python3", () => import("ace-builds/src-noconflict/mode-python.js"));
8
+ ace.config.setModuleLoader("ace/mode/javascript", () => import("ace-builds/src-noconflict/mode-javascript.js"));
9
+ ace.config.setModuleUrl("ace/mode/javascript_worker", new URL("ace-builds/src-noconflict/worker-javascript.js", import.meta.url)
10
+ .href);
11
+ ace.config.setModuleLoader("ace/mode/ocaml", () => import("ace-builds/src-noconflict/mode-ocaml.js"));
12
+ ace.config.setModuleLoader("ace/mode/sql", () => import("ace-builds/src-noconflict/mode-sql.js"));
13
+ /* themes */
14
+ ace.config.setModuleLoader("ace/theme/monokai", () => import("ace-builds/src-noconflict/theme-monokai.js"));
15
+ ace.config.setModuleLoader("ace/theme/xcode", () => import("ace-builds/src-noconflict/theme-xcode.js"));
16
+ export { Ace };
17
+ export default ace;
@@ -0,0 +1,35 @@
1
+ import { KernelBase } from "@basthon/kernel-base";
2
+ import { type Language, type Theme } from "../state-manager";
3
+ /**
4
+ * Wrapper class around Ace editor.
5
+ */
6
+ export default class Editor {
7
+ private _kernel;
8
+ private _editor;
9
+ private _onSetContent?;
10
+ constructor(element: HTMLElement, kernel: KernelBase, onSetContent?: (_: string) => void, onChange?: () => void);
11
+ /**
12
+ * Set Ace mode from language.
13
+ */
14
+ setLanguage(language: Language): Promise<void>;
15
+ /**
16
+ * Set Ace theme from our theme.
17
+ */
18
+ setTheme(theme: Theme): Promise<void>;
19
+ /**
20
+ * Dispatch action to set content.
21
+ */
22
+ setContent(content: string): void;
23
+ /**
24
+ * Set Ace editor content.
25
+ */
26
+ setContentNoDispatch(content: string): void;
27
+ /**
28
+ * Get editor's content
29
+ */
30
+ getContent(): string;
31
+ /**
32
+ * Destroy all created by ace.
33
+ */
34
+ destroy(): void;
35
+ }
@@ -0,0 +1,113 @@
1
+ import ace from "./ace";
2
+ /**
3
+ * Wrapper class around Ace editor.
4
+ */
5
+ export default class Editor {
6
+ constructor(element, kernel, onSetContent, onChange) {
7
+ this._kernel = kernel;
8
+ this._onSetContent = onSetContent;
9
+ this._editor = ace.edit(element);
10
+ this._editor.setOptions({
11
+ enableLiveAutocompletion: true,
12
+ enableBasicAutocompletion: true,
13
+ highlightActiveLine: false,
14
+ highlightSelectedWord: true,
15
+ fontSize: "12pt",
16
+ });
17
+ // connect onchange
18
+ if (onChange != null)
19
+ this._editor.session.on("change", () => onChange());
20
+ /* editor's completion */
21
+ // removing all completers and keeping track of local completer
22
+ const localCompleter = this._editor.completers[1]; // any hint to detect this dynamically?
23
+ this._editor.completers = [
24
+ {
25
+ getCompletions: async (editor, session, pos, prefix, callback) => {
26
+ // get local completions (from editor, not namespace)
27
+ let locals = [];
28
+ localCompleter.getCompletions(editor, session, pos, prefix, function (_, completions) {
29
+ locals = completions;
30
+ });
31
+ // recover editor content up to position
32
+ let lines = editor.getValue().split("\n");
33
+ lines = lines.slice(0, pos.row + 1);
34
+ lines[lines.length - 1] = lines[lines.length - 1].slice(0, pos.column);
35
+ const src = lines.join("\n");
36
+ // Basthon completion
37
+ const kernelCompletions = await this._kernel.complete(src);
38
+ if (!kernelCompletions?.length)
39
+ return;
40
+ const start = kernelCompletions[1];
41
+ let completions = kernelCompletions[0];
42
+ // removing prefix
43
+ const basthon_prefix = src.slice(start, -prefix.length);
44
+ completions = completions.map((c) => c.slice(basthon_prefix.length));
45
+ // union with local completions
46
+ const completionsSet = new Set(completions);
47
+ for (let c of locals) {
48
+ c = c.value;
49
+ if (!(completionsSet.has(c) || completionsSet.has(c + "(")))
50
+ completionsSet.add(c);
51
+ }
52
+ // rendering
53
+ callback(null, [...completionsSet].map((value) => ({
54
+ caption: value,
55
+ value: value,
56
+ meta: "code",
57
+ score: 100,
58
+ })));
59
+ },
60
+ },
61
+ ];
62
+ this._editor.focus();
63
+ }
64
+ /**
65
+ * Set Ace mode from language.
66
+ */
67
+ async setLanguage(language) {
68
+ // some languages are not supported (e.g. xcas)
69
+ if (!["sql", "javascript", "python3", "ocaml"].includes(language))
70
+ return;
71
+ await new Promise((resolve) => {
72
+ this._editor.session.setMode(`ace/mode/${language}`, resolve);
73
+ });
74
+ }
75
+ /**
76
+ * Set Ace theme from our theme.
77
+ */
78
+ async setTheme(theme) {
79
+ const aceTheme = theme === "dark" ? "monokai" : "xcode";
80
+ await new Promise((resolve) => {
81
+ this._editor.setTheme(`ace/theme/${aceTheme}`, resolve);
82
+ });
83
+ }
84
+ /**
85
+ * Dispatch action to set content.
86
+ */
87
+ setContent(content) {
88
+ this._onSetContent?.(content);
89
+ }
90
+ /**
91
+ * Set Ace editor content.
92
+ */
93
+ setContentNoDispatch(content) {
94
+ if (content != null)
95
+ this._editor.setValue(content);
96
+ this._editor.scrollToRow(0);
97
+ this._editor.gotoLine(0, 0, false);
98
+ }
99
+ /**
100
+ * Get editor's content
101
+ */
102
+ getContent() {
103
+ return this._editor.getValue();
104
+ }
105
+ /**
106
+ * Destroy all created by ace.
107
+ */
108
+ destroy() {
109
+ this._editor.destroy();
110
+ // this is advertised by ace but conflicts with react -> disabled
111
+ // aceEditor.container.remove();
112
+ }
113
+ }
@@ -0,0 +1,44 @@
1
+ import { KernelBase } from "@basthon/kernel-base";
2
+ declare global {
3
+ interface Window {
4
+ MathJax?: any;
5
+ }
6
+ }
7
+ /**
8
+ * Dedicated class to manage action not managed by react.
9
+ */
10
+ export default class Graphics {
11
+ private _elem;
12
+ private _onContentUpdate;
13
+ private _destroyCb;
14
+ private _handleDynamicIFrame?;
15
+ constructor(element: HTMLElement, kernel: KernelBase, onContentUpdate: Function);
16
+ /**
17
+ * Set the dynamic IFrame handler called on each new dynamic iframe.
18
+ */
19
+ setDynamicIFrameHandler(handler: (_: HTMLIFrameElement) => void): void;
20
+ /**
21
+ * Same as that.innerHTML = html but with script execution support.
22
+ */
23
+ setInnerHTML(html: string, runScripts?: boolean): void;
24
+ /**
25
+ * CSS transform the first child element to fit the view.
26
+ */
27
+ private fitContent;
28
+ /**
29
+ * To be called when resized.
30
+ */
31
+ onResize(): void;
32
+ /**
33
+ * Display an element from Basthon data.
34
+ */
35
+ display(data: any): Promise<void>;
36
+ /**
37
+ * Clean graphics.
38
+ */
39
+ clean(): void;
40
+ /**
41
+ * Destroy all created by graphics.
42
+ */
43
+ destroy(): void;
44
+ }