Initial commit

This commit is contained in:
2026-02-18 23:47:03 +01:00
commit 94757e8cc3
8 changed files with 421 additions and 0 deletions

92
static/html-view.html Normal file
View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>HTML Viewer</title>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
background-color: #222;
}
body {
display: flex;
align-items: stretch;
justify-content: stretch;
}
#viewer-frame {
display: block;
width: 100%;
height: 100%;
border: 0;
background: #fff;
}
</style>
<script type="module">
import { Subscription_Endpoint } from './subscription.mjs';
function get_source_name() {
const params = new URLSearchParams(location.search);
return params.get('source') || 'default-html';
}
document.addEventListener('DOMContentLoaded', async () => {
const viewer_frame = document.getElementById('viewer-frame');
const ep = new Subscription_Endpoint();
await ep.connect('/subscription');
const source = get_source_name();
ep.subscribe(source);
let current_url = null;
let pending_scroll = null;
ep.addEventListener('message', (ev) => {
// Capture current scroll position (best-effort)
try {
const win = viewer_frame.contentWindow;
if (win) {
pending_scroll = {
x: win.scrollX,
y: win.scrollY
};
}
} catch {
pending_scroll = null;
}
if (current_url) {
URL.revokeObjectURL(current_url);
}
current_url = URL.createObjectURL(ev.detail);
viewer_frame.src = current_url;
});
// Restore scroll after the new document loads
viewer_frame.addEventListener('load', () => {
if (!pending_scroll) return;
try {
viewer_frame.contentWindow.scrollTo(
pending_scroll.x,
pending_scroll.y
);
} catch {
/* ignore */
}
pending_scroll = null;
});
});
</script>
</head>
<body>
<iframe id="viewer-frame"></iframe>
</body>
</html>

92
static/image-view.html Normal file
View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>Test of remote view</title>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
background-color: #222;
}
body {
display: flex;
align-items: center;
justify-content: center;
/* Two 50/50 linear gradients one horizontal, one vertical */
background-image:
linear-gradient(0deg, #f1f1f1 0% 50%, #d1d1d1 50% 100%),
linear-gradient(90deg, #f1f1f1 0% 50%, #d1d1d1 50% 100%);
/* Each square is 8px; the pattern repeats */
background-size: 16px 16px;
background-repeat: repeat;
/* Blend the two gradients multiply gives a proper “XOR” look */
background-blend-mode: multiply;
}
body.full {
display: block;
width: none;
height: none;
}
#viewer-image {
display: block;
border: 1px solid #864;
max-width: 100vw;
max-height: 100vh;
}
body.full #viewer-image {
border: none;
max-width: none;
max-height: none;
}
</style>
<script type="module">
import { Subscription_Endpoint } from './subscription.mjs';
document.addEventListener('DOMContentLoaded', async () => {
const viewer_image = document.getElementById('viewer-image')
viewer_image.addEventListener('click', () => {
document.body.classList.toggle('full');
})
const ep = new Subscription_Endpoint();
await ep.connect('/subscription');
ep.subscribe('demo');
ep.addEventListener('message', (ev) => {
viewer_image.src = URL.createObjectURL(ev.detail);
console.log("Got image", ev.detail);
});
});
</script>
</head>
<body>
<img id="viewer-image">
</body>
</html>

29
static/post-event.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Test of posting event</title>
</head>
<body>
<form action="/emit-multipart" method="post" enctype="multipart/form-data">
<div>
<label>
Resource ID:
<input type="text" name="resource" required>
</label>
</div>
<div>
<label>
File:
<input type="file" name="file" required>
</label>
</div>
<div>
<button type="submit">Post</button>
</div>
</form>
</body>
</html>

41
static/subscription.mjs Normal file
View File

@@ -0,0 +1,41 @@
export class Subscription_Endpoint extends EventTarget {
async connect(path) {
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${location.host}${path}`;
const ws = new WebSocket(wsUrl);
await new Promise((resolve, reject) => {
ws.addEventListener('open', resolve, { once: true });
ws.addEventListener('error', reject, { once: true });
});
ws.addEventListener('message', ev => {
this.dispatchEvent(new CustomEvent('message', { detail: ev.data }));
});
ws.addEventListener('close', () => {
this.dispatchEvent(new Event('close'));
});
ws.addEventListener('error', err => {
this.dispatchEvent(new CustomEvent('error', { detail: err }));
});
this.ws = ws;
return ws;
}
subscribe(resource) {
const { ws } = this;
ws.send(JSON.stringify({
cmd: 'subscribe',
resource
}));
}
}