chore (plugin): polish the new plugin interface

This commit is contained in:
MickaelK 2025-01-23 13:46:45 +11:00
parent daf96fdcff
commit 1510e0bcda
8 changed files with 64 additions and 63 deletions

View file

@ -57,6 +57,8 @@
"gltf": "model/gltf+json",
"glb": "model/gltf-binary",
"gpx": "application/gpx+xml",
"gp4": "application/x-guitar-pro",
"gp5": "application/x-guitar-pro",
"gz": "application/x-gzip",
"heic": "image/heic",
"heif": "image/heic",
@ -92,6 +94,7 @@
"mef": "image/x-mamiya-mef",
"mid": "audio/midi",
"midi": "application/x-midi",
"mjs": "application/javascript",
"mkv": "video/x-matroska",
"mml": "text/mathml",
"mng": "video/x-mng",
@ -118,6 +121,7 @@
"ogv": "application/ogg",
"orf": "image/x-olympus-orf",
"org": "text/org",
"otf": "font/otf",
"pdb": "application/x-pilot",
"pdf": "application/pdf",
"pef": "image/x-pentax-pef",
@ -169,6 +173,7 @@
"tiff": "image/tiff",
"tk": "application/x-tcl",
"ts": "text/plain",
"ttf": "application/x-font-ttf",
"txt": "text/plain",
"url": "application/x-url",
"vcf": "text/vcard",
@ -185,7 +190,8 @@
"wmlc": "application/vnd.wap.wmlc",
"wms": "application/vnd.ogc.wms_xml",
"wmv": "video/x-ms-wmv",
"woff": "application/font-woff",
"woff": "font/woff",
"woff2": "font/woff2",
"wrl": "x-world/x-vrml",
"x3d": "model/x3d+xml",
"x3dv": "model/x3d-vrml",

View file

