2024-02-01 15:03:33 +01:00
|
|
|
let wasmSize = null;
|
|
|
|
// See comment in dist/web/Dockerfile about imhex.wasm.size
|
|
|
|
fetch("imhex.wasm.size").then(async (resp) => {
|
|
|
|
wasmSize = parseInt((await resp.text()).trim());
|
2024-02-10 23:31:05 +01:00
|
|
|
console.log(`Real WASM binary size is ${wasmSize} bytes`);
|
2024-02-01 15:03:33 +01:00
|
|
|
});
|
|
|
|
|
2024-01-31 22:15:23 +01:00
|
|
|
// Monkeypatch WebAssembly to have a progress bar
|
|
|
|
// inspired from: https://github.com/WordPress/wordpress-playground/pull/46 (but had to be modified)
|
|
|
|
function monkeyPatch(progressFun) {
|
|
|
|
const _instantiateStreaming = WebAssembly.instantiateStreaming;
|
|
|
|
WebAssembly.instantiateStreaming = (response, ...args) => {
|
2024-02-01 15:03:33 +01:00
|
|
|
// Do not collect wasm content length here see above
|
2024-01-31 22:15:23 +01:00
|
|
|
const file = response.url.substring(
|
|
|
|
new URL(response.url).origin.length + 1
|
|
|
|
);
|
|
|
|
const reportingResponse = new Response(
|
2024-02-01 10:18:54 +01:00
|
|
|
new ReadableStream(
|
|
|
|
{
|
|
|
|
async start(controller) {
|
|
|
|
const reader = response.clone().body.getReader();
|
|
|
|
let loaded = 0;
|
|
|
|
for (; ;) {
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
if (done) {
|
2024-02-01 15:03:33 +01:00
|
|
|
if(wasmSize) progressFun(file, wasmSize);
|
2024-02-01 10:18:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
loaded += value.byteLength;
|
2024-02-01 15:03:33 +01:00
|
|
|
progressFun(file, loaded);
|
2024-02-01 10:18:54 +01:00
|
|
|
controller.enqueue(value);
|
2024-01-31 22:15:23 +01:00
|
|
|
}
|
2024-02-01 10:18:54 +01:00
|
|
|
controller.close();
|
2024-01-31 22:15:23 +01:00
|
|
|
}
|
2024-02-01 10:18:54 +01:00
|
|
|
},
|
2024-01-31 22:15:23 +01:00
|
|
|
{
|
|
|
|
status: response.status,
|
|
|
|
statusText: response.statusText
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
for (const pair of response.headers.entries()) {
|
|
|
|
reportingResponse.headers.set(pair[0], pair[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _instantiateStreaming(reportingResponse, ...args);
|
|
|
|
}
|
|
|
|
}
|
2024-02-01 15:03:33 +01:00
|
|
|
monkeyPatch((file, done) => {
|
|
|
|
if (!wasmSize) return;
|
|
|
|
if (done > wasmSize) {
|
2024-02-10 23:31:05 +01:00
|
|
|
console.warn(`Downloaded binary size ${done} is larger than expected WASM size ${wasmSize}`);
|
2024-02-01 12:40:37 +01:00
|
|
|
return;
|
2024-02-10 23:31:05 +01:00
|
|
|
}
|
2024-02-01 12:40:37 +01:00
|
|
|
|
2024-02-01 15:03:33 +01:00
|
|
|
const percent = ((done / wasmSize) * 100).toFixed(0);
|
2024-02-01 12:40:37 +01:00
|
|
|
const mibNow = (done / 1024**2).toFixed(1);
|
2024-02-01 15:03:33 +01:00
|
|
|
const mibTotal = (wasmSize / 1024**2).toFixed(1);
|
2024-02-01 10:18:54 +01:00
|
|
|
|
|
|
|
let root = document.querySelector(':root');
|
|
|
|
root.style.setProperty("--progress", `${percent}%`)
|
|
|
|
document.getElementById("progress-bar-content").innerHTML = `${percent}% [${mibNow} MiB / ${mibTotal} MiB]`;
|
2024-01-31 22:15:23 +01:00
|
|
|
});
|
|
|
|
|
2023-10-04 12:00:32 +02:00
|
|
|
function glfwSetCursorCustom(wnd, shape) {
|
|
|
|
let body = document.getElementsByTagName("body")[0]
|
|
|
|
switch (shape) {
|
|
|
|
case 0x00036001: // GLFW_ARROW_CURSOR
|
|
|
|
body.style.cursor = "default";
|
|
|
|
break;
|
|
|
|
case 0x00036002: // GLFW_IBEAM_CURSOR
|
|
|
|
body.style.cursor = "text";
|
|
|
|
break;
|
|
|
|
case 0x00036003: // GLFW_CROSSHAIR_CURSOR
|
|
|
|
body.style.cursor = "crosshair";
|
|
|
|
break;
|
|
|
|
case 0x00036004: // GLFW_HAND_CURSOR
|
|
|
|
body.style.cursor = "pointer";
|
|
|
|
break;
|
|
|
|
case 0x00036005: // GLFW_HRESIZE_CURSOR
|
|
|
|
body.style.cursor = "ew-resize";
|
|
|
|
break;
|
|
|
|
case 0x00036006: // GLFW_VRESIZE_CURSOR
|
|
|
|
body.style.cursor = "ns-resize";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
body.style.cursor = "default";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function glfwCreateStandardCursorCustom(shape) {
|
|
|
|
return shape
|
|
|
|
}
|
|
|
|
|
2023-12-09 12:06:20 +01:00
|
|
|
var notWorkingTimer = setTimeout(() => {
|
|
|
|
document.getElementById("not_working").classList.add("visible")
|
|
|
|
}, 5000);
|
|
|
|
|
2023-10-04 12:00:32 +02:00
|
|
|
var Module = {
|
|
|
|
preRun: [],
|
2024-07-03 22:32:33 +02:00
|
|
|
postRun: function() {
|
|
|
|
// Patch the emscripten GLFW module to send mouse and touch events in the right order
|
|
|
|
// For ImGui interactions to correctly work with touch input, MousePos events need
|
|
|
|
// to be processed first and then MouseButton events in the next frame. By default,
|
|
|
|
// GLFW does the exact opposite, which causes buttons to require two taps to register
|
|
|
|
// and windows get "stuck" to the cursor when dragged or resized
|
|
|
|
GLFW.onMousemove = event => {
|
|
|
|
if (event.type === "touchmove") {
|
|
|
|
event.preventDefault();
|
|
|
|
let primaryChanged = false;
|
|
|
|
for (let i of event.changedTouches) {
|
|
|
|
if (GLFW.primaryTouchId === i.identifier) {
|
|
|
|
Browser.setMouseCoords(i.pageX, i.pageY);
|
|
|
|
primaryChanged = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!primaryChanged) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Browser.calculateMouseEvent(event);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GLFW.onMouseButtonChanged = (event, status) => {
|
|
|
|
if (!GLFW.active) return;
|
|
|
|
if (event.target != Module["canvas"]) return;
|
|
|
|
const isTouchType = event.type === "touchstart" || event.type === "touchend" || event.type === "touchcancel";
|
|
|
|
let eventButton = 0;
|
|
|
|
if (isTouchType) {
|
|
|
|
event.preventDefault();
|
|
|
|
let primaryChanged = false;
|
|
|
|
if (GLFW.primaryTouchId === null && event.type === "touchstart" && event.targetTouches.length > 0) {
|
|
|
|
const chosenTouch = event.targetTouches[0];
|
|
|
|
GLFW.primaryTouchId = chosenTouch.identifier;
|
|
|
|
Browser.setMouseCoords(chosenTouch.pageX, chosenTouch.pageY);
|
|
|
|
primaryChanged = true;
|
|
|
|
} else if (event.type === "touchend" || event.type === "touchcancel") {
|
|
|
|
for (let i of event.changedTouches) {
|
|
|
|
if (GLFW.primaryTouchId === i.identifier) {
|
|
|
|
GLFW.primaryTouchId = null;
|
|
|
|
primaryChanged = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!primaryChanged) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Browser.calculateMouseEvent(event);
|
|
|
|
eventButton = GLFW.DOMToGLFWMouseButton(event);
|
|
|
|
}
|
|
|
|
if (status == 1) {
|
|
|
|
GLFW.active.buttons |= (1 << eventButton);
|
|
|
|
try {
|
|
|
|
event.target.setCapture();
|
|
|
|
} catch (e) {}
|
|
|
|
} else {
|
|
|
|
GLFW.active.buttons &= ~(1 << eventButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GLFW.active.cursorPosFunc) {
|
|
|
|
getWasmTableEntry(GLFW.active.cursorPosFunc)(GLFW.active.id, Browser.mouseX, Browser.mouseY);
|
|
|
|
}
|
|
|
|
if (GLFW.active.mouseButtonFunc) {
|
|
|
|
getWasmTableEntry(GLFW.active.mouseButtonFunc)(GLFW.active.id, eventButton, status, GLFW.getModBits(GLFW.active));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
2023-10-04 12:00:32 +02:00
|
|
|
onRuntimeInitialized: function() {
|
|
|
|
// Triggered when the wasm module is loaded and ready to use.
|
2023-12-08 16:22:47 +01:00
|
|
|
document.getElementById("loading").style.display = "none"
|
2023-10-04 12:00:32 +02:00
|
|
|
document.getElementById("canvas").style.display = "initial"
|
2023-12-09 12:06:20 +01:00
|
|
|
|
|
|
|
clearTimeout(notWorkingTimer);
|
2023-10-04 12:00:32 +02:00
|
|
|
},
|
|
|
|
print: (function() { })(),
|
2023-12-09 12:06:20 +01:00
|
|
|
printErr: function(text) { },
|
2023-10-04 12:00:32 +02:00
|
|
|
canvas: (function() {
|
2024-07-02 23:16:19 +02:00
|
|
|
const canvas = document.getElementById('canvas');
|
|
|
|
canvas.addEventListener("webglcontextlost", function(e) {
|
|
|
|
alert('WebGL context lost, please reload the page');
|
|
|
|
e.preventDefault();
|
|
|
|
}, false);
|
2023-10-04 12:00:32 +02:00
|
|
|
|
2024-07-04 23:15:33 +02:00
|
|
|
// Turn long touches into right-clicks
|
|
|
|
let timer = null;
|
|
|
|
canvas.addEventListener('touchstart', event => {
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
let eventArgs = {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: true,
|
|
|
|
view: window,
|
|
|
|
screenX: event.touches[0].screenX,
|
|
|
|
screenY: event.touches[0].screenY,
|
|
|
|
clientX: event.touches[0].clientX,
|
|
|
|
clientY: event.touches[0].clientY,
|
|
|
|
button: 2,
|
|
|
|
buttons: 2,
|
|
|
|
relatedTarget: event.target,
|
|
|
|
region: event.region
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.dispatchEvent(new MouseEvent('mousedown', eventArgs));
|
|
|
|
canvas.dispatchEvent(new MouseEvent('mouseup', eventArgs));
|
|
|
|
}, 400);
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.addEventListener('touchend', event => {
|
|
|
|
if (timer) {
|
|
|
|
clearTimeout(timer);
|
|
|
|
timer = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-07-02 23:16:19 +02:00
|
|
|
if (typeof WebGL2RenderingContext !== 'undefined') {
|
|
|
|
let gl = canvas.getContext('webgl2', { stencil: true });
|
|
|
|
if (!gl) {
|
|
|
|
console.error('WebGL 2 not available, falling back to WebGL');
|
|
|
|
gl = canvas.getContext('webgl', { stencil: true });
|
|
|
|
}
|
|
|
|
if (!gl) {
|
|
|
|
alert('WebGL not available with stencil buffer');
|
|
|
|
}
|
|
|
|
return canvas;
|
|
|
|
} else {
|
|
|
|
alert('WebGL 2 not supported by this browser');
|
|
|
|
}
|
2023-10-04 12:00:32 +02:00
|
|
|
})(),
|
|
|
|
setStatus: function(text) { },
|
|
|
|
totalDependencies: 0,
|
2024-01-31 22:15:23 +01:00
|
|
|
monitorRunDependencies: function(left) {
|
|
|
|
},
|
2023-10-04 12:00:32 +02:00
|
|
|
instantiateWasm: function(imports, successCallback) {
|
|
|
|
imports.env.glfwSetCursor = glfwSetCursorCustom
|
|
|
|
imports.env.glfwCreateStandardCursor = glfwCreateStandardCursorCustom
|
2024-01-31 22:15:23 +01:00
|
|
|
instantiateAsync(wasmBinary, wasmBinaryFile, imports, (result) => {
|
|
|
|
successCallback(result.instance, result.module)
|
|
|
|
});
|
2024-02-01 11:57:26 +01:00
|
|
|
},
|
|
|
|
arguments: []
|
2023-10-04 12:00:32 +02:00
|
|
|
};
|
|
|
|
|
2024-02-01 11:57:26 +01:00
|
|
|
// Handle passing arguments to the wasm module
|
|
|
|
const queryString = window.location.search;
|
|
|
|
const urlParams = new URLSearchParams(queryString);
|
|
|
|
if (urlParams.has("lang")) {
|
|
|
|
Module["arguments"].push("--language");
|
|
|
|
Module["arguments"].push(urlParams.get("lang"));
|
|
|
|
}
|
2023-10-04 12:00:32 +02:00
|
|
|
|
|
|
|
window.addEventListener('resize', js_resizeCanvas, false);
|
|
|
|
function js_resizeCanvas() {
|
|
|
|
let canvas = document.getElementById('canvas');
|
2023-12-27 22:00:09 +01:00
|
|
|
|
2024-07-01 20:09:16 +02:00
|
|
|
canvas.top = document.documentElement.clientTop;
|
|
|
|
canvas.left = document.documentElement.clientLeft;
|
|
|
|
canvas.width = Math.min(document.documentElement.clientWidth, window.innerWidth || 0);
|
|
|
|
canvas.height = Math.min(document.documentElement.clientHeight, window.innerHeight || 0);
|
2023-10-04 12:00:32 +02:00
|
|
|
}
|