mirror of
https://github.com/extism/extism.git
synced 2026-04-23 03:00:11 -04:00
- Adds ability to define host functions when creating ExtismPlugins in the browser runtime - The API is a little simpler than the Rust runtime - Functions don't handle userdata, userdata should be captured by the function declaration
249 lines
7.3 KiB
HTML
249 lines
7.3 KiB
HTML
<html>
|
|
<head>
|
|
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
|
|
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
|
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
|
|
<style>
|
|
#main {
|
|
width: 100%;
|
|
}
|
|
.manifest {
|
|
display: flex; /* or inline-flex */
|
|
flex-direction: row;
|
|
flex-wrap: nowrap;
|
|
width: 100%;
|
|
}
|
|
.urlInput {
|
|
width: 600px;
|
|
}
|
|
.funcName {
|
|
width: 150px;
|
|
}
|
|
.textAreas {
|
|
display: flex; /* or inline-flex */
|
|
flex-direction: row;
|
|
flex-wrap: nowrap;
|
|
width: 100%;
|
|
height: 300px;
|
|
}
|
|
.inputBox {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.inputBox > textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.outputBox {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.outputBox > textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.space {
|
|
height: 80px;
|
|
}
|
|
.dragAreas {
|
|
display: flex; /* or inline-flex */
|
|
flex-direction: row;
|
|
flex-wrap: nowrap;
|
|
width: 100%;
|
|
height: 200px;
|
|
}
|
|
.dragInput {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-style: dotted;
|
|
border-color: #000;
|
|
}
|
|
.dragOutput {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.dropZone {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.outputImage {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
</style>
|
|
|
|
<script type="text/babel">
|
|
function getBase64(file, cb) {
|
|
var reader = new FileReader();
|
|
reader.readAsDataURL(file);
|
|
reader.onload = function () {
|
|
cb(reader.result)
|
|
};
|
|
reader.onerror = function (error) {
|
|
console.log("error")
|
|
};
|
|
}
|
|
|
|
function arrayTob64(buffer) {
|
|
var binary = '';
|
|
var bytes = [].slice.call(buffer);
|
|
bytes.forEach((b) => binary += String.fromCharCode(b));
|
|
return window.btoa(binary);
|
|
}
|
|
|
|
|
|
class App extends React.Component {
|
|
state = {
|
|
url: "https://raw.githubusercontent.com/extism/extism/main/wasm/code.wasm",
|
|
input: new Uint8Array(),
|
|
output: new Uint8Array(),
|
|
func_name: "count_vowels",
|
|
functions: []
|
|
}
|
|
|
|
async loadFunctions(url) {
|
|
let helloWorld = function(index){
|
|
console.log("Hello, " + this.allocator.getString(index));
|
|
return index;
|
|
};
|
|
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] }, {"hello_world": helloWorld});
|
|
let functions = Object.keys(await plugin.getExports())
|
|
console.log("funcs ", functions)
|
|
this.setState({functions})
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.loadFunctions(this.state.url)
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props)
|
|
this.extismContext = props.extismContext
|
|
}
|
|
|
|
handleInputChange(e) {
|
|
e.preventDefault();
|
|
this.setState({ [e.target.name]: e.target.value })
|
|
if (e.target.name === "url") {
|
|
this.loadFunctions(e.target.value)
|
|
}
|
|
}
|
|
|
|
onInputKeyPress(e) {
|
|
if (e.keyCode == 13 && e.shiftKey == true) {
|
|
e.preventDefault()
|
|
this.handleOnRun()
|
|
}
|
|
}
|
|
|
|
async handleOnRun(e) {
|
|
e && e.preventDefault && e.preventDefault();
|
|
let helloWorld = function(index){
|
|
console.log("Hello, " + this.allocator.getString(index));
|
|
return index;
|
|
};
|
|
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] }, {
|
|
"hello_world": helloWorld
|
|
});
|
|
let result = await plugin.call(this.state.func_name, this.state.input)
|
|
let output = result
|
|
this.setState({output})
|
|
}
|
|
|
|
nop = (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
};
|
|
handleDrop = e => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
let files = [...e.dataTransfer.files];
|
|
if (files && files.length == 1) {
|
|
let file = files[0]
|
|
console.log(file)
|
|
file.arrayBuffer().then(b => {
|
|
this.setState({input: new Uint8Array(b)})
|
|
this.handleOnRun()
|
|
})
|
|
} else {
|
|
throw Error("Only one file please")
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const funcOptions = this.state.functions.map(f => <option value={f}>{f}</option>)
|
|
let image = null
|
|
if (this.state.output) {
|
|
image = <img src={`data:image/png;base64,${arrayTob64(this.state.output)}`}/>
|
|
}
|
|
|
|
return <div className="app">
|
|
<div className="manifest">
|
|
<div>
|
|
<label>WASM Url: </label>
|
|
<input type="text" name="url" className="urlInput" value={this.state.url} onChange={this.handleInputChange.bind(this)} />
|
|
</div>
|
|
<div>
|
|
<label>Function: </label>
|
|
<select type="text" name="func_name" className="funcName" value={this.state.func_name} onChange={this.handleInputChange.bind(this)}>
|
|
{funcOptions}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button onClick={this.handleOnRun.bind(this)}>Run</button>
|
|
</div>
|
|
</div>
|
|
<div className="textAreas">
|
|
<div className="inputBox">
|
|
<h3>Text Input</h3>
|
|
<textarea name="input" value={this.state.input} onChange={this.handleInputChange.bind(this)} onKeyDown={this.onInputKeyPress.bind(this)}></textarea>
|
|
</div>
|
|
<div className="outputBox">
|
|
<h3>Text Output</h3>
|
|
<textarea name="output" value={new TextDecoder().decode(this.state.output)} ></textarea>
|
|
</div>
|
|
</div>
|
|
<div className="space" />
|
|
<div className="dragAreas">
|
|
<div className="dragInput">
|
|
<h3>Image Input</h3>
|
|
<div className="dropZone"
|
|
onDrop={this.handleDrop.bind(this)}
|
|
onDragOver={this.nop.bind(this)}
|
|
onDragEnter={this.nop.bind(this)}
|
|
onDragLeave={this.nop.bind(this)}
|
|
>
|
|
</div>
|
|
</div>
|
|
<div className="dragOutput">
|
|
<h3>Image Output</h3>
|
|
<div className="outputImage">
|
|
{image}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
window.App = App
|
|
</script>
|
|
|
|
<script type="module">
|
|
import {ExtismContext} from './dist/index.esm.js'
|
|
const e = React.createElement;
|
|
|
|
window.onload = () => {
|
|
const domContainer = document.getElementById('main');
|
|
console.log(domContainer)
|
|
const root = ReactDOM.createRoot(domContainer);
|
|
const extismContext = new ExtismContext()
|
|
root.render(e(App, {extismContext}));
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="main"></div>
|
|
</body>
|
|
</html>
|