add http-client

This commit is contained in:
Sebastian Hugentobler 2022-03-25 19:18:55 +01:00
parent ac2904588c
commit 8bcd555d71
36 changed files with 1978 additions and 49 deletions

17
http-client/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "http-client"
version = "0.1.0"
edition = "2021"
[dependencies]
bank = { path = "../bank" }
http-lib = { path = "../http-lib" }
gloo-console = "0.2.1"
js-sys = "0.3.56"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
wasm-bindgen = "0.2.79"
wasm-bindgen-futures = "0.4.29"
web-sys = { version = "0.3.56", features = ["Headers", "HtmlSelectElement", "Request", "RequestInit", "RequestMode", "Response", "Window"] }
yew = "0.19.3"
yew-agent = "0.1.0"

View file

@ -0,0 +1,588 @@
let wasm;
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
let heap_next = heap.length;
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
function getObject(idx) { return heap[idx]; }
let WASM_VECTOR_LEN = 0;
let cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
let cachegetFloat64Memory0 = null;
function getFloat64Memory0() {
if (cachegetFloat64Memory0 === null || cachegetFloat64Memory0.buffer !== wasm.memory.buffer) {
cachegetFloat64Memory0 = new Float64Array(wasm.memory.buffer);
}
return cachegetFloat64Memory0;
}
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function makeClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
try {
return f(state.a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);
state.a = 0;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_22(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0b1695f7853f7e5d(arg0, arg1, addHeapObject(arg2));
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_25(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0235f8df8778306b(arg0, arg1, addHeapObject(arg2));
}
let cachegetUint32Memory0 = null;
function getUint32Memory0() {
if (cachegetUint32Memory0 === null || cachegetUint32Memory0.buffer !== wasm.memory.buffer) {
cachegetUint32Memory0 = new Uint32Array(wasm.memory.buffer);
}
return cachegetUint32Memory0;
}
function getArrayJsValueFromWasm0(ptr, len) {
const mem = getUint32Memory0();
const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
for (let i = 0; i < slice.length; i++) {
result.push(takeObject(slice[i]));
}
return result;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('index-57ce73b23c43b0f5_bg.wasm', import.meta.url);
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
var ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
var ret = typeof(obj) === 'string' ? obj : undefined;
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
var ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_number_new = function(arg0) {
var ret = arg0;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
const obj = getObject(arg1);
var ret = typeof(obj) === 'number' ? obj : undefined;
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) {
try {
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(arg0, arg1);
}
};
imports.wbg.__wbg_new_693216e109162396 = function() {
var ret = new Error();
return addHeapObject(ret);
};
imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) {
var ret = getObject(arg1).stack;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_warn_2aa0e7178e1d35f6 = function(arg0, arg1) {
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
wasm.__wbindgen_free(arg0, arg1 * 4);
console.warn(...v0);
};
imports.wbg.__wbg_instanceof_Window_434ce1849eb4e0fc = function(arg0) {
var ret = getObject(arg0) instanceof Window;
return ret;
};
imports.wbg.__wbg_document_5edd43643d1060d9 = function(arg0) {
var ret = getObject(arg0).document;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_fetch_427498e0ccea81f4 = function(arg0, arg1) {
var ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_body_7538539844356c1c = function(arg0) {
var ret = getObject(arg0).body;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_createElement_d017b8d2af99bab9 = function() { return handleError(function (arg0, arg1, arg2) {
var ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_createElementNS_fd4a7e49f74039e1 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
var ret = getObject(arg0).createElementNS(arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_createTextNode_39a0de25d14bcde5 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).createTextNode(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_value_d3a30bc2c7caf357 = function(arg0, arg1) {
var ret = getObject(arg1).value;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_setvalue_6a34bab301f38bf2 = function(arg0, arg1, arg2) {
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_target_e560052e31e4567c = function(arg0) {
var ret = getObject(arg0).target;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_cancelBubble_17d7988ab2fbe4c9 = function(arg0) {
var ret = getObject(arg0).cancelBubble;
return ret;
};
imports.wbg.__wbg_headers_1a60dec7fbd28a3b = function(arg0) {
var ret = getObject(arg0).headers;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithstrandinit_c07f0662ece15bc6 = function() { return handleError(function (arg0, arg1, arg2) {
var ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_setchecked_f6ead3490df88a7f = function(arg0, arg1) {
getObject(arg0).checked = arg1 !== 0;
};
imports.wbg.__wbg_value_fc1c354d1a0e9714 = function(arg0, arg1) {
var ret = getObject(arg1).value;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_setvalue_ce4a23f487065c07 = function(arg0, arg1, arg2) {
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_addEventListener_55682f77717d7665 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4));
}, arguments) };
imports.wbg.__wbg_parentElement_96e1e07348340043 = function(arg0) {
var ret = getObject(arg0).parentElement;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_lastChild_e2b014abab089e08 = function(arg0) {
var ret = getObject(arg0).lastChild;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_setnodeValue_f175b74a390f8fda = function(arg0, arg1, arg2) {
getObject(arg0).nodeValue = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_appendChild_3fe5090c665d3bb4 = function() { return handleError(function (arg0, arg1) {
var ret = getObject(arg0).appendChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_insertBefore_4f09909023feac91 = function() { return handleError(function (arg0, arg1, arg2) {
var ret = getObject(arg0).insertBefore(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_removeChild_f4a83c9698136bbb = function() { return handleError(function (arg0, arg1) {
var ret = getObject(arg0).removeChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_instanceof_Element_c9423704dd5d9b1d = function(arg0) {
var ret = getObject(arg0) instanceof Element;
return ret;
};
imports.wbg.__wbg_namespaceURI_e9a971e6c1ce68db = function(arg0, arg1) {
var ret = getObject(arg1).namespaceURI;
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_removeAttribute_1adaecf6b4d35a09 = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).removeAttribute(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_setAttribute_1776fcc9b98d464e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_set_f9448486a94c9aef = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_value_d4cea9e999ffb147 = function(arg0, arg1) {
var ret = getObject(arg1).value;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_instanceof_Response_ea36d565358a42f7 = function(arg0) {
var ret = getObject(arg0) instanceof Response;
return ret;
};
imports.wbg.__wbg_text_aeba5a5bbfef7f15 = function() { return handleError(function (arg0) {
var ret = getObject(arg0).text();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
var ret = false;
return ret;
};
imports.wbg.__wbg_newnoargs_f579424187aa1717 = function(arg0, arg1) {
var ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_call_89558c3e96703ca1 = function() { return handleError(function (arg0, arg1) {
var ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_valueOf_39e0d6bc7e4232b9 = function(arg0) {
var ret = getObject(arg0).valueOf();
return ret;
};
imports.wbg.__wbg_is_3d73f4d91adacc37 = function(arg0, arg1) {
var ret = Object.is(getObject(arg0), getObject(arg1));
return ret;
};
imports.wbg.__wbg_new_d3138911a89329b0 = function() {
var ret = new Object();
return addHeapObject(ret);
};
imports.wbg.__wbg_resolve_4f8f547f26b30b27 = function(arg0) {
var ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_a6860c82b90816ca = function(arg0, arg1) {
var ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_58a04e42527f52c6 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_globalThis_d61b1f48a57191ae = function() { return handleError(function () {
var ret = globalThis.globalThis;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_self_e23d74ae45fb17d1 = function() { return handleError(function () {
var ret = self.self;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_window_b4be7f48b24ac56e = function() { return handleError(function () {
var ret = window.window;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_global_e7669da72fd7f239 = function() { return handleError(function () {
var ret = global.global;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbindgen_is_undefined = function(arg0) {
var ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbg_get_8bbb82393651dd9c = function() { return handleError(function (arg0, arg1) {
var ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_set_c42875065132a932 = function() { return handleError(function (arg0, arg1, arg2) {
var ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
var ret = debugString(getObject(arg1));
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_closure_wrapper4145 = function(arg0, arg1, arg2) {
var ret = makeClosure(arg0, arg1, 178, __wbg_adapter_22);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper7730 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 194, __wbg_adapter_25);
return addHeapObject(ret);
};
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
const { instance, module } = await load(await input, imports);
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
wasm.__wbindgen_start();
return wasm;
}
export default init;

Binary file not shown.

34
http-client/dist/index.html vendored Normal file
View file

@ -0,0 +1,34 @@
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>Vesys Bank</title>
<link rel="stylesheet" href="/styles-3143ec7e42adb2c6.css">
<link rel="preload" href="/index-57ce73b23c43b0f5_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
<link rel="modulepreload" href="/index-57ce73b23c43b0f5.js"></head>
<body><script type="module">import init from '/index-57ce73b23c43b0f5.js';init('/index-57ce73b23c43b0f5_bg.wasm');</script><script>(function () {
var url = 'ws://' + window.location.host + '/_trunk/ws';
var poll_interval = 5000;
var reload_upon_connect = () => {
window.setTimeout(
() => {
// when we successfully reconnect, we'll force a
// reload (since we presumably lost connection to
// trunk due to it being killed, so it will have
// rebuilt on restart)
var ws = new WebSocket(url);
ws.onopen = () => window.location.reload();
ws.onclose = reload_upon_connect;
},
poll_interval);
};
var ws = new WebSocket(url);
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.reload) {
window.location.reload();
}
};
ws.onclose = reload_upon_connect;
})()
</script></body></html>

View file

@ -0,0 +1,123 @@
html, body {
font-style: normal;
font-family: monospace;
padding: 0;
margin: 0;
}
.account {
border-style: solid;
border-color: black;
}
.account__grid {
border-style: none;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.account__title {
background-color: black;
color: white;
padding: 3px 6px;
}
.account__label {
margin: 0 .5em 0 .5em;
font-weight: bold;
border-bottom-style: solid;
}
.account__amount {
grid-column: 1;
grid-row: 1;
display: flex;
flex-direction: column;
}
.account__accounts {
grid-column: 2 / 4;
grid-row: 1;
display: flex;
flex-direction: column;
}
.account__button {
color: black;
border-radius: 0;
border-color: black;
border-style: solid;
border-width: .2em;
padding: 1em;
margin: .5em;
background: linear-gradient(to top, darkgray, 20%, lightgray);
box-shadow: 3px 3px 3px black;
}
.account__button:hover:enabled {
background: linear-gradient(lightgray, darkgray);
}
.account__button:active:enabled {
transform: translateY(3px);
}
.account__button:disabled {
background: lightgray;
color: darkgray;
}
.account__input {
padding: 1em;
color: black;
margin: .5em;
background-color: #eeeeee;
border-radius: 0;
border-color: black;
border-style: solid;
border-width: .2em;
box-shadow: 3px 3px 3px black;
}
.accounts {
list-style-type: none;
padding: 0.5em;
border-style: solid;
border-color: black;
}
.accounts__item {
border-style: solid;
border-bottom-color: lightgray;
border-width: 0 0 0.1em 0;
}
.accounts__item-selected {
border-bottom-color: darkgray;
background-color: #eeeeee;
font-weight: bold;
}
.error {
background: red;
padding: 1em;
color: white;
}
.content {
padding: 1em;
min-width: 370px;
}
@media (max-width: 605px) {
.account__grid {
display: grid;
grid-template-columns: 1fr;
}
.account__accounts {
grid-column: 1;
grid-row: 2;
}
}

8
http-client/index.html Normal file
View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vesys Bank</title>
<link data-trunk rel="css" type="text/css" href="styles.css">
</head>
</html>

34
http-client/src/api.rs Normal file
View file

@ -0,0 +1,34 @@
use wasm_bindgen::JsValue;
use http_lib::json_account::JsonAccount;
use crate::client;
use crate::client::FetchError;
pub async fn fetch_account_nrs(host: &str) -> Result<Vec<String>, FetchError> {
match client::get_json(&format! {"{}/accounts", host}).await? {
None => Ok(vec![]),
Some(response) => {
let nrs: Vec<String> = serde_json::from_str(&response)?;
Ok(nrs)
}
}
}
pub async fn fetch_account(host: &str, nr: &str) -> Result<JsonAccount, FetchError> {
match client::get_json(&format! {"{}/accounts/{}", host, nr}).await? {
None => Err(FetchError {
err: JsValue::from_str("no such account"),
}),
Some(response) => {
let acc: JsonAccount = serde_json::from_str(&response)?;
Ok(acc)
}
}
}
pub async fn set_balance(host: &str, nr: &str, balance: f64) -> Result<(), FetchError> {
let data = JsValue::from_str(&format!("amount={}", balance));
client::put(&format! {"{}/accounts/{}", host, nr}, data).await?;
Ok(())
}

67
http-client/src/client.rs Normal file
View file

@ -0,0 +1,67 @@
use std::error::Error;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
#[derive(Debug, Clone, PartialEq)]
pub struct FetchError {
pub err: JsValue,
}
impl Display for FetchError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.err, f)
}
}
impl Error for FetchError {}
impl From<JsValue> for FetchError {
fn from(value: JsValue) -> Self {
Self { err: value }
}
}
impl From<serde_json::Error> for FetchError {
fn from(e: serde_json::Error) -> Self {
Self {
err: JsValue::from(e.to_string()),
}
}
}
async fn send_request(request: Request) -> Result<Response, FetchError> {
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into()?;
Ok(resp)
}
pub async fn get_json(url: &str) -> Result<Option<String>, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(url, &opts)?;
request.headers().set("Accept", "application/json")?;
let resp = self::send_request(request).await?;
let text = JsFuture::from(resp.text()?).await?;
Ok(text.as_string())
}
pub async fn put(url: &str, data: JsValue) -> Result<(), FetchError> {
let mut opts = RequestInit::new();
opts.method("PUT");
opts.mode(RequestMode::Cors);
opts.body(Some(&data));
let request = Request::new_with_str_and_init(url, &opts)?;
request.headers().set("Content-Type", "application/x-www-form-urlencoded")?;
self::send_request(request).await?;
Ok(())
}

View file

@ -0,0 +1,151 @@
use std::str::FromStr;
use web_sys::{HtmlInputElement, HtmlSelectElement};
use yew::{classes, html, Component, Context, Html, Properties, NodeRef};
use yew_agent::{Dispatched, Dispatcher};
use crate::event_bus::{EventBus, Request};
use crate::events::Event;
pub enum Msg {
AmountChanged,
Deposit,
Withdraw,
Transfer,
}
#[derive(Properties, PartialEq)]
pub struct AccountProps {
pub account_nrs: Vec<String>,
pub balance: f64,
pub nr: String,
pub owner: String,
}
pub struct Account {
amount_ref: NodeRef,
transfer_account_ref: NodeRef,
amount_valid: bool,
event_bus: Dispatcher<EventBus>,
}
impl Account {
fn amount(&self) -> f64 {
if let Some(amount_el) = self.amount_ref.cast::<HtmlInputElement>() {
f64::from_str(&amount_el.value()).unwrap_or_default()
} else { 0_f64 }
}
fn is_amount_valid(&self) -> bool {
self.amount() > 0_f64
}
fn selected_transfer_account(&self) -> Option<String> {
if let Some(transfer_account_el) = self.transfer_account_ref.cast::<HtmlSelectElement>() {
let value: String = transfer_account_el.value();
if value.is_empty() || value == "undefined" { None } else { Some(value) }
} else { None }
}
}
impl Component for Account {
type Message = Msg;
type Properties = AccountProps;
fn create(_: &Context<Self>) -> Self {
Self {
amount_ref: NodeRef::default(),
transfer_account_ref: NodeRef::default(),
amount_valid: false,
event_bus: EventBus::dispatcher(),
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::AmountChanged => {
let is_amount_valid = self.is_amount_valid();
let needs_redraw = is_amount_valid != self.amount_valid;
self.amount_valid = is_amount_valid;
needs_redraw
}
Msg::Deposit => {
let amount = self.amount() + ctx.props().balance;
self.event_bus.send(Request::EventBusMsg(Event::SetBalance(amount, ctx.props().nr.clone())));
false
}
Msg::Withdraw => {
let amount = ctx.props().balance - self.amount();
if amount > 0_f64 {
self.event_bus.send(Request::EventBusMsg(Event::SetBalance(amount, ctx.props().nr.clone())));
} else {
self.event_bus.send(Request::EventBusMsg(Event::ShowError("Balance can not be overdrawn".into())));
}
false
}
Msg::Transfer => {
let amount = ctx.props().balance - self.amount();
if amount > 0_f64 {
self.event_bus.send(Request::EventBusMsg(Event::SetBalance(amount, ctx.props().nr.clone())));
} else {
let transfer_nr = self.selected_transfer_account();
if let Some(transfer_nr) = transfer_nr {
self.event_bus.send(Request::EventBusMsg(Event::Transfer(amount, ctx.props().nr.clone(), transfer_nr)));
}
}
false
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onchange = ctx
.link()
.callback(|_| Msg::AmountChanged);
let on_deposit = ctx
.link()
.callback(|_| Msg::Deposit);
let on_withdraw = ctx
.link()
.callback(|_| Msg::Withdraw);
let on_transfer = ctx
.link()
.callback(|_| Msg::Transfer);
html! {
<>
<section class={classes!("account")}>
<fieldset class={classes!("account__grid")}>
<legend class={classes!("account__title")}>
{&ctx.props().owner} {""} {&ctx.props().balance}
</legend>
<div class={classes!("account__amount")}>
<label class={classes!("account__label")} for="amount">{"Amount"}</label>
<input {onchange} ref={self.amount_ref.clone()} class={classes!("account__input")} id="amount" type="number" step="0.01" min="0" placeholder="0.0" />
</div>
<div class={classes!("account__accounts")}>
<label class={classes!("account__label")} for="accounts">{"Remote Account"}</label>
<select ref={self.transfer_account_ref.clone()} class={classes!("account__input")} name="accounts" id="account-select">
{ for ctx.props().account_nrs.iter().filter(|e| {
ctx.props().nr != e.to_string()
}).map(|e| html!{ <option value={e.to_string()}>{e}</option> }) }
</select>
</div>
<button onclick={on_deposit} class={classes!("account__button")} disabled={!self.amount_valid}>{"deposit"}</button>
<button onclick={on_withdraw} class={classes!("account__button")} disabled={!self.amount_valid}>{"withdraw"}</button>
<button onclick={on_transfer} class={classes!("account__button")} disabled={!self.amount_valid}>{"transfer"}</button>
</fieldset>
</section>
</>
}
}
}

View file

@ -0,0 +1,73 @@
use crate::event_bus::{EventBus, Request};
use crate::events::Event;
use yew::{classes, html, Classes, Component, Context, Html, Properties};
use yew_agent::{Dispatched, Dispatcher};
pub enum Msg {
SelectAccountNr(String),
}
#[derive(Properties, PartialEq)]
pub struct AccountsProps {
pub account_nrs: Vec<String>,
pub selected_nr: String
}
pub struct Accounts {
event_bus: Dispatcher<EventBus>,
}
impl Component for Accounts {
type Message = Msg;
type Properties = AccountsProps;
fn create(_: &Context<Self>) -> Self {
Self {
event_bus: EventBus::dispatcher(),
}
}
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::SelectAccountNr(nr) => {
self.event_bus
.send(Request::EventBusMsg(Event::SelectAccountNr(nr)));
false
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<>
<ul class={classes!("accounts")}>
{ for ctx.props().account_nrs.iter().map(|e| self.account_entry(e, &ctx.props().selected_nr, ctx)) }
</ul>
</>
}
}
}
impl Accounts {
fn account_entry(
&self,
nr: &str,
selected_nr: &str,
ctx: &Context<Self>,
) -> Html {
let mut class = Classes::from("accounts__item");
if selected_nr == nr {
class.push("accounts__item-selected");
}
let nr = nr.to_string();
let account_nr = nr.clone();
let onclick = ctx
.link()
.callback(move |_| Msg::SelectAccountNr(nr.clone()));
html! {
<li {onclick} class={class}>{account_nr}</li>
}
}
}

View file

@ -0,0 +1,117 @@
use crate::components::account::Account;
use crate::components::accounts::Accounts;
use crate::event_bus::EventBus;
use crate::events::Event;
use crate::api;
use yew::{classes, html, Component, Context, Html};
use yew_agent::{Bridge, Bridged};
pub struct Main {
_subscriber: Box<dyn Bridge<EventBus>>,
error: Option<String>,
account_nrs: Vec<String>,
selected_balance: f64,
selected_owner: String,
selected_nr: String,
}
impl Main {
fn set_selected_account(&self, ctx: &Context<Self>, nr: String) {
ctx.link().send_future(async move {
match api::fetch_account("http://localhost:8000", &nr).await {
Err(e) => Event::ShowError(e.to_string()),
Ok(account) => Event::SetSelectedAccount(account),
}
});
}
}
impl Component for Main {
type Message = Event;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
Self {
error: None,
_subscriber: EventBus::bridge(ctx.link().callback(|x| x)),
account_nrs: vec![],
selected_balance: 0_f64,
selected_nr: "".into(),
selected_owner: "".into()
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Event::GetAccountNrs => {
ctx.link().send_future(async {
match api::fetch_account_nrs("http://localhost:8000").await {
Err(e) => Event::ShowError(e.to_string()),
Ok(nrs) => Event::SetAccountNrs(nrs),
}
});
false
}
Event::SetAccountNrs(nrs) => {
if self.account_nrs.is_empty() && !nrs.is_empty() {
let nr = nrs[0].clone();
self.set_selected_account(ctx, nr);
}
self.account_nrs = nrs;
true
}
Event::ShowError(error) => {
self.error = Some(error);
true
}
Event::SetSelectedAccount(account) => {
self.selected_balance = account.balance;
self.selected_owner = account.owner;
self.selected_nr = account.number;
true
}
Event::SelectAccountNr(nr) => {
self.set_selected_account(ctx, nr);
true
}
Event::SetBalance(balance, nr) => {
ctx.link().send_future(async move {
match api::set_balance("http://localhost:8000", &nr, balance).await {
Err(e) => Event::ShowError(e.to_string()),
Ok(_) => Event::SelectAccountNr(nr),
}
});
false
}
Event::Transfer(amount, from, to) => {
false
}
}
}
fn view(&self, _: &Context<Self>) -> Html {
html! {
<>
if let Some(error_msg) = &self.error {
<div class={classes!("error")}>{ error_msg }</div>
}
<main class={classes!("content")}>
<h1>
{"welcome to your vaults"}
</h1>
<Accounts account_nrs={self.account_nrs.clone()} selected_nr={self.selected_nr.clone()} />
<Account balance={self.selected_balance} owner={self.selected_owner.clone()} nr={self.selected_nr.clone()} account_nrs={self.account_nrs.clone()} />
</main>
</>
}
}
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
if first_render {
ctx.link().send_message(Event::GetAccountNrs);
}
}
}

View file

@ -0,0 +1,3 @@
pub mod account;
pub mod accounts;
pub mod main;

View file

@ -0,0 +1,48 @@
use crate::events::Event;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use yew_agent::{Agent, AgentLink, Context, HandlerId};
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
EventBusMsg(Event),
}
pub struct EventBus {
link: AgentLink<EventBus>,
subscribers: HashSet<HandlerId>,
}
impl Agent for EventBus {
type Reach = Context<Self>;
type Message = ();
type Input = Request;
type Output = Event;
fn create(link: AgentLink<Self>) -> Self {
Self {
link,
subscribers: HashSet::new(),
}
}
fn update(&mut self, _msg: Self::Message) {}
fn connected(&mut self, id: HandlerId) {
self.subscribers.insert(id);
}
fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) {
match msg {
Request::EventBusMsg(s) => {
for sub in self.subscribers.iter() {
self.link.respond(*sub, s.clone());
}
}
}
}
fn disconnected(&mut self, id: HandlerId) {
self.subscribers.remove(&id);
}
}

13
http-client/src/events.rs Normal file
View file

@ -0,0 +1,13 @@
use http_lib::json_account::JsonAccount;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Event {
GetAccountNrs,
SetAccountNrs(Vec<String>),
SelectAccountNr(String),
SetSelectedAccount(JsonAccount),
SetBalance(f64, String),
ShowError(String),
Transfer(f64, String, String),
}

11
http-client/src/main.rs Normal file
View file

@ -0,0 +1,11 @@
use crate::components::main::Main;
mod client;
mod components;
mod event_bus;
mod events;
mod api;
fn main() {
yew::start_app::<Main>();
}

123
http-client/styles.css Normal file
View file

@ -0,0 +1,123 @@
html, body {
font-style: normal;
font-family: monospace;
padding: 0;
margin: 0;
}
.account {
border-style: solid;
border-color: black;
}
.account__grid {
border-style: none;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.account__title {
background-color: black;
color: white;
padding: 3px 6px;
}
.account__label {
margin: 0 .5em 0 .5em;
font-weight: bold;
border-bottom-style: solid;
}
.account__amount {
grid-column: 1;
grid-row: 1;
display: flex;
flex-direction: column;
}
.account__accounts {
grid-column: 2 / 4;
grid-row: 1;
display: flex;
flex-direction: column;
}
.account__button {
color: black;
border-radius: 0;
border-color: black;
border-style: solid;
border-width: .2em;
padding: 1em;
margin: .5em;
background: linear-gradient(to top, darkgray, 20%, lightgray);
box-shadow: 3px 3px 3px black;
}
.account__button:hover:enabled {
background: linear-gradient(lightgray, darkgray);
}
.account__button:active:enabled {
transform: translateY(3px);
}
.account__button:disabled {
background: lightgray;
color: darkgray;
}
.account__input {
padding: 1em;
color: black;
margin: .5em;
background-color: #eeeeee;
border-radius: 0;
border-color: black;
border-style: solid;
border-width: .2em;
box-shadow: 3px 3px 3px black;
}
.accounts {
list-style-type: none;
padding: 0.5em;
border-style: solid;
border-color: black;
}
.accounts__item {
border-style: solid;
border-bottom-color: lightgray;
border-width: 0 0 0.1em 0;
}
.accounts__item-selected {
border-bottom-color: darkgray;
background-color: #eeeeee;
font-weight: bold;
}
.error {
background: red;
padding: 1em;
color: white;
}
.content {
padding: 1em;
min-width: 370px;
}
@media (max-width: 605px) {
.account__grid {
display: grid;
grid-template-columns: 1fr;
}
.account__accounts {
grid-column: 1;
grid-row: 2;
}
}