@basthon/gui-base 0.37.2 → 0.38.3
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/main.d.ts +68 -2
- package/lib/main.js +200 -5
- package/package.json +5 -4
package/lib/main.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { KernelBase } from "@basthon/kernel-base";
|
|
2
2
|
import { KernelLoader } from "@basthon/kernel-loader";
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
_basthonEmptyContent?: any;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export interface GUIOptions {
|
|
9
|
+
kernelRootPath: string;
|
|
10
|
+
language: string;
|
|
11
|
+
uiName?: string;
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
14
|
* Base class for console and notebook GUI.
|
|
5
15
|
*/
|
|
@@ -7,8 +17,12 @@ export declare class GUIBase {
|
|
|
7
17
|
private readonly _language;
|
|
8
18
|
private readonly _loaded;
|
|
9
19
|
private _loader;
|
|
20
|
+
private _checkpoints;
|
|
21
|
+
private _maxCheckpoints;
|
|
22
|
+
protected _contentFilename: string;
|
|
23
|
+
protected _urlKey: string;
|
|
10
24
|
private _console_error;
|
|
11
|
-
constructor(
|
|
25
|
+
constructor(options: GUIOptions);
|
|
12
26
|
/**
|
|
13
27
|
* Language getter.
|
|
14
28
|
*/
|
|
@@ -53,6 +67,46 @@ export declare class GUIBase {
|
|
|
53
67
|
* Initialize the GUI.
|
|
54
68
|
*/
|
|
55
69
|
init(options?: any): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Get the content (script or notebook content).
|
|
72
|
+
*/
|
|
73
|
+
content(): string;
|
|
74
|
+
/**
|
|
75
|
+
* Set the content (script or notebook content).
|
|
76
|
+
*/
|
|
77
|
+
setContent(content: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Loading the content from query string (ipynb=/script= or from=).
|
|
80
|
+
*/
|
|
81
|
+
loadFromQS(key: string): Promise<string | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Setup the UI (typically connect events etc..).
|
|
84
|
+
*/
|
|
85
|
+
protected setupUI(options: any): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Load content at startup.
|
|
88
|
+
*/
|
|
89
|
+
protected loadInitialContent(options: any): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Load content from local forage.
|
|
92
|
+
*/
|
|
93
|
+
protected loadFromStorage(): Promise<string | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Tag last backup as "approved".
|
|
96
|
+
*/
|
|
97
|
+
validateBackup(): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Select a checkpoint to load in the script.
|
|
100
|
+
*/
|
|
101
|
+
selectCheckpoint(): Promise<string | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Backup to checkpoints.
|
|
104
|
+
*/
|
|
105
|
+
backup(approved?: boolean): Promise<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Internal GUI init.
|
|
108
|
+
* It calls setupUI then loadInitialContent.
|
|
109
|
+
*/
|
|
56
110
|
protected _init(options?: any): Promise<void>;
|
|
57
111
|
/**
|
|
58
112
|
* Change loader text and call init function.
|
|
@@ -97,10 +151,22 @@ export declare class GUIBase {
|
|
|
97
151
|
* Open an URL in a new tab or download a file.
|
|
98
152
|
*/
|
|
99
153
|
static openURL(url: string, download?: string): void;
|
|
154
|
+
/**
|
|
155
|
+
* Returning the sharing link for the content.
|
|
156
|
+
*/
|
|
157
|
+
sharingURL(key: string): Promise<string>;
|
|
100
158
|
/**
|
|
101
159
|
* Share content via URL.
|
|
102
160
|
*/
|
|
103
|
-
protected
|
|
161
|
+
protected share(): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Opening file (async) and load its content.
|
|
164
|
+
*/
|
|
165
|
+
open(file: Blob | File): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Download content to file.
|
|
168
|
+
*/
|
|
169
|
+
download(): void;
|
|
104
170
|
/**
|
|
105
171
|
* Copy a text to clipboard.
|
|
106
172
|
*/
|
package/lib/main.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { PromiseDelegate } from "promise-delegate";
|
|
2
|
+
import { CheckpointsManager } from "@basthon/checkpoints";
|
|
2
3
|
import { KernelBase } from "@basthon/kernel-base";
|
|
3
4
|
import { KernelLoader } from "@basthon/kernel-loader";
|
|
4
5
|
/**
|
|
5
6
|
* Base class for console and notebook GUI.
|
|
6
7
|
*/
|
|
7
8
|
export class GUIBase {
|
|
8
|
-
constructor(
|
|
9
|
+
constructor(options) {
|
|
9
10
|
this._loaded = new PromiseDelegate();
|
|
11
|
+
this._maxCheckpoints = 5;
|
|
12
|
+
this._contentFilename = "content.txt";
|
|
13
|
+
this._urlKey = "content";
|
|
10
14
|
/* console errors redirected to notification system */
|
|
11
15
|
this._console_error = console.error;
|
|
12
|
-
this._language = language;
|
|
13
|
-
this._loader = new KernelLoader(kernelRootPath, language);
|
|
16
|
+
this._language = options.language;
|
|
17
|
+
this._loader = new KernelLoader(options.kernelRootPath, options.language);
|
|
14
18
|
// loading Basthon (errors are fatal)
|
|
15
19
|
this._loader.showLoader("Chargement de Basthon...", false, false);
|
|
20
|
+
/* per language checkpoints */
|
|
21
|
+
this._checkpoints = new CheckpointsManager(`${options.uiName}.${options.language}`, this._maxCheckpoints);
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
24
|
* Language getter.
|
|
@@ -78,12 +84,154 @@ export class GUIBase {
|
|
|
78
84
|
this._loaded.reject(error);
|
|
79
85
|
}
|
|
80
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the content (script or notebook content).
|
|
89
|
+
*/
|
|
90
|
+
content() { return ""; }
|
|
91
|
+
/**
|
|
92
|
+
* Set the content (script or notebook content).
|
|
93
|
+
*/
|
|
94
|
+
setContent(content) { }
|
|
95
|
+
/**
|
|
96
|
+
* Loading the content from query string (ipynb=/script= or from=).
|
|
97
|
+
*/
|
|
98
|
+
async loadFromQS(key) {
|
|
99
|
+
const url = new URL(window.location.href);
|
|
100
|
+
const from_key = 'from';
|
|
101
|
+
let content = null;
|
|
102
|
+
if (url.searchParams.has(key)) {
|
|
103
|
+
content = url.searchParams.get(key) || "";
|
|
104
|
+
try {
|
|
105
|
+
content = await this.inflate(content);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
/* backward compatibility with non compressed param */
|
|
109
|
+
if (content != null)
|
|
110
|
+
content = decodeURIComponent(content);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (url.searchParams.has(from_key)) {
|
|
114
|
+
let fileURL = url.searchParams.get(from_key);
|
|
115
|
+
if (fileURL != null)
|
|
116
|
+
fileURL = decodeURIComponent(fileURL);
|
|
117
|
+
try {
|
|
118
|
+
content = await GUIBase.xhr({
|
|
119
|
+
url: fileURL,
|
|
120
|
+
method: 'GET'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = `Le chargement de ${fileURL} a échoué.`;
|
|
125
|
+
throw new ErrorEvent(message, { message: message });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (content != null)
|
|
129
|
+
this.setContent(content);
|
|
130
|
+
return content;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Setup the UI (typically connect events etc..).
|
|
134
|
+
*/
|
|
135
|
+
async setupUI(options) {
|
|
136
|
+
this.kernelLoader.kernelAvailable().then((kernel) => {
|
|
137
|
+
// this is ugly but it seems complicated to make
|
|
138
|
+
// GUI available in websocket for the notebook
|
|
139
|
+
// so patch it!
|
|
140
|
+
const dispatchEvent = kernel.dispatchEvent.bind(this.kernel);
|
|
141
|
+
kernel.dispatchEvent = async (event, data) => {
|
|
142
|
+
if (event === "eval.request")
|
|
143
|
+
await this.backup(false);
|
|
144
|
+
dispatchEvent(event, data);
|
|
145
|
+
};
|
|
146
|
+
kernel.addEventListener("eval.finished", () => {
|
|
147
|
+
this.validateBackup();
|
|
148
|
+
});
|
|
149
|
+
kernel.addEventListener("eval.error", () => {
|
|
150
|
+
this.validateBackup();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Load content at startup.
|
|
156
|
+
*/
|
|
157
|
+
async loadInitialContent(options) {
|
|
158
|
+
if (window._basthonEmptyContent)
|
|
159
|
+
return;
|
|
160
|
+
if (await this.loadFromQS(this._urlKey) != null)
|
|
161
|
+
return;
|
|
162
|
+
this.loadFromStorage();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Load content from local forage.
|
|
166
|
+
*/
|
|
167
|
+
async loadFromStorage() {
|
|
168
|
+
const approved = await this._checkpoints.lastHasTag("approved");
|
|
169
|
+
let content = null;
|
|
170
|
+
if (approved == null) {
|
|
171
|
+
content = null;
|
|
172
|
+
}
|
|
173
|
+
else if (approved) {
|
|
174
|
+
content = await this._checkpoints.getLast();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const promise = new PromiseDelegate();
|
|
178
|
+
this.confirm("Récupération", "Il semble que Basthon ait rencontré un problème à sa dernière utilisation. Que voulez-vous faire ?", "Choisir une sauvegarde", async () => {
|
|
179
|
+
promise.resolve(await this.selectCheckpoint());
|
|
180
|
+
}, "Laisser le document vide", () => { promise.resolve(null); });
|
|
181
|
+
content = await promise.promise;
|
|
182
|
+
}
|
|
183
|
+
if (content != null)
|
|
184
|
+
this.setContent(content);
|
|
185
|
+
return content;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Tag last backup as "approved".
|
|
189
|
+
*/
|
|
190
|
+
async validateBackup() {
|
|
191
|
+
await this._checkpoints.tagLast("approved");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Select a checkpoint to load in the script.
|
|
195
|
+
*/
|
|
196
|
+
async selectCheckpoint() {
|
|
197
|
+
const times = await this._checkpoints.times();
|
|
198
|
+
const promise = new PromiseDelegate();
|
|
199
|
+
const choices = times === null || times === void 0 ? void 0 : times.map((t, i) => ({
|
|
200
|
+
text: CheckpointsManager.toHumanDate(parseInt(t, 10)),
|
|
201
|
+
handler: async () => { var _a; promise.resolve((_a = (await this._checkpoints.getIndex(i))) !== null && _a !== void 0 ? _a : null); }
|
|
202
|
+
}));
|
|
203
|
+
if (choices == null)
|
|
204
|
+
return null;
|
|
205
|
+
this.select("Choisissez une sauvegarde", "De quelle sauvegarde souhaitez-vous reprendre ?", choices, "Annuler", () => { promise.resolve(null); });
|
|
206
|
+
return await promise.promise;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Backup to checkpoints.
|
|
210
|
+
*/
|
|
211
|
+
async backup(approved = true) {
|
|
212
|
+
return await this._checkpoints.push(this.content(), approved ? ["approved"] : []);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Internal GUI init.
|
|
216
|
+
* It calls setupUI then loadInitialContent.
|
|
217
|
+
*/
|
|
81
218
|
async _init(options = null) {
|
|
82
219
|
/* all errors redirected to notification system */
|
|
83
220
|
const onerror = this.notifyError.bind(this);
|
|
84
221
|
window.addEventListener('error', onerror);
|
|
85
222
|
window.addEventListener("unhandledrejection", onerror);
|
|
86
223
|
console.error = (message) => onerror(new ErrorEvent(message, { message: message }));
|
|
224
|
+
await this._checkpoints.ready();
|
|
225
|
+
await this.setupUI(options);
|
|
226
|
+
await this.loadInitialContent(options);
|
|
227
|
+
await this.kernelLoader.kernelLoaded();
|
|
228
|
+
const init = this.initCaller.bind(this);
|
|
229
|
+
// loading aux files from URL
|
|
230
|
+
await init(this.loadURLAux.bind(this), "Chargement des fichiers auxiliaires...", true);
|
|
231
|
+
// loading modules from URL
|
|
232
|
+
await init(this.loadURLModules.bind(this), "Chargement des modules annexes...", true);
|
|
233
|
+
// end
|
|
234
|
+
this.kernelLoader.hideLoader();
|
|
87
235
|
}
|
|
88
236
|
/**
|
|
89
237
|
* Change loader text and call init function.
|
|
@@ -211,11 +359,58 @@ export class GUIBase {
|
|
|
211
359
|
anchor.click();
|
|
212
360
|
document.body.removeChild(anchor);
|
|
213
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Returning the sharing link for the content.
|
|
364
|
+
*/
|
|
365
|
+
async sharingURL(key) {
|
|
366
|
+
// to retain the Line breaks.
|
|
367
|
+
let content = this.content().replace(/\r\n|\r|\n/g, "\r\n");
|
|
368
|
+
const url = new URL(window.location.href);
|
|
369
|
+
url.hash = "";
|
|
370
|
+
url.searchParams.delete("from"); // take care of collapsing params
|
|
371
|
+
try {
|
|
372
|
+
content = await this.deflate(content);
|
|
373
|
+
}
|
|
374
|
+
catch (e) { // fallback
|
|
375
|
+
content = encodeURIComponent(content).replace(/\(/g, '%28').replace(/\)/g, '%29');
|
|
376
|
+
}
|
|
377
|
+
url.searchParams.set(key, content);
|
|
378
|
+
return url.href;
|
|
379
|
+
}
|
|
214
380
|
/**
|
|
215
381
|
* Share content via URL.
|
|
216
382
|
*/
|
|
217
|
-
async
|
|
218
|
-
|
|
383
|
+
async share() {
|
|
384
|
+
const url = await this.sharingURL(this._urlKey);
|
|
385
|
+
const message = `
|
|
386
|
+
Un lien permanant vers le contenu actuel a été créé.
|
|
387
|
+
<br>
|
|
388
|
+
<i class="fas fa-exclamation-circle"></i> Attention, partager un document trop long peut ne pas fonctionner avec certains navigateurs.`;
|
|
389
|
+
this.confirm("Partager ce document", message, "Copier dans le presse-papier", () => GUIBase.copyToClipboard(url), "Tester le lien", () => GUIBase.openURL(url));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Opening file (async) and load its content.
|
|
393
|
+
*/
|
|
394
|
+
open(file) {
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
const reader = new FileReader();
|
|
397
|
+
reader.readAsText(file);
|
|
398
|
+
reader.onload = (event) => {
|
|
399
|
+
var _a;
|
|
400
|
+
this.setContent((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.result);
|
|
401
|
+
resolve();
|
|
402
|
+
};
|
|
403
|
+
reader.onerror = reject;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Download content to file.
|
|
408
|
+
*/
|
|
409
|
+
download() {
|
|
410
|
+
this.backup();
|
|
411
|
+
const content = this.content().replace(/\r\n|\r|\n/g, "\r\n"); // To retain the Line breaks.
|
|
412
|
+
let blob = new Blob([content], { type: "text/plain" });
|
|
413
|
+
GUIBase.openURL(window.URL.createObjectURL(blob), this._contentFilename);
|
|
219
414
|
}
|
|
220
415
|
/**
|
|
221
416
|
* Binding to kernel's XHR.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basthon/gui-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.3",
|
|
4
4
|
"description": "Basthon - Base GUI",
|
|
5
5
|
"homepage": "https://basthon.fr",
|
|
6
6
|
"bugs": {
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"clean": "rm -rf lib/"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@basthon/
|
|
29
|
-
"@basthon/kernel-
|
|
28
|
+
"@basthon/checkpoints": "^0.38.3",
|
|
29
|
+
"@basthon/kernel-base": "^0.38.3",
|
|
30
|
+
"@basthon/kernel-loader": "^0.38.3",
|
|
30
31
|
"js-base64": "^3.7.2",
|
|
31
32
|
"pako": "^2.0.4",
|
|
32
33
|
"promise-delegate": "^1.0.1"
|
|
@@ -40,5 +41,5 @@
|
|
|
40
41
|
"publishConfig": {
|
|
41
42
|
"access": "public"
|
|
42
43
|
},
|
|
43
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "193136a6ba876d31b2960d72fb8325fddaa24af1"
|
|
44
45
|
}
|