Skip to content

Commit

Permalink
clean up demo a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
mostthingsweb committed May 13, 2024
1 parent 23f7fcc commit 185237d
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 50 deletions.
6 changes: 3 additions & 3 deletions src/components/JQueryTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as $ from "jquery";
import 'jquery.terminal';
import 'jquery.terminal/css/jquery.terminal.min.css';
import {terminal} from "jquery";
import {usePyodide} from "../hooks/usePyodide";

interface Props {
interpreter?: TypeOrArray<JQueryTerminal.Interpreter>,
Expand Down Expand Up @@ -32,9 +33,8 @@ export const JQueryTerminal: React.ForwardRefExoticComponent<React.PropsWithoutR
update: (line: number, str: string) => {
terminalObjectRef.current?.update(line, str);
},
freeze: () => {
terminalObjectRef.current?.freeze(true);
terminalObjectRef.current?.set_prompt("");
freeze: (toggle?: boolean) => {
terminalObjectRef.current?.freeze(toggle);
},
setPrompt: (prompt) => {
terminalObjectRef.current?.set_prompt(prompt);
Expand Down
191 changes: 174 additions & 17 deletions src/components/PlaygroundTerminal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {JQueryTerminal} from "./JQueryTerminal";
import React, {useEffect, useRef} from "react";
import {usePyodide} from "../hooks/usePyodide";

import {terminal} from "jquery";
import {useEnvironmentSetup} from "../hooks/useEnvironmentSetup";
import {EnvironmentStatus, useEnvironmentSetup} from "../hooks/useEnvironmentSetup";
import {PyodideStatus} from "../hooks/usePyodide";
import {PyodideInterface} from "pyodide";

// Taken from https://terminal.jcubic.pl/examples.php
function progress(percent, width) {
var size = Math.round(width*percent/100);
var left = '', taken = '', i;
Expand All @@ -20,31 +20,188 @@ function progress(percent, width) {
return '[' + taken + left + '] ' + percent + '%';
}

// Terminal code largely taken from https://github.com/pyodide/pyodide/blob/main/src/templates/console.html
function sleep(s) {
return new Promise((resolve) => setTimeout(resolve, s));
}

function create_interpreter(pyodide: PyodideInterface, term: JQueryTerminal) {
let {repr_shorten, PyodideConsole} = pyodide.pyimport("pyodide.console");
const pyconsole = PyodideConsole(pyodide.globals);

const namespace = pyodide.globals.get("dict")();
const await_fut = pyodide.runPython(
`
import builtins
from pyodide.ffi import to_js
async def await_fut(fut):
res = await fut
if res is not None:
builtins._ = res
return to_js([res], depth=1)
await_fut
`,
{globals: namespace},
);
namespace.destroy();

const echo = (msg, ...opts) => {
return term.echo(
msg
.replaceAll("]]", "&rsqb;&rsqb;")
.replaceAll("[[", "&lsqb;&lsqb;"),
...opts,
);
};

async function lock() {
let resolve;
const ready = term.ready;
term.ready = new Promise((res) => (resolve = res));
await ready;
return resolve;
}

const ps1 = ">>> ";
const ps2 = "... ";

async function interpreter(command, term: JQueryTerminal) {
const unlock = await lock();
term.pause();
// multiline should be split (useful when pasting)
for (const c of command.split("\n")) {
const escaped = c.replaceAll(/\u00a0/g, " ");
const fut = pyconsole.push(escaped);
term.set_prompt(fut.syntax_check === "incomplete" ? ps2 : ps1);
switch (fut.syntax_check) {
case "syntax-error":
term.error(fut.formatted_error.trimEnd());
continue;
case "incomplete":
continue;
case "complete":
break;
default:
throw new Error(`Unexpected type ${ty}`);
}
// In JavaScript, await automatically also awaits any results of
// awaits, so if an async function returns a future, it will await
// the inner future too. This is not what we want so we
// temporarily put it into a list to protect it.
const wrapped = await_fut(fut);
// complete case, get result / error and print it.
try {
const [value] = await wrapped;
if (value !== undefined) {
echo(
repr_shorten.callKwargs(value, {
separator: "\n<long output truncated>\n",
}),
);
}
if (value instanceof pyodide.ffi.PyProxy) {
value.destroy();
}
} catch (e) {
if (e.constructor.name === "PythonError") {
const message = fut.formatted_error || e.message;
term.error(message.trimEnd());
} else {
throw e;
}
} finally {
fut.destroy();
wrapped.destroy();
}
}
term.resume();
await sleep(10);
unlock();
}

pyconsole.stdout_callback = (s) => echo(s, { newline: false });
pyconsole.stderr_callback = (s) => {
term.error(s.trimEnd());
};

return interpreter;
}

export const PlaygroundTerminal: React.FC = () => {
const terminalRef = useRef(null);

const {state} = useEnvironmentSetup();
const {state, pyodide} = useEnvironmentSetup();

const setupComplete = useRef<boolean>(false);

useEffect(() => {
terminalRef.current.echo("Setting up environment");
terminalRef.current.freeze();
terminalRef.current.echo("Setting up environment...");
terminalRef.current.freeze(true);
terminalRef.current.setPrompt("");
}, [])

useEffect(() => {
if (state.pyodideStatus !== "done unpacking") {
if (state.environmentStatus === EnvironmentStatus.WaitBitbakeOrPyodide) {
let s = "";
switch (state.pyodideStatus) {
case PyodideStatus.Idle:
s = "idle";
break;
case PyodideStatus.Fetching:
s = "fetching...";
break;
case PyodideStatus.Loading:
s = "loading...";
break;
case PyodideStatus.Done:
s = "done!";
break;
case PyodideStatus.Inactive:
s = "inactive";
break;
}

terminalRef.current.setPrompt(
`Downloading bitbake: ${progress(state.bitbakeProgress, 80)}%\nPyodide: ${state.pyodideStatus}`
`Downloading BitBake: ${progress(state.bitbakeProgress, 30)}%\nPyodide: ${s}`
)
} else {
terminalRef.current.setPrompt(
`Done unpacking BitBake`
)
}
}, [state]);
switch (state.environmentStatus) {
case EnvironmentStatus.UnpackingBitbake:
terminalRef.current.setPrompt(
`Unpacking BitBake...`
)
break;
case EnvironmentStatus.LoadingSqlite3:
terminalRef.current.setPrompt(
`Loading sqlite3...`
)
break;
case EnvironmentStatus.Configuring:
terminalRef.current.setPrompt(
`Installing import hooks...`
)
break;
case EnvironmentStatus.ImportingBitbake:
terminalRef.current.setPrompt(
`Importing BitBake...`
)
break;
case EnvironmentStatus.Ready:
if (!setupComplete.current) {
setupComplete.current = true;

const interpreter = (command, term) => {
terminalRef.current.setInterpreter(create_interpreter(pyodide, terminalRef.current));

};
terminalRef.current.echo("Ready :)\n");
terminalRef.current.setPrompt(">>> ");
terminalRef.current.freeze(false);
}
break;
}
}
}, [pyodide, state]);

return (<JQueryTerminal interpreter={interpreter} ref={terminalRef}/>)
return (<JQueryTerminal ref={terminalRef}/>)
}
70 changes: 46 additions & 24 deletions src/hooks/useEnvironmentSetup.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import {useImmerReducer} from "use-immer";
import {usePyodide} from "./usePyodide";
import {useEffect} from "react";
import {PyodideStatus, usePyodide} from "./usePyodide";
import {useEffect, useRef} from "react";
import {useSWRProgress} from "./useSWRProgress";

export enum EnvironmentStatus {
WaitBitbakeOrPyodide,
LoadingSqlite3,
UnpackingBitbake,
Configuring,
ImportingBitbake,
Ready,
}

enum InternalStatus {
NotRun,
Running,
Done
}

const initialEnvironmentState = {
pyodideStatus: "idle",
environmentStatus: EnvironmentStatus.WaitBitbakeOrPyodide,
pyodideStatus: PyodideStatus.Idle,
bitbakeProgress: 0
};

Expand All @@ -18,6 +32,9 @@ function reducer(draft, action) {
case "bitbakeProgressChanged":
draft.bitbakeProgress = action.bitbakeProgress;
return;
case "environmentStatusChanged":
draft.environmentStatus = action.environmentStatus;
break;
}
}

Expand All @@ -33,19 +50,21 @@ export const useEnvironmentSetup = () => {

useEffect(() => {
dispatch({type: "bitbakeProgressChanged", bitbakeProgress: progress});
}, [progress, dispatch]);
}, [dispatch, progress]);

