notebook example inspired by the now abandoned alpha.iodide.io, wasm + rustpython

This commit is contained in:
Mireille Raad committed 2020-09-14 21:45:45 -04:00
1 parent b569ccf240
commit 6b8aea057d
5 files changed
+392

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
{
"name": "app",
"version": "1.0.0",
"description": "Bindings to the RustPython library for WebAssembly",
"main": "index.js",
"dependencies": {
"codemirror": "^5.42.0",
"local-echo": "^0.2.0",
"xterm": "^3.8.0"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.1",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"raw-loader": "^4.0.0",
"serve": "^11.0.2",
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
},
"scripts": {
"dev": "webpack-dev-server -d",
"build": "webpack",
"dist": "webpack --mode production",
"test": "webpack --mode production && cd ../tests && pipenv run pytest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/RustPython/RustPython.git"
},
"author": "Ryan Liddle",
"license": "MIT",
"bugs": {
"url": "https://github.com/RustPython/RustPython/issues"
},
"homepage": "https://github.com/RustPython/RustPython#readme"
}
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link defer async href="https://fonts.googleapis.com/css?family=Fira+Sans|Sen:800&display=swap" rel="stylesheet">
<title>🐍 RustPython Notebook</title>
</head>
<body>
<!-- navigation bar -->
<div class="nav-bar">
<div>
<button id="run-btn">run notebook &#9658; </button>
<button id="snippet-btn" class="secondary-btn mr-1">import code</button>
</div>
<div class="header">RustPython 🐍 😱 🤘</div>
<div><a href="https://github.com/RustPython/">github</a></div>
</div>
<!-- user interface elements for importing code -->
<div id="url-container" class="d-none">
<div><label for="url">public url: (uses github api file url for now)</label></div>
<div>
<input type="url" name="url" id="snippet-url">
</div>
<div>
<button id="fetch-code" class="secondary-btn">fetch</button>
</div>
</div>
<!-- code editor and output display -->
<!-- split view -->
<div class="split-view full-height">
<textarea id="code"></textarea>
<div id="console"></div>
</div>
<!-- errors and keyboard shortcuts -->
<div class="split-view">
<div>
<mark>errors</mark>
<div id="error"></div>
</div>
<div class="text-small mt-1">
<mark>Keyboard Shortcuts:</mark>
<div>
<div>Run code: 'Ctrl-Enter' or 'Cmd-Enter'</div>
<div>Decrease Indent: 'Shift-Tab' </div>
<div>Toggle comment: 'Ctrl-/' or 'Cmd-/'</div>
</div>
</div>
</div>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
import './style.css';
import CodeMirror from 'codemirror';
import 'codemirror/mode/python/python';
import 'codemirror/addon/comment/comment';
import 'codemirror/lib/codemirror.css';
let rp;
// UI elements
const consoleElement = document.getElementById('console');
const errorElement = document.getElementById('error');
const fetchbtnElement = document.getElementById("fetch-code");
const urlConainerElement = document.getElementById('url-container');
// A dependency graph that contains any wasm must be imported asynchronously.
import('rustpython')
.then(rustpy => {
rp = rustpy;
// so people can play around with it
window.rp = rustpy;
onReady();
})
.catch(e => {
console.error('Error importing `rustpython`:', e);
document.getElementById('error').textContent = e;
});
// Code Mirror code editor
const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
extraKeys: {
'Ctrl-Enter': runCodeFromTextarea,
'Cmd-Enter': runCodeFromTextarea,
'Shift-Tab': 'indentLess',
'Ctrl-/': 'toggleComment',
'Cmd-/': 'toggleComment',
Tab: editor => {
var spaces = Array(editor.getOption('indentUnit') + 1).join(' ');
editor.replaceSelection(spaces);
}
},
lineNumbers: true,
mode: 'text/x-python',
indentUnit: 4,
autofocus: true
});
// Runs the code the the code editor
function runCodeFromTextarea() {
// Clean the console and errors
consoleElement.innerHTML = '';
errorElement.textContent = '';
const code = editor.getValue();
try {
rp.pyExec(code, {
stdout: output => {
consoleElement.innerHTML += output;
}
});
} catch (err) {
if (err instanceof WebAssembly.RuntimeError) {
err = window.__RUSTPYTHON_ERROR || err;
}
errorElement.textContent = err;
console.error(err);
}
}
function onReady() {
// snippets.addEventListener('change', updateSnippet);
document
.getElementById('run-btn')
.addEventListener('click', runCodeFromTextarea);
// so that the test knows that we're ready
const readyElement = document.createElement('div');
readyElement.id = 'rp_loaded';
document.head.appendChild(readyElement);
}
// when clicking the import code button
// show a UI with a url input + fetch button
// only accepts api.github.com urls (for now)
// add another function to parse a regular url
fetchbtnElement.addEventListener("click", function () {
// https://developer.github.com/v3/repos/contents/#get-repository-content
// Format:
// https://api.github.com/repos/username/reponame/contents/filename.py
let url = document
.getElementById('snippet-url')
.value;
// minimal js fetch code
// needs better error handling
fetch(url)
.then(res => res.json())
.then(data => {
// The Python code is in data.content
// it is encoded with Base64. Use atob to decode it.
//https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob
var decodedData = atob(data.content);
// set the value of the code editor
editor.setValue(decodedData);
urlConainerElement.classList.add("d-none");
}).catch(err => {
document
.getElementById("errors")
.innerHTML = "Couldn't fetch code. Make sure the link is public."
});
});
document.getElementById("snippet-btn").addEventListener("click", function () {
urlConainerElement.classList.remove("d-none");
});
+110
View File
@@ -0,0 +1,110 @@
.header {
font-family: 'Sen', sans-serif;
;
}
.d-flex {
display: flex;
}
.d-none {
display: none;
}
.mr-1 {
margin-right: 1rem;
}
.mt-1 {
margin-top: 1rem;
}
.text-small {
font-size: 0.75rem;
}
mark {
background-color: #000;
color: #fff;
font-size: 0.75rem;
}
.nav-bar {
border-bottom: 2px solid #F74C00;
display: flex;
justify-content: space-between;
position: sticky;
top: 0px;
background-color: #fff;
height: 20px;
z-index: 99;
}
.split-view {
display: flex;
justify-content: space-between;
}
.split-view div {
flex-basis: 50%;
}
.full-height {
height: calc(90vh - 60px);
}
#console {
padding: 1rem;
}
.CodeMirror {
height: 90% !important;
border-right: 1px solid gray;
}
#run-btn,
.secondary-btn {
border: 0px;
color: white;
cursor: pointer;
}
#run-btn {
background-color: #F74C00;
}
input[type="url"] {
border: 1px solid black;
border-radius: 0px;
width: 55%;
margin: 5px 0px 5px 0px;
font-size: 0.7rem;
padding: 4px;
font-family: monospace;
}
.url-container {
padding: 10px;
background-color: white;
font-size: smaller;
}
.secondary-btn {
background-color: #000;
}
.secondary-btn.active,
.secondary-btn:hover {
background-color: #F74C00;
}
#error {
color: tomato;
margin-top: 10px;
font-family: monospace;
white-space: pre;
}
#code-wrapper {
position: relative;
}
+72
View File
@@ -0,0 +1,72 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path = require('path');
const fs = require('fs');
const interval = setInterval(() => console.log('keepalive'), 1000 * 60 * 5);
module.exports = (env = {}) => {
const config = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'index.js'
},
mode: 'development',
resolve: {
alias: {
rustpython: path.resolve(
__dirname,
env.rustpythonPkg || '../lib/pkg'
)
}
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.ejs',
templateParameters: {
snippets: fs
.readdirSync(path.join(__dirname, 'snippets'))
.map(filename =>
path.basename(filename, path.extname(filename))
),
defaultSnippetName: 'fibonacci',
defaultSnippet: fs.readFileSync(
path.join(__dirname, 'snippets/fibonacci.py')
)
}
}),
new MiniCssExtractPlugin({
filename: 'styles.css'
}),
{
apply(compiler) {
compiler.hooks.done.tap('clearInterval', () => {
clearInterval(interval);
});
}
}
]
};
if (!env.noWasmPack) {
config.plugins.push(
new WasmPackPlugin({
crateDirectory: path.join(__dirname, '../lib')
})
);
}
return config;
};