@ -1,3 +1,6 @@
const DEBUG = false;
const log = (msg) => DEBUG && console.log(msg);
export default async function(baseURL, path, opts = {}) {
const wasi = new Wasi();
const wasm = await WebAssembly.instantiateStreaming(
@ -43,11 +46,18 @@ export function readFS(fd) {
return file.buffer.subarray(0, end);
}
export function clearFS() {
Object.keys(FS).forEach((key) => {
if (key > 2) delete FS[key];
});
nextFd = 3;
}
function getFile(path) {
const allFds = Object.keys(FS);
for (let i=allFds.length - 1; i>0; i--) {
if (FS[allFds[i]].path === path) {
console.log(`fileopen fd=${i} path=${path}`);
log(` fileopen fd=${i} path=${path}`);
return FS[allFds[i]];
}
}
@ -84,7 +94,7 @@ export const syscalls = {
console.log(`Stubbed __syscall_stat64 called with pathPtr=${pathPtr}, bufPtr=${bufPtr}`);
return 0; // Return 0 for a successful call
},
__syscall_lstat64: () => {
__syscall_lstat64: () => {
console.log(`Stubbed __syscall_lstat64 called`);
return -1;
},
@ -182,7 +192,14 @@ export class Wasi {
if (fd === 1 || fd === 2) {
let msg = fd === 1? "stdout: " : "stderr: ";
msg += new TextDecoder().decode(readFS(fd));
FS[fd] = { buffer: new Uint8Array(0), position: 0, path: "" };
console.log(msg);
FS[fd] = {
buffer: new Uint8Array(0),
position: 0,
path: FS[fd].path || "",
};
} else {
log(`wasi::fd_write fd=${fd}`);
}
return 0;
}
@ -204,7 +221,7 @@ export class Wasi {
length,
file.buffer.length - file.position,
);
if (bytesToRead <= 0) {
if (bytesToRead < 0) {
break;
}
memory.set(
@ -214,6 +231,7 @@ export class Wasi {
file.position += bytesToRead;
totalBytesRead += bytesToRead;
}
log(`wasi::fd_read fd=${fd} iovs_len=${iovs_len} totalBytesRead=${totalBytesRead}`);
new DataView(this.#instance.exports.memory.buffer).setUint32(
nread,
totalBytesRead,
@ -222,7 +240,8 @@ export class Wasi {
return 0;
}
fd_seek(fd, offsetBigInt, whence) {
fd_seek(fd, offsetBigInt, _, whence) {
log(`wasi::fd_seek fd=${fd} offset=${offsetBigInt} whence=${whence}`);
const offset = Number(offsetBigInt);
const file = FS[fd];
if (!file) {
@ -283,7 +302,6 @@ export class Wasi {
}
__syscall_openat(dirFd, pathPtr, flags, mode) {
console.debug(`openat called with dirFd=${dirFd}, pathPtr=${pathPtr}, flags=${flags}, mode=${mode}`);
const memory = new Uint8Array(this.#instance.exports.memory.buffer);
let path = "";
for (let i = pathPtr; memory[i] !== 0; i++) {
@ -292,15 +310,15 @@ export class Wasi {
const allFds = Object.keys(FS);
for (let i=allFds.length - 1; i>0; i--) {
if (FS[allFds[i]].path === path) {
console.log(`fileopen fd=${i} path=${path}`);
log(` syscall::openat::result fd=${i} path=${path}`);
return i;
}
}
return -1;
throw new Error("Unknown file for __syscall_openat");
}
__syscall_stat64(pathPtr, buf) {
console.log(`stat64`);
log(` syscall::stat64 pathPtr=${pathPtr}, bufPtr=${buf}`);
const memory = new Uint8Array(this.#instance.exports.memory.buffer);
let path = "";
for (let i = pathPtr; memory[i] !== 0; i++) {
@ -309,11 +327,6 @@ export class Wasi {
const file = getFile(path);
const HEAP32 = new Int32Array(this.#instance.exports.memory.buffer);
const HEAPU32 = new Uint32Array(this.#instance.exports.memory.buffer);
const tempI64 = [0, 0];
const tempDouble = 0;
// Dummy stat data
const stat = {
dev: 1,
ino: 42,
@ -329,47 +342,32 @@ export class Wasi {
mtime: new Date(),
ctime: new Date(),
};
// Fill the buffer
HEAP32[(buf >> 2)] = stat.dev;
HEAP32[((buf + 4) >> 2)] = stat.mode;
HEAPU32[((buf + 8) >> 2)] = stat.nlink;
HEAP32[((buf + 12) >> 2)] = stat.uid;
HEAP32[((buf + 16) >> 2)] = stat.gid;
HEAP32[((buf + 20) >> 2)] = stat.rdev;
HEAP32[((buf + 24) >> 2)] = stat.size & 0xFFFFFFFF; // Lower 32 bits
HEAP32[((buf + 28) >> 2)] = Math.floor(stat.size / 4294967296); // Upper 32 bits
HEAP32[((buf + 24) >> 2)] = stat.size & 0xFFFFFFFF;
HEAP32[((buf + 28) >> 2)] = Math.floor(stat.size / 4294967296);
HEAP32[((buf + 32) >> 2)] = stat.blksize;
HEAP32[((buf + 36) >> 2)] = stat.blocks;
// Write timestamps
const atimeSeconds = Math.floor(stat.atime.getTime() / 1000);
const atimeNanos = (stat.atime.getTime() % 1000) * 1e6;
HEAP32[((buf + 40) >> 2)] = atimeSeconds;
HEAP32[((buf + 44) >> 2)] = 0; // Upper 32 bits of atime
HEAP32[((buf + 48) >> 2)] = atimeNanos;
const mtimeSeconds = Math.floor(stat.mtime.getTime() / 1000);
const mtimeNanos = (stat.mtime.getTime() % 1000) * 1e6;
HEAP32[((buf + 56) >> 2)] = mtimeSeconds;
HEAP32[((buf + 60) >> 2)] = 0; // Upper 32 bits of mtime
HEAP32[((buf + 64) >> 2)] = mtimeNanos;
const ctimeSeconds = Math.floor(stat.ctime.getTime() / 1000);
const ctimeNanos = (stat.ctime.getTime() % 1000) * 1e6;
HEAP32[((buf + 72) >> 2)] = ctimeSeconds;
HEAP32[((buf + 76) >> 2)] = 0; // Upper 32 bits of ctime
HEAP32[((buf + 80) >> 2)] = ctimeNanos;
// Dummy inode
HEAP32[((buf + 88) >> 2)] = stat.ino & 0xFFFFFFFF; // Lower 32 bits
HEAP32[((buf + 92) >> 2)] = Math.floor(stat.ino / 4294967296); // Upper 32 bits
console.debug(`Stubbed __syscall_stat64 called with pathPtr=${pathPtr}, bufPtr=${buf}`);
HEAP32[((buf + 40) >> 2)] = Math.floor(stat.atime.getTime() / 1000);
HEAP32[((buf + 44) >> 2)] = 0;
HEAP32[((buf + 48) >> 2)] = (stat.atime.getTime() % 1000) * 1e6;
HEAP32[((buf + 56) >> 2)] = Math.floor(stat.mtime.getTime() / 1000);
HEAP32[((buf + 60) >> 2)] = 0;
HEAP32[((buf + 64) >> 2)] = (stat.mtime.getTime() % 1000) * 1e6;
HEAP32[((buf + 72) >> 2)] = Math.floor(stat.ctime.getTime() / 1000);
HEAP32[((buf + 76) >> 2)] = 0;
HEAP32[((buf + 80) >> 2)] = (stat.ctime.getTime() % 1000) * 1e6;
HEAP32[((buf + 88) >> 2)] = stat.ino & 0xFFFFFFFF;
HEAP32[((buf + 92) >> 2)] = Math.floor(stat.ino / 4294967296);
return 0;
}
__cxa_throw(ptr, type, destructor) {
console.error(`Exception thrown at ptr=${ptr}, type=${type}, destructor=${destructor}`);
console.error(` syscall::cxa_throw ptr=${ptr}, type=${type}, destructor=${destructor}`);
throw new Error("WebAssembly exception");
}

View file

@ -22,7 +22,7 @@ export function get(mime) {
export async function load(mime) {
const specs = plugins[mime];
if (!specs) return null;
const [_, url] = specs;
const [, url] = specs;
const module = await import(url);
return module.default;
}

View file

@ -5,7 +5,6 @@ import { AjaxError } from "../../lib/error.js";
import { load as loadPlugin } from "../../model/plugin.js";
import { loadCSS } from "../../helpers/loader.js";
import { createLoader } from "../../components/loader.js";
import t from "../../locales/index.js";
import ctrlError from "../ctrl_error.js";
import componentDownloader, { init as initDownloader } from "./application_downloader.js";
@ -18,7 +17,6 @@ import withCube from "./application_3d/scene_cube.js";
import ctrlToolbar from "./application_3d/toolbar.js";
class I3DLoader {
constructor() {}
load() { throw new Error("NOT_IMPLEMENTED"); }
transform() { throw new Error("NOT_IMPLEMENTED"); }
is2D() { return false; }
@ -45,7 +43,7 @@ export default async function(render, { mime, acl$, getDownloadUrl = nop, getFil
const removeLoader = createLoader($draw);
await effect(rxjs.from(loadPlugin(mime)).pipe(
rxjs.mergeMap(async (loader) => {
rxjs.mergeMap(async(loader) => {
if (!loader) {
componentDownloader(render, { mime, acl$, getFilename, getDownloadUrl });
return rxjs.EMPTY;
@ -59,11 +57,7 @@ export default async function(render, { mime, acl$, getDownloadUrl = nop, getFil
(err) => observer.error(err),
)).pipe(
removeLoader,
rxjs.mergeMap((mesh) => create3DScene({
mesh,
$draw, $toolbar, $menubar,
hasCube, mime, is2D: loader.is2D,
})),
rxjs.mergeMap((mesh) => create3DScene({ mime, mesh, is2D: loader.is2D, $draw, $toolbar, $menubar, hasCube })),
)),
rxjs.catchError((err) => {
let _err = err;

View file

@ -5,6 +5,7 @@ import { load as loadPlugin } from "../../model/plugin.js";
import { loadCSS } from "../../helpers/loader.js";
import { createLoader } from "../../components/loader.js";
import ctrlError from "../ctrl_error.js";
import componentDownloader, { init as initDownloader } from "./application_downloader.js";
export default function(render, { mime, getFilename, getDownloadUrl, acl$ }) {
const $page = createElement(`
@ -35,5 +36,6 @@ export default function(render, { mime, getFilename, getDownloadUrl, acl$ }) {
export function init() {
return Promise.all([
loadCSS(import.meta.url, "./application_skeleton.css"),
initDownloader(),
]);
}

View file

@ -51,7 +51,7 @@ export default async function(render, { mime, getDownloadUrl = nop, getFilename
rxjs.mergeMap(async({ response }) => {
const loader = getPlugin(mime);
if (!loader) throw new TypeError(`unsupported mimetype "${mime}"`);
const [_, url] = loader;
const [, url] = loader;
const module = await import(url);
const table = new (await module.default(ITable))(response, { $menubar });
STATE.header = table.getHeader();
@ -98,7 +98,7 @@ export default async function(render, { mime, getDownloadUrl = nop, getFilename
// feature: infinite scroll
effect(rxjs.fromEvent($dom.tbody, "scroll").pipe(
rxjs.mergeMap(async (e) => {
rxjs.mergeMap(async(e) => {
const scrollBottom = e.target.scrollHeight - (e.target.scrollTop + e.target.clientHeight);
if (scrollBottom > 0) return;
else if (STATE.rows.length <= MAX_ROWS) return;
@ -115,7 +115,7 @@ export default async function(render, { mime, getDownloadUrl = nop, getFilename
init$,
init$.pipe(
rxjs.mergeMap(() => rxjs.fromEvent(window, "resize")),
rxjs.debounce((e) => e.debounce === false ? rxjs.of(null) : rxjs.timer(100)),
rxjs.debounce((e) => e["debounce"] === false ? rxjs.of(null) : rxjs.timer(100)),
),
).pipe(
rxjs.tap(() => resizeLastColumnIfNeeded({
@ -159,7 +159,7 @@ async function buildRows(rows, legends, $tbody, padding, isInit, withClear) {
`));
if (!isInit) {
const e = new Event("resize");
e.debounce = false;
e["debounce"] = false;
window.dispatchEvent(e);
await new Promise(requestAnimationFrame);
}
@ -219,7 +219,7 @@ function sortBy(rows, ascending, key) {
const o = ascending ? 1 : -1;
return rows.sort((a, b) => {
if (a[key] === b[key]) return 0;
else if (a[key] < b[key]) return -o
else if (a[key] < b[key]) return -o;
return o;
});
}

View file

@ -81,20 +81,21 @@ export function buttonDownload(name, link) {
}
export function buttonFullscreen($screen, fullscreen = null) {
let fullscreenHandler = null;
if (fullscreen === null) {
if ("webkitRequestFullscreen" in document.body) {
fullscreen = () => $screen.webkitRequestFullscreen();
fullscreenHandler = () => $screen.webkitRequestFullscreen();
} else if ("mozRequestFullScreen" in document.body) {
fullscreen = () => $screen.mozRequestFullScreen();
fullscreenHandler = () => $screen.mozRequestFullScreen();
}
}
if (!fullscreen) return;
if (!fullscreenHandler) return;
const $el = createElement(`
<span>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MzguNTQzIDQzOC41NDMiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuNzI5LDAsMCwwLjcyOSw1OS40MjI1NzYsNTkuNDIyNDQxKSI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojZjJmMmYyO2ZpbGwtb3BhY2l0eToxIiBkPSJtIDQwNy40MiwxNTkuMDI5IGMgMy42MiwzLjYxNiA3Ljg5OCw1LjQyOCAxMi44NDcsNS40MjggMi4yODIsMCA0LjY2OCwtMC40NzYgNy4xMzksLTEuNDI5IDcuNDI2LC0zLjIzNSAxMS4xMzYsLTguODUzIDExLjEzNiwtMTYuODQ2IFYgMTguMjc2IGMgMCwtNC45NDkgLTEuODA3LC05LjIzMSAtNS40MjgsLTEyLjg0NyAtMy42MSwtMy42MTcgLTcuODk4LC01LjQyNCAtMTIuODQ3LC01LjQyNCBIIDI5Mi4zNiBjIC03Ljk5MSwwIC0xMy42MDcsMy44MDUgLTE2Ljg0OCwxMS40MTkgLTMuMjMsNy40MjMgLTEuOTAyLDEzLjk5IDQsMTkuNjk4IEwgMzIwLjYyMyw3Mi4yMzQgMjE5LjI3MSwxNzMuNTg5IDExNy45MTcsNzIuMjMxIDE1OS4wMjksMzEuMTE5IGMgNS45MDEsLTUuNzA4IDcuMjMyLC0xMi4yNzUgMy45OTksLTE5LjY5OCBDIDE1OS43ODksMy44MDcgMTU0LjE3NSwwIDE0Ni4xODIsMCBIIDE4LjI3NiBDIDEzLjMyNCwwIDkuMDQxLDEuODA5IDUuNDI1LDUuNDI2IDEuODA4LDkuMDQyIDAuMDAxLDEzLjMyNCAwLjAwMSwxOC4yNzMgViAxNDYuMTggYyAwLDcuOTk2IDMuODA5LDEzLjYxIDExLjQxOSwxNi44NDYgMi4yODUsMC45NDggNC41NywxLjQyOSA2Ljg1NSwxLjQyOSA0Ljk0OCwwIDkuMjI5LC0xLjgxMiAxMi44NDcsLTUuNDI3IEwgNzIuMjM0LDExNy45MTkgMTczLjU4OCwyMTkuMjczIDcyLjIzNCwzMjAuNjIyIDMxLjEyMiwyNzkuNTA5IGMgLTUuNzExLC01LjkwMyAtMTIuMjc1LC03LjIzMSAtMTkuNzAyLC00LjAwMSAtNy42MTQsMy4yNDEgLTExLjQxOSw4Ljg1NiAtMTEuNDE5LDE2Ljg1NCB2IDEyNy45MDYgYyAwLDQuOTQ4IDEuODA3LDkuMjI5IDUuNDI0LDEyLjg0NyAzLjYxOSwzLjYxNCA3LjkwMiw1LjQyMSAxMi44NTEsNS40MjEgaCAxMjcuOTA2IGMgNy45OTYsMCAxMy42MSwtMy44MDYgMTYuODQ2LC0xMS40MTYgMy4yMzQsLTcuNDI3IDEuOTAzLC0xMy45OSAtMy45OTksLTE5LjcwNSBMIDExNy45MTcsMzY2LjMwOSAyMTkuMjcxLDI2NC45NSAzMjAuNjI0LDM2Ni4zMTEgMjc5LjUxLDQwNy40MjEgYyAtNS44OTksNS43MDggLTcuMjI4LDEyLjI3OSAtMy45OTcsMTkuNjk4IDMuMjM3LDcuNjE3IDguODU2LDExLjQyMyAxNi44NTEsMTEuNDIzIGggMTI3LjkwNyBjIDQuOTQ4LDAgOS4yMzIsLTEuODEzIDEyLjg0NywtNS40MjggMy42MTMsLTMuNjEzIDUuNDIsLTcuODk4IDUuNDIsLTEyLjg0NyBWIDI5Mi4zNjIgYyAwLC03Ljk5NCAtMy43MDksLTEzLjYxMyAtMTEuMTM2LC0xNi44NTEgLTcuODAyLC0zLjIzIC0xNC40NjIsLTEuOTAzIC0xOS45ODUsNC4wMDQgTCAzNjYuMzExLDMyMC42MjEgMjY0Ljk1MiwyMTkuMjcxIDM2Ni4zMSwxMTcuOTE3IFoiIC8+CiAgPC9nPgo8L3N2Zz4K" alt="fullscreen">
</span>
`);
$el.onclick = fullscreen;
$el.onclick = fullscreenHandler;
return $el;
}

View file

@ -26,6 +26,6 @@ func init() {
BUILD_REF = "%s"
BUILD_DATE = "%s"
}
`, b.String(), time.Now().Format("20060102"))))
`, strings.TrimSpace(b.String()), time.Now().Format("20060102"))))
f.Close()
}