const effectStatus = useRef<InternalStatus>(InternalStatus.NotRun);

useEffect(() => {
if (pyodide && data) {
dispatch({type: "pyodideStatusChanged", pyodideStatus: "Loading sqlite3"});
if (pyodideStatus === PyodideStatus.Done && progress === 100 && effectStatus.current === InternalStatus.NotRun) {
effectStatus.current = InternalStatus.Running;
const f = async() => {
dispatch({type: "environmentStatusChanged", environmentStatus: EnvironmentStatus.LoadingSqlite3});
await pyodide.loadPackage("sqlite3");
dispatch({type: "pyodideStatusChanged", pyodideStatus: "done sqlite3"});
dispatch({type: "pyodideStatusChanged", pyodideStatus: "Unpacking..."});
dispatch({type: "environmentStatusChanged", pyodideStatus: EnvironmentStatus.UnpackingBitbake});
pyodide.unpackArchive(data, "zip", {
extractDir: "bb"
});
dispatch({type: "pyodideStatusChanged", pyodideStatus: "done unpacking"});
dispatch({type: "environmentStatusChanged", pyodideStatus: EnvironmentStatus.Configuring});

pyodide.runPython(`
import os.path
Expand Down Expand Up @@ -157,31 +176,34 @@ sys.meta_path.append(BuiltinImporterShim())
print(sys.meta_path)
`)
const file = pyodide.FS.readdir("./bb");
console.log(file);
// const file = pyodide.FS.readdir("./bb");
// console.log(file);

dispatch({type: "environmentStatusChanged", environmentStatus: EnvironmentStatus.ImportingBitbake});
pyodide.runPython(`
import sys
sys.path.insert(0, "./bb/bitbake-2.8.0/lib/")
from bb.data_smart import DataSmart
`)

const DataSmart = pyodide.globals.get('DataSmart');
const d = DataSmart();

d.setVar("A", "B");
d.setVar("A:test", "C");
d.setVar("OVERRIDES", "test");
d.setVarFlag("A", "p", "OK");

console.log(d.getVar("A"));

DataSmart.destroy();
dispatch({type: "environmentStatusChanged", environmentStatus: EnvironmentStatus.Ready});
// const DataSmart = pyodide.globals.get('DataSmart');
// const d = DataSmart();
//
// d.setVar("A", "B");
// d.setVar("A:test", "C");
// d.setVar("OVERRIDES", "test");
// d.setVarFlag("A", "p", "OK");
//
// console.log(d.getVar("A"));
//
// DataSmart.destroy();
}

f();
effectStatus.current = InternalStatus.Done;
}
}, [data, dispatch, pyodide]);
}, [data, dispatch, progress, pyodide, pyodideStatus]);

