mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-21 11:01:26 -05:00
More plugins: splashscreen and importmap (#938)
This PR move codes from main.ts into two new plugins: - splashscreen (formerly known as py-loader) - importmap The old setting config.autoclose_loader is still supported but deprecated; the new setting is config.splashscreen.autoclose. Moreover, it does a small refactoring around UserError: now UserErrors are correctly caught even if they are raised from within afterRuntimeLoad.
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-loader');
|
||||
|
||||
export class PyLoader extends HTMLElement {
|
||||
widths: string[];
|
||||
label: string;
|
||||
mount_name: string;
|
||||
details: HTMLElement;
|
||||
operation: HTMLElement;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `<div id="pyscript_loading_splash" class="py-overlay">
|
||||
<div class="py-pop-up">
|
||||
<div class="smooth spinner"></div>
|
||||
<div id="pyscript-loading-label" class="label">
|
||||
<div id="pyscript-operation-details">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.operation = document.getElementById('pyscript-operation');
|
||||
this.details = document.getElementById('pyscript-operation-details');
|
||||
}
|
||||
|
||||
log(msg: string) {
|
||||
// loader messages are showed both in the HTML and in the console
|
||||
logger.info(msg);
|
||||
const newLog = document.createElement('p');
|
||||
newLog.innerText = msg;
|
||||
this.details.appendChild(newLog);
|
||||
}
|
||||
|
||||
close() {
|
||||
logger.info('Closing');
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
const CLOSEBUTTON = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill="currentColor" width="12px"><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>`;
|
||||
|
||||
type MessageType = "text" | "html";
|
||||
|
||||
export class UserError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = "UserError"
|
||||
messageType: MessageType;
|
||||
|
||||
constructor(message: string, t: MessageType = "text") {
|
||||
super(message);
|
||||
this.name = "UserError";
|
||||
this.messageType = t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +22,7 @@ export class FetchError extends Error {
|
||||
export function _createAlertBanner(
|
||||
message: string,
|
||||
level: "error" | "warning" = "error",
|
||||
messageType: "text" | "html" = "text",
|
||||
messageType: MessageType = "text",
|
||||
logMessage = true) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
switch (`log-${level}-${logMessage}`) {
|
||||
@@ -53,27 +58,3 @@ export function _createAlertBanner(
|
||||
|
||||
document.body.prepend(banner);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is used to handle UserError, if we see an error of this
|
||||
* type, we will automatically create a banner on the page that will tell
|
||||
* the user what went wrong. Note that the error will still stop execution,
|
||||
* any other errors we will simply throw them and no banner will be shown.
|
||||
*/
|
||||
export function withUserErrorHandler(fn) {
|
||||
try {
|
||||
return fn();
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof UserError) {
|
||||
/*
|
||||
* Display a page-wide error message to show that something has gone wrong with
|
||||
* PyScript or Pyodide during loading. Probably not be used for issues that occur within
|
||||
* Python scripts, since stderr can be routed to somewhere in the DOM
|
||||
*/
|
||||
_createAlertBanner(error.message);
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,16 @@ import type { AppConfig } from './pyconfig';
|
||||
import type { Runtime } from './runtime';
|
||||
import { PluginManager } from './plugin';
|
||||
import { make_PyScript, initHandlers, mountElements } from './components/pyscript';
|
||||
import { PyLoader } from './components/pyloader';
|
||||
import { PyodideRuntime } from './pyodide';
|
||||
import { getLogger } from './logger';
|
||||
import { handleFetchError, showWarning, globalExport } from './utils';
|
||||
import { calculatePaths } from './plugins/fetch';
|
||||
import { createCustomElements } from './components/elements';
|
||||
import { UserError, withUserErrorHandler } from "./exceptions"
|
||||
import { UserError, _createAlertBanner } from "./exceptions"
|
||||
import { type Stdio, StdioMultiplexer, DEFAULT_STDIO } from './stdio';
|
||||
import { PyTerminalPlugin } from './plugins/pyterminal';
|
||||
|
||||
type ImportType = { [key: string]: unknown };
|
||||
type ImportMapType = {
|
||||
imports: ImportType | null;
|
||||
};
|
||||
import { SplashscreenPlugin } from './plugins/splashscreen';
|
||||
import { ImportmapPlugin } from './plugins/importmap';
|
||||
|
||||
const logger = getLogger('pyscript/main');
|
||||
|
||||
@@ -28,7 +24,7 @@ const logger = getLogger('pyscript/main');
|
||||
|
||||
2. loadConfig(): search for py-config and compute the config for the app
|
||||
|
||||
3. show the loader/splashscreen
|
||||
3. (it used to be "show the splashscreen", but now it's a plugin)
|
||||
|
||||
4. loadRuntime(): start downloading the actual runtime (e.g. pyodide.js)
|
||||
|
||||
@@ -60,7 +56,6 @@ More concretely:
|
||||
|
||||
export class PyScriptApp {
|
||||
config: AppConfig;
|
||||
loader: PyLoader;
|
||||
runtime: Runtime;
|
||||
PyScript: any; // XXX would be nice to have a more precise type for the class itself
|
||||
plugins: PluginManager;
|
||||
@@ -69,20 +64,47 @@ export class PyScriptApp {
|
||||
constructor() {
|
||||
// initialize the builtin plugins
|
||||
this.plugins = new PluginManager();
|
||||
this.plugins.add(new PyTerminalPlugin(this));
|
||||
this.plugins.add(
|
||||
new SplashscreenPlugin(),
|
||||
new PyTerminalPlugin(this),
|
||||
new ImportmapPlugin(),
|
||||
);
|
||||
|
||||
this._stdioMultiplexer = new StdioMultiplexer();
|
||||
this._stdioMultiplexer.addListener(DEFAULT_STDIO);
|
||||
}
|
||||
|
||||
// lifecycle (1)
|
||||
// Error handling logic: if during the execution we encounter an error
|
||||
// which is ultimate responsibility of the user (e.g.: syntax error in the
|
||||
// config, file not found in fetch, etc.), we can throw UserError(). It is
|
||||
// responsibility of main() to catch it and show it to the user in a
|
||||
// proper way (e.g. by using a banner at the top of the page).
|
||||
main() {
|
||||
try {
|
||||
this._realMain();
|
||||
}
|
||||
catch(error) {
|
||||
this._handleUserErrorMaybe(error);
|
||||
}
|
||||
}
|
||||
|
||||
_handleUserErrorMaybe(error) {
|
||||
if (error instanceof UserError) {
|
||||
_createAlertBanner(error.message, "error", error.messageType);
|
||||
this.plugins.onUserError(error);
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ lifecycle ============
|
||||
|
||||
// lifecycle (1)
|
||||
_realMain() {
|
||||
this.loadConfig();
|
||||
this.plugins.configure(this.config);
|
||||
|
||||
this.showLoader(); // this should be a plugin
|
||||
this.plugins.beforeLaunch(this.config);
|
||||
|
||||
this.loadRuntime();
|
||||
}
|
||||
|
||||
@@ -97,10 +119,6 @@ export class PyScriptApp {
|
||||
let el: Element | null = null;
|
||||
if (elements.length > 0) el = elements[0];
|
||||
if (elements.length >= 2) {
|
||||
// XXX: ideally, I would like to have a way to raise "fatal
|
||||
// errors" and stop the computation, but currently our life cycle
|
||||
// is too messy to implement it reliably. We might want to revisit
|
||||
// this once it's in a better shape.
|
||||
showWarning(
|
||||
'Multiple <py-config> tags detected. Only the first is ' +
|
||||
'going to be parsed, all the others will be ignored',
|
||||
@@ -110,15 +128,6 @@ export class PyScriptApp {
|
||||
logger.info('config loaded:\n' + JSON.stringify(this.config, null, 2));
|
||||
}
|
||||
|
||||
// lifecycle (3)
|
||||
showLoader() {
|
||||
// add loader to the page body
|
||||
logger.info('add py-loader');
|
||||
customElements.define('py-loader', PyLoader);
|
||||
this.loader = <PyLoader>document.createElement('py-loader');
|
||||
document.body.append(this.loader);
|
||||
}
|
||||
|
||||
// lifecycle (4)
|
||||
loadRuntime() {
|
||||
logger.info('Initializing runtime');
|
||||
@@ -135,11 +144,20 @@ export class PyScriptApp {
|
||||
runtime_cfg.src,
|
||||
runtime_cfg.name,
|
||||
runtime_cfg.lang);
|
||||
this.loader.log(`Downloading ${runtime_cfg.name}...`);
|
||||
this.logStatus(`Downloading ${runtime_cfg.name}...`);
|
||||
|
||||
// download pyodide by using a <script> tag. Once it's ready, the
|
||||
// "load" event will be fired and the exeuction logic will continue.
|
||||
// Note that the load event is fired asynchronously and thus any
|
||||
// exception which is throw inside the event handler is *NOT* caught
|
||||
// by the try/catch inside main(): that's why we need to .catch() it
|
||||
// explicitly and call _handleUserErrorMaybe also there.
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = this.runtime.src;
|
||||
script.addEventListener('load', () => {
|
||||
void this.afterRuntimeLoad(this.runtime);
|
||||
this.afterRuntimeLoad(this.runtime).catch((error) => {
|
||||
this._handleUserErrorMaybe(error);
|
||||
});
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
@@ -148,40 +166,35 @@ export class PyScriptApp {
|
||||
// See the overview comment above for an explanation of how we jump from
|
||||
// point (4) to point (5).
|
||||
//
|
||||
// Invariant: this.config and this.loader are set and available.
|
||||
// Invariant: this.config is set and available.
|
||||
async afterRuntimeLoad(runtime: Runtime): Promise<void> {
|
||||
console.assert(this.config !== undefined);
|
||||
console.assert(this.loader !== undefined);
|
||||
|
||||
this.loader.log('Python startup...');
|
||||
this.logStatus('Python startup...');
|
||||
await runtime.loadInterpreter();
|
||||
this.loader.log('Python ready!');
|
||||
this.logStatus('Python ready!');
|
||||
|
||||
// eslint-disable-next-line
|
||||
runtime.globals.set('pyscript_loader', this.loader);
|
||||
|
||||
this.loader.log('Setting up virtual environment...');
|
||||
this.logStatus('Setting up virtual environment...');
|
||||
await this.setupVirtualEnv(runtime);
|
||||
await mountElements(runtime);
|
||||
|
||||
// lifecycle (6.5)
|
||||
this.plugins.afterSetup(runtime);
|
||||
|
||||
this.loader.log('Executing <py-script> tags...');
|
||||
this.logStatus('Executing <py-script> tags...');
|
||||
this.executeScripts(runtime);
|
||||
|
||||
this.loader.log('Initializing web components...');
|
||||
this.logStatus('Initializing web components...');
|
||||
// lifecycle (8)
|
||||
createCustomElements(runtime);
|
||||
|
||||
if (runtime.config.autoclose_loader) {
|
||||
this.loader.close();
|
||||
}
|
||||
await initHandlers(runtime);
|
||||
|
||||
// NOTE: runtime message is used by integration tests to know that
|
||||
// pyscript initialization has complete. If you change it, you need to
|
||||
// change it also in tests/integration/support.py
|
||||
this.logStatus("Startup complete");
|
||||
this.plugins.afterStartup(runtime);
|
||||
logger.info('PyScript page fully initialized');
|
||||
}
|
||||
|
||||
@@ -208,8 +221,6 @@ export class PyScriptApp {
|
||||
try {
|
||||
await runtime.loadFromFile(paths[i], fetchPaths[i]);
|
||||
} catch (e) {
|
||||
// Remove the loader so users can see the banner better
|
||||
this.loader.remove()
|
||||
// The 'TypeError' here happens when running pytest
|
||||
// I'm not particularly happy with this solution.
|
||||
if (e.name === "FetchError" || e.name === "TypeError") {
|
||||
@@ -224,57 +235,20 @@ export class PyScriptApp {
|
||||
|
||||
// lifecycle (7)
|
||||
executeScripts(runtime: Runtime) {
|
||||
void this.register_importmap(runtime);
|
||||
this.PyScript = make_PyScript(runtime);
|
||||
customElements.define('py-script', this.PyScript);
|
||||
}
|
||||
|
||||
// ================= registraton API ====================
|
||||
|
||||
registerStdioListener(stdio: Stdio) {
|
||||
this._stdioMultiplexer.addListener(stdio);
|
||||
logStatus(msg: string) {
|
||||
logger.info(msg);
|
||||
const ev = new CustomEvent("py-status-message", { detail: msg });
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
async register_importmap(runtime: Runtime) {
|
||||
// make importmap ES modules available from python using 'import'.
|
||||
//
|
||||
// XXX: this code can probably be improved because errors are silently
|
||||
// ignored. Moreover at the time of writing we don't really have a test
|
||||
// for it and this functionality is used only by the d3 example. We
|
||||
// might want to rethink the whole approach at some point. E.g., maybe
|
||||
// we should move it to py-config?
|
||||
//
|
||||
// Moreover, it's also wrong because it's async and currently we don't
|
||||
// await the module to be fully registered before executing the code
|
||||
// inside py-script. It's also unclear whether we want to wait or not
|
||||
// (or maybe only wait only if we do an actual 'import'?)
|
||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||
const importmap: ImportMapType = (() => {
|
||||
try {
|
||||
return JSON.parse(node.textContent) as ImportMapType;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (importmap?.imports == null) continue;
|
||||
|
||||
for (const [name, url] of Object.entries(importmap.imports)) {
|
||||
if (typeof name != 'string' || typeof url != 'string') continue;
|
||||
|
||||
let exports: object;
|
||||
try {
|
||||
// XXX: pyodide doesn't like Module(), failing with
|
||||
// "can't read 'name' of undefined" at import time
|
||||
exports = { ...(await import(url)) } as object;
|
||||
} catch {
|
||||
logger.warn(`failed to fetch '${url}' for '${name}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
runtime.registerJsModule(name, exports);
|
||||
}
|
||||
}
|
||||
registerStdioListener(stdio: Stdio) {
|
||||
this._stdioMultiplexer.addListener(stdio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +259,6 @@ globalExport('pyscript_get_config', pyscript_get_config);
|
||||
|
||||
// main entry point of execution
|
||||
const globalApp = new PyScriptApp();
|
||||
withUserErrorHandler(globalApp.main.bind(globalApp));
|
||||
globalApp.main();
|
||||
|
||||
export const runtime = globalApp.runtime;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AppConfig } from './pyconfig';
|
||||
import type { Runtime } from './runtime';
|
||||
import type { UserError } from './exceptions';
|
||||
|
||||
export class Plugin {
|
||||
|
||||
@@ -38,6 +39,19 @@ export class Plugin {
|
||||
*/
|
||||
afterSetup(runtime: Runtime) {
|
||||
}
|
||||
|
||||
|
||||
/** Startup complete. The interpreter is initialized and ready, user
|
||||
* scripts have been executed: the main initialization logic ends here and
|
||||
* the page is ready to accept user interactions.
|
||||
*/
|
||||
afterStartup(runtime: Runtime) {
|
||||
}
|
||||
|
||||
/** Called when an UserError is raised
|
||||
*/
|
||||
onUserError(error: UserError) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +62,9 @@ export class PluginManager {
|
||||
this._plugins = [];
|
||||
}
|
||||
|
||||
add(p: Plugin) {
|
||||
this._plugins.push(p);
|
||||
add(...plugins: Plugin[]) {
|
||||
for (const p of plugins)
|
||||
this._plugins.push(p);
|
||||
}
|
||||
|
||||
configure(config: AppConfig) {
|
||||
@@ -66,4 +81,14 @@ export class PluginManager {
|
||||
for (const p of this._plugins)
|
||||
p.afterSetup(runtime);
|
||||
}
|
||||
|
||||
afterStartup(runtime: Runtime) {
|
||||
for (const p of this._plugins)
|
||||
p.afterStartup(runtime);
|
||||
}
|
||||
|
||||
onUserError(error: UserError) {
|
||||
for (const p of this._plugins)
|
||||
p.onUserError(error);
|
||||
}
|
||||
}
|
||||
|
||||
56
pyscriptjs/src/plugins/importmap.ts
Normal file
56
pyscriptjs/src/plugins/importmap.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Runtime } from '../runtime';
|
||||
import { showWarning } from '../utils';
|
||||
import { Plugin } from '../plugin';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('plugins/importmap');
|
||||
|
||||
type ImportType = { [key: string]: unknown };
|
||||
type ImportMapType = {
|
||||
imports: ImportType | null;
|
||||
};
|
||||
|
||||
export class ImportmapPlugin extends Plugin {
|
||||
|
||||
async afterSetup(runtime: Runtime) {
|
||||
// make importmap ES modules available from python using 'import'.
|
||||
//
|
||||
// XXX: this code can probably be improved because errors are silently
|
||||
// ignored.
|
||||
//
|
||||
// Moreover, it's also wrong because it's async and currently we don't
|
||||
// await the module to be fully registered before executing the code
|
||||
// inside py-script. It's also unclear whether we want to wait or not
|
||||
// (or maybe only wait only if we do an actual 'import'?)
|
||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||
const importmap: ImportMapType = (() => {
|
||||
try {
|
||||
return JSON.parse(node.textContent) as ImportMapType;
|
||||
}
|
||||
catch(error) {
|
||||
showWarning("Failed to parse import map: " + error.message);
|
||||
}
|
||||
})();
|
||||
|
||||
if (importmap?.imports == null) continue;
|
||||
|
||||
for (const [name, url] of Object.entries(importmap.imports)) {
|
||||
if (typeof name != 'string' || typeof url != 'string') continue;
|
||||
|
||||
let exports: object;
|
||||
try {
|
||||
// XXX: pyodide doesn't like Module(), failing with
|
||||
// "can't read 'name' of undefined" at import time
|
||||
exports = { ...(await import(url)) } as object;
|
||||
} catch {
|
||||
logger.warn(`failed to fetch '${url}' for '${name}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info("Registering JS module", name);
|
||||
runtime.registerJsModule(name, exports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { PyScriptApp } from '../main';
|
||||
import type { AppConfig } from '../pyconfig';
|
||||
import type { Runtime } from '../runtime';
|
||||
import { Plugin } from '../plugin';
|
||||
import { UserError } from "../exceptions"
|
||||
import { getLogger } from '../logger';
|
||||
@@ -46,7 +47,7 @@ export class PyTerminalPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
afterSetup() {
|
||||
afterSetup(runtime: Runtime) {
|
||||
// the Python interpreter has been initialized and we are ready to
|
||||
// execute user code:
|
||||
//
|
||||
|
||||
103
pyscriptjs/src/plugins/splashscreen.ts
Normal file
103
pyscriptjs/src/plugins/splashscreen.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { PyScriptApp } from '../main';
|
||||
import type { AppConfig } from '../pyconfig';
|
||||
import type { UserError } from '../exceptions';
|
||||
import type { Runtime } from '../runtime';
|
||||
import { showWarning } from '../utils';
|
||||
import { Plugin } from '../plugin';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-splashscreen');
|
||||
|
||||
const AUTOCLOSE_LOADER_DEPRECATED = `
|
||||
The setting autoclose_loader is deprecated. Please use the
|
||||
following instead:<br>
|
||||
<pre>
|
||||
<py-config>
|
||||
[splashscreen]
|
||||
autoclose = false
|
||||
</py-config>
|
||||
</pre>`;
|
||||
|
||||
export class SplashscreenPlugin extends Plugin {
|
||||
elem: PySplashscreen;
|
||||
autoclose: boolean;
|
||||
|
||||
configure(config: AppConfig) {
|
||||
// the officially supported setting is config.splashscreen.autoclose,
|
||||
// but we still also support the old config.autoclose_loader (with a
|
||||
// deprecation warning)
|
||||
this.autoclose = true;
|
||||
|
||||
if ("autoclose_loader" in config) {
|
||||
this.autoclose = config.autoclose_loader;
|
||||
showWarning(AUTOCLOSE_LOADER_DEPRECATED);
|
||||
}
|
||||
|
||||
if (config.splashscreen) {
|
||||
this.autoclose = config.splashscreen.autoclose ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
beforeLaunch(config: AppConfig) {
|
||||
// add the splashscreen to the DOM
|
||||
logger.info('add py-splashscreen');
|
||||
customElements.define('py-splashscreen', PySplashscreen);
|
||||
this.elem = <PySplashscreen>document.createElement('py-splashscreen');
|
||||
document.body.append(this.elem);
|
||||
|
||||
document.addEventListener("py-status-message", (e: CustomEvent) => {
|
||||
const msg = e.detail;
|
||||
this.elem.log(msg);
|
||||
});
|
||||
}
|
||||
|
||||
afterStartup(runtime: Runtime) {
|
||||
if (this.autoclose) {
|
||||
this.elem.close();
|
||||
}
|
||||
}
|
||||
|
||||
onUserError(error: UserError) {
|
||||
if (this.elem !== undefined) {
|
||||
// Remove the splashscreen so users can see the banner better
|
||||
this.elem.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PySplashscreen extends HTMLElement {
|
||||
widths: string[];
|
||||
label: string;
|
||||
mount_name: string;
|
||||
details: HTMLElement;
|
||||
operation: HTMLElement;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `<div id="pyscript_loading_splash" class="py-overlay">
|
||||
<div class="py-pop-up">
|
||||
<div class="smooth spinner"></div>
|
||||
<div id="pyscript-loading-label" class="label">
|
||||
<div id="pyscript-operation-details">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.operation = document.getElementById('pyscript-operation');
|
||||
this.details = document.getElementById('pyscript-operation-details');
|
||||
}
|
||||
|
||||
log(msg: string) {
|
||||
const newLog = document.createElement('p');
|
||||
newLog.innerText = msg;
|
||||
this.details.appendChild(newLog);
|
||||
}
|
||||
|
||||
close() {
|
||||
logger.info('Closing');
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export interface AppConfig extends Record<string, any> {
|
||||
author_name?: string;
|
||||
author_email?: string;
|
||||
license?: string;
|
||||
autoclose_loader?: boolean;
|
||||
runtimes?: RuntimeConfig[];
|
||||
packages?: string[];
|
||||
fetch?: FetchConfig[];
|
||||
@@ -44,14 +43,12 @@ export type PyScriptMetadata = {
|
||||
const allKeys = {
|
||||
string: ['name', 'description', 'version', 'type', 'author_name', 'author_email', 'license'],
|
||||
number: ['schema_version'],
|
||||
boolean: ['autoclose_loader'],
|
||||
array: ['runtimes', 'packages', 'fetch', 'plugins'],
|
||||
};
|
||||
|
||||
export const defaultConfig: AppConfig = {
|
||||
schema_version: 1,
|
||||
type: 'app',
|
||||
autoclose_loader: true,
|
||||
runtimes: [
|
||||
{
|
||||
src: 'https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js',
|
||||
@@ -163,7 +160,7 @@ function parseConfig(configText: string, configType = 'toml') {
|
||||
throw new UserError(`The config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}`);
|
||||
}
|
||||
} else {
|
||||
throw new UserError(`<p>The type of config supplied'${configType}' is not supported, supported values are ["toml", "json"].</p>`);
|
||||
throw new UserError(`The type of config supplied '${configType}' is not supported, supported values are ["toml", "json"]`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,8 @@ export function showWarning(msg: string, messageType: "text" | "html" = "text"):
|
||||
}
|
||||
|
||||
export function handleFetchError(e: Error, singleFile: string) {
|
||||
//Should we still export full error contents to console?
|
||||
// XXX: What happens if I make a typo? i.e. a web server is being used but a file
|
||||
// that doesn't exist is being accessed. We should cover this case as well.
|
||||
console.warn(`Caught an error in fetchPaths:\r\n ${e.toString()}`);
|
||||
// XXX: inspecting the error message to understand what happened is very
|
||||
// fragile. We need a better solution.
|
||||
let errorContent: string;
|
||||
if (e.message.includes('Failed to fetch')) {
|
||||
errorContent = `<p>PyScript: Access to local files
|
||||
@@ -66,11 +64,7 @@ export function handleFetchError(e: Error, singleFile: string) {
|
||||
} else {
|
||||
errorContent = `<p>PyScript encountered an error while loading from file: ${e.message} </p>`;
|
||||
}
|
||||
// We need to create the banner because `handleFetchError` is called before we
|
||||
// use withUserErrorHandler in main.js we are also disabling the log message
|
||||
// because it will be logged by the uncaught exception in promise.
|
||||
_createAlertBanner(errorContent, "error", "html", false);
|
||||
throw new UserError(errorContent);
|
||||
throw new UserError(errorContent, "html");
|
||||
}
|
||||
|
||||
export function readTextFromPath(path: string) {
|
||||
|
||||
Reference in New Issue
Block a user