Skip to content

Commit

Permalink
feat(web/demo): make demo page look nice
Browse files Browse the repository at this point in the history
  • Loading branch information
sergey-melnychuk committed Sep 27, 2024
1 parent def9de8 commit 072b2f5
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 149 deletions.
268 changes: 152 additions & 116 deletions web/app.js
Original file line number Diff line number Diff line change
@@ -1,132 +1,168 @@
var ready;
var id = 0;

const worker = new Worker(new URL('./wrk.js', import.meta.url), { type: 'module' });
worker.onmessage = event => {
if (!ready) {
if (event.data === 'OK') {
dump('log', 'worker ready');
ready = true;
set_status(event.data);
} else {
dump('log', event.data, 'error');
set_status(event.data);
document.addEventListener("DOMContentLoaded", () => {
const chatLog = document.getElementById("chat-log");
const input = document.getElementById("json-input");
const sendBtn = document.getElementById("send-btn");
const templateBtns = document.querySelectorAll('.template-btn');
const alchemyKeyInput = document.getElementById("alchemy-key");
const initBtn = document.getElementById("init-btn");
const statusSpan = document.getElementById("status");
let messageId = 1;

const statusIcons = {
unknown: '❓',
pending: '⏳',
ready: '✅',
error: '❌'
};

var ready;
const worker = new Worker(new URL('./wrk.js', import.meta.url), { type: 'module' });
worker.onmessage = event => {
if (!ready) {
if (event.data === 'OK') {
ready = true;
statusSpan.innerText = statusIcons.ready;
} else {
console.error(event.data);
statusSpan.innerText = statusIcons.error;
}
return;
}
return;
}

try {
let json = JSON.parse(event.data);
let pretty = JSON.stringify(json, null, 2);
if (json.hasOwnProperty('error')) {
dump('log', '<<< ' + pretty, 'error');
} else {
dump('log', '<<< ' + pretty);
try {
let json = JSON.parse(event.data);
let responseContent = document.getElementById(json.id);
delete json.id;
let response = formatJSON(JSON.stringify(json));
responseContent.innerHTML = response;

if (json.hasOwnProperty('error')) {
console.error(json['error']);
responseContent.parentElement.setAttribute("style", "border-left-color:#FF0000");
}
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
dump('log', '[invalid JSON] <<< ' + event.data, 'error');
};

worker.onerror = error => {
console.error(error);
}
};
worker.onerror = error => {
dump('log', error, 'error');
}

const request = {
"calldata": [],
"contract_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"entry_point_selector": "0x361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60"
};

function post(message) {
message.id = id;
let payload = JSON.stringify(message, null, 2);
get('txt').value = payload;
}

function dump(id, text, style) {
let div = document.getElementById(id);
let p = document.createElement('pre');
if (style != undefined) {
p.className = style;

function sendMessage(userMessage) {
if (!ready || userMessage.trim() === "") return;

addMessagePair(userMessage, messageId);

let message = appendId(userMessage, messageId);
worker.postMessage(message);

messageId++;
input.value = '';
}
if (style === 'error') {
console.error(text);
} else {
console.log(text);

function appendId(message, id) {
let object = JSON.parse(message);
object.id = id;
return JSON.stringify(object);
}
p.innerText = text;
div.appendChild(p);
}

function get(id) {
return document.getElementById(id);
}

function run() {
var key = get('key');
var setup = get('setup');
setup.onclick = () => {
const config = JSON.stringify({
network: "mainnet",
ethereum_url: `http://127.0.0.1:3000/eth-mainnet.g.alchemy.com/v2/${key.value}`,
starknet_url: `http://127.0.0.1:3000/starknet-mainnet.g.alchemy.com/v2/${key.value}`
});
worker.postMessage(config);
set_status('wait');

function addMessagePair(userMessage, id) {
const messagePairDiv = document.createElement("div");
messagePairDiv.classList.add("message-pair");

const requestMessage = createMessageDiv(formatJSON(userMessage), id, "request");
const responseMessage = createMessageDiv(statusIcons.pending, id, "response");
messagePairDiv.appendChild(requestMessage);
messagePairDiv.appendChild(responseMessage);
chatLog.prepend(messagePairDiv);
}

var state = get('get');
state.onclick = () => {
post({"state": {}});
};
function createMessageDiv(messageContent, id, type) {
const messageDiv = document.createElement("div");
messageDiv.classList.add("message", type);

var exe = get('exe');
exe.onclick = () => {
post({"execute": request});
};
const content = document.createElement("div");
if (type === "response") {
content.id = id;
}
content.classList.add("content");
content.innerHTML = messageContent;

get('clear').onclick = () => {
let log = get('log');
log.replaceChildren();
};
const messageIdDiv = document.createElement("div");
messageIdDiv.classList.add("id");
messageIdDiv.textContent = `#${id}`;

var run = get('run');
run.disabled = true;
run.onclick = () => {
let payload = get('txt').value;
if (!ready) {
throw new Error('worker not ready');
return;
messageDiv.appendChild(content);
messageDiv.appendChild(messageIdDiv);

return messageDiv;
}

sendBtn.addEventListener("click", () => sendMessage(input.value));
input.addEventListener("keypress", (e) => {
if (e.key === "Enter" && e.shiftKey) {
sendMessage(input.value);
e.preventDefault(); // Prevents default Enter behavior (add new line)
}
});

templateBtns.forEach(btn => {
btn.addEventListener('click', () => {
input.value = JSON.stringify(JSON.parse(btn.dataset.template), null, 2);
});
});

function formatJSON(jsonString) {
try {
const obj = JSON.parse(jsonString);
return syntaxHighlight(JSON.stringify(obj, null, 2));
} catch (e) {
return jsonString; // Return input string if not a valid JSON
}
dump('log', '>>> ' + payload);
worker.postMessage(payload);
id += 1;
}

get('txt').value = '';
}

function set_status(message) {
var status = get('status');
if (message === 'OK') {
status.innerText = 'READY';
status.classList.remove('status-wait');
status.classList.add('status-ready');
get('setup').disabled = true;
get('run').disabled = false;
} else if (message === 'wait') {
status.innerText = 'WAIT';
status.classList.add('status-wait');
get('setup').disabled = true;
get('run').disabled = true;
} else {
status.innerText = 'ERROR';
status.classList.remove('status-wait');
status.classList.add('status-error');
get('setup').disabled = false;
get('run').disabled = true;
function syntaxHighlight(json) {
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\d+)/g, function (match) {
let cls = 'json-value';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'json-key';
} else {
cls = 'json-string';
}
} else if (/true|false/.test(match)) {
cls = 'json-boolean';
}
return `<span class="${cls}">${match}</span>`;
});
}
}

run();
input.addEventListener('input', function () {
try {
const formattedJSON = JSON.stringify(JSON.parse(input.value), null, 2);
input.value = formattedJSON;
} catch (e) {
// ignore if the input is not a valid JSON yet
}
});

initBtn.addEventListener("click", () => {
if (ready) {
return;
}
const alchemyKey = alchemyKeyInput.value;
if (!alchemyKey) {
console.log("Alchemy key is empty");
return;
}
const config = JSON.stringify({
network: "mainnet",
ethereum_url: `http://127.0.0.1:3000/eth-mainnet.g.alchemy.com/v2/${alchemyKey}`,
starknet_url: `http://127.0.0.1:3000/starknet-mainnet.g.alchemy.com/v2/${alchemyKey}`
});
worker.postMessage(config);
statusSpan.innerText = statusIcons.pending;
});
});
48 changes: 27 additions & 21 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,36 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Beerus WebAssembly demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css" />
<script type="module" src="app.js"></script>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Beerus</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Beerus WebAssembly</h2>
<div>
<span>Alchemy key: </span>
<input id="key" type="password" value="nLWc0kd1-CBFNJ57gvB_lVcQpSYCGZS1" />
<button id="setup">Setup</button>
<span>Status: </span><span id="status">none</span>
</div>
<hr />
<div>
<button id="get">Get State</button>
<button id="exe">Make Call</button>
<div class="container">
<div class="alchemy-section">
<div><h2>Beerus</h2></div>
<div class="alchemy-wrapper">
<input type="password" id="alchemy-key" placeholder="Enter Alchemy key" value="nLWc0kd1-CBFNJ57gvB_lVcQpSYCGZS1">
<button id="init-btn">Init</button>
<button class="template-btn">Status: <span id="status"></span></button>
<button class="template-btn">&#9432;</button>
</div>
</div>
<div class="chat-log" id="chat-log"></div>
<div class="input-section">
<div class="input-wrapper">
<textarea id="json-input" placeholder="Type JSON request here..." rows="5"></textarea>
</div>
<div class="send-container">
<button id="send-btn">Send</button>
<div class="template-buttons">
<button class="template-btn" data-template='{"execute": {"calldata":[],"contract_address":"0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","entry_point_selector": "0x361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60"}}'>Make Call</button>
<button class="template-btn" data-template='{"state": {}}'>Get State</button>
</div>
</div>
</div>
</div>
<div>
<textarea id="txt" rows="9" cols="100"></textarea>
<button id="run">Run</button>
</div>
<hr />
<button id="clear">Clear</button>
<div id="log"></div>
<script type="module" src="app.js"></script>
</body>
</html>
Loading

0 comments on commit 072b2f5

Please sign in to comment.