@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.
- package/lib/components/app.d.ts +15 -0
- package/lib/components/app.js +44 -0
- package/lib/components/basthon.svg +62 -0
- package/lib/components/button.d.ts +7 -0
- package/lib/components/button.js +9 -0
- package/lib/components/editor.d.ts +11 -0
- package/lib/components/editor.js +60 -0
- package/lib/components/footer.d.ts +7 -0
- package/lib/components/footer.js +8 -0
- package/lib/components/graphics.d.ts +6 -0
- package/lib/components/graphics.js +51 -0
- package/lib/components/header.d.ts +9 -0
- package/lib/components/header.js +15 -0
- package/lib/components/main-container.d.ts +14 -0
- package/lib/components/main-container.js +25 -0
- package/lib/components/styles.module.scss +394 -0
- package/lib/components/terminal.d.ts +5 -0
- package/lib/components/terminal.js +41 -0
- package/lib/gui/ace.d.ts +4 -0
- package/lib/gui/ace.js +17 -0
- package/lib/gui/editor.d.ts +35 -0
- package/lib/gui/editor.js +113 -0
- package/lib/gui/graphics.d.ts +44 -0
- package/lib/gui/graphics.js +185 -0
- package/lib/gui/gui.d.ts +87 -0
- package/lib/gui/gui.js +188 -0
- package/lib/gui/terminal.d.ts +67 -0
- package/lib/gui/terminal.js +242 -0
- package/lib/hooks/async-effect.d.ts +7 -0
- package/lib/hooks/async-effect.js +17 -0
- package/lib/hooks/basthon.d.ts +16 -0
- package/lib/hooks/basthon.js +55 -0
- package/lib/hooks/dispatch.d.ts +4 -0
- package/lib/hooks/dispatch.js +4 -0
- package/lib/hooks/gui.d.ts +3 -0
- package/lib/hooks/gui.js +3 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/state-manager.d.ts +38 -0
- package/lib/state-manager.js +101 -0
- 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,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 };
|
package/lib/gui/ace.d.ts
ADDED
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
|
+
}
|