return {state};
return {state, pyodide};
};
20 changes: 14 additions & 6 deletions src/hooks/usePyodide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@ import {PyodideInterface} from "pyodide";

let cachedInstance: PyodideInterface = null;

export const usePyodide: () => { pyodide: PyodideInterface; status: string } = () => {
export enum PyodideStatus {
Idle,
Fetching,
Loading,
Done,
Inactive,
}

export const usePyodide: () => { pyodide: PyodideInterface; status: PyodideStatus } = () => {
const [pyodide, setPyodide] = useState<PyodideInterface>(null);
const [status, setStatus] = useState<string>('idle');
const [status, setStatus] = useState<PyodideStatus>(PyodideStatus.Idle);

useEffect(() => {
let isActive = true;

const loadPyodide = async () => {
if (!cachedInstance) {
setStatus("importing");
setStatus(PyodideStatus.Fetching);
const { loadPyodide: loadPyodideModule } = await import("https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.mjs");
setStatus("loading");
setStatus(PyodideStatus.Loading);
cachedInstance = await loadPyodideModule();
setStatus("done");
setStatus(PyodideStatus.Done);
}
if (isActive) {
setPyodide(cachedInstance);
Expand All @@ -27,7 +35,7 @@ export const usePyodide: () => { pyodide: PyodideInterface; status: string } = (
loadPyodide();

return () => {
setStatus("inactive");
setStatus(PyodideStatus.Inactive);
isActive = false;
};
}, []);
Expand Down

0 comments on commit 185237d

Please sign in to comment.