add app loading splash screen and AppConfig (#279)

* add PyLoader class

* create global loader during app creation time and remove it when pyscript loading operations are done

* make the loader global and open/close when apps is starting. Also add concept of app config so users can set if they want to autoclose the loader of handle it themselves

* add pyconfig file

* auto add global config if there's no config set in the page

* remove changes to simple_clock example
This commit is contained in:
Fabio Pliger
2022-05-10 16:00:25 -05:00
committed by GitHub
parent 5f19756ff3
commit 71319d0969
6 changed files with 179 additions and 1 deletions

View File

@@ -21,6 +21,8 @@
<div id="outputDiv2" class="font-mono"></div>
<div id="outputDiv3" class="font-mono"></div>
<py-script output="outputDiv">
# demonstrates how use the global PyScript pyscript_loader
# to send operation log messages to it
import utils
utils.now()
</py-script>

View File

@@ -1,11 +1,29 @@
<script lang="ts">
import Tailwind from './Tailwind.svelte';
import { loadInterpreter } from './interpreter';
import { initializers, loadedEnvironments, mode, postInitializers, pyodideLoaded, scriptsQueue } from './stores';
import type { AppConfig } from './components/pyconfig';
import { initializers, loadedEnvironments, mode, postInitializers, pyodideLoaded, scriptsQueue, globalLoader, appConfig } from './stores';
let pyodideReadyPromise;
let loader;
let appConfig_: AppConfig = {
autoclose_loader: true,
};
globalLoader.subscribe(value => {
loader = value;
});
appConfig.subscribe( (value:AppConfig) => {
if (value){
appConfig_ = value;
}
console.log("config set!")
});
const initializePyodide = async () => {
loader.log("Loading runtime...")
pyodideReadyPromise = loadInterpreter();
const pyodide = await pyodideReadyPromise;
let newEnv = {
@@ -16,16 +34,22 @@
};
pyodideLoaded.set(pyodide);
// Inject the loader into the runtime namespace
pyodide.globals.set("pyscript_loader", loader);
loader.log("Runtime created...")
loadedEnvironments.update((value: any): any => {
value[newEnv['id']] = newEnv;
});
// now we call all initializers before we actually executed all page scripts
loader.log("Initializing components...")
for (let initializer of $initializers) {
await initializer();
}
// now we can actually execute the page scripts if we are in play mode
loader.log("Initializing scripts...")
if ($mode == 'play') {
for (let script of $scriptsQueue) {
await script.evaluate();
@@ -34,6 +58,13 @@
}
// now we call all post initializers AFTER we actually executed all page scripts
loader.log("Running post initializers...");
if (appConfig_ && appConfig_.autoclose_loader) {
loader.close();
console.log("------ loader closed ------");
}
setTimeout(async () => {
for (let initializer of $postInitializers) {
await initializer();
@@ -42,6 +73,38 @@
};
</script>
<style global>
.spinner::after {
content: '';
box-sizing: border-box;
width: 40px;
height: 40px;
position: absolute;
top: calc(40% - 20px);
left: calc(50% - 20px);
border-radius: 50%;
}
.spinner.smooth::after {
border-top: 4px solid rgba(255, 255, 255, 1.0);
border-left: 4px solid rgba(255, 255, 255, 1.0);
border-right: 4px solid rgba(255, 255, 255, 0.0);
animation: spinner .6s linear infinite;
}
@keyframes spinner {
to {transform: rotate(360deg);}
}
.label {
text-align: center;
width: 100%;
display: block;
color: rgba(255, 255, 255, 0.8);
font-size: 0.8rem;
margin-top: 6rem;
}
</style>
<svelte:head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js" on:load={initializePyodide}></script>
</svelte:head>

View File

@@ -0,0 +1,55 @@
import * as jsyaml from 'js-yaml';
import { BaseEvalElement } from './base';
import { appConfig } from '../stores';
let appConfig_;
appConfig.subscribe(value => {
appConfig_ = value;
});
export type AppConfig = {
autoclose_loader: boolean;
name?: string;
version?: string;
};
export class PyConfig extends BaseEvalElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
label: string;
mount_name: string;
details: HTMLElement;
operation: HTMLElement;
code: string;
values: AppConfig;
constructor() {
super();
}
connectedCallback() {
this.code = this.innerHTML;
this.innerHTML = '';
this.values = jsyaml.load(this.code);
if (this.values === undefined){
this.values = {
autoclose_loader: true,
};
}
appConfig.set(this.values);
console.log("config set", this.values);
}
log(msg: string){
const newLog = document.createElement('p');
newLog.innerText = msg;
this.details.appendChild(newLog);
}
close() {
this.remove();
}
}

View File

@@ -0,0 +1,38 @@
import { BaseEvalElement } from './base';
export class PyLoader extends BaseEvalElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
label: string;
mount_name: string;
details: HTMLElement;
operation: HTMLElement;
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<div id="pyscript_loading_splash" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-600 opacity-75 flex flex-col items-center justify-center">
<div class="smooth spinner"></div>
<div id="pyscript-loading-label" class="label">
<div id="pyscript-operation-details">
</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() {
this.remove();
}
}

View File

@@ -8,6 +8,9 @@ import { PyButton } from './components/pybutton';
import { PyTitle } from './components/pytitle';
import { PyInputBox } from './components/pyinputbox';
import { PyWidget } from './components/base';
import { PyLoader } from './components/pyloader';
import { globalLoader } from './stores';
import { PyConfig } from './components/pyconfig';
const xPyScript = customElements.define('py-script', PyScript);
const xPyRepl = customElements.define('py-repl', PyRepl);
@@ -17,6 +20,21 @@ const xPyButton = customElements.define('py-button', PyButton);
const xPyTitle = customElements.define('py-title', PyTitle);
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
const xPyWidget = customElements.define('py-register-widget', PyWidget);
const xPyLoader = customElements.define('py-loader', PyLoader);
const xPyConfig = customElements.define('py-config', PyConfig);
// As first thing, loop for application configs
const config = document.querySelector('py-config');
if (!config){
const loader = document.createElement('py-config');
document.body.append(loader);
}
// add loader to the page body
const loader = document.createElement('py-loader');
document.body.append(loader);
globalLoader.set(loader);
const app = new App({
target: document.body,

View File

@@ -20,6 +20,8 @@ export const mode = writable(DEFAULT_MODE);
export const scriptsQueue = writable<PyScript[]>([]);
export const initializers = writable<Initializer[]>([]);
export const postInitializers = writable<Initializer[]>([]);
export const globalLoader = writable();
export const appConfig = writable();
let scriptsQueue_: PyScript[] = [];
let initializers_: Initializer[] = [];