tetratto/crates/app/src/public/html/littleweb/browser.lisp
2025-07-13 17:54:12 -04:00

227 lines
6.4 KiB
Common Lisp

(text "{% extends \"root.html\" %} {% block head %}")
(title
(text "{{ config.name }}"))
(text "{% endblock %} {% block body %}")
(div
("id" "panel")
("class" "flex flex-row gap-2")
(a
("class" "button camo")
("href" "/")
(icon (text "house")))
(button
("class" "lowered")
("onclick" "back()")
(icon (text "arrow-left")))
(button
("class" "lowered")
("onclick" "forward()")
(icon (text "arrow-right")))
(button
("class" "lowered")
("onclick" "reload()")
(icon (text "rotate-cw")))
(form
("class" "w-full flex gap-1 flex-row")
("onsubmit" "event.preventDefault(); littleweb_navigate(event.target.uri.getAttribute('true_value'))")
(input
("type" "uri")
("class" "w-full")
("true_value" "")
("name" "uri")
("id" "uri"))
(button ("class" "lowered small square") (icon (text "arrow-right"))))
(text "{% if user -%}")
(div
("class" "dropdown")
(button
("class" "flex-row camo")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
("style" "gap: var(--pad-1) !important")
(text "{{ components::avatar(username=user.username, size=\"24px\") }}")
(icon_class (text "chevron-down") (text "dropdown_arrow")))
(text "{{ components::user_menu() }}"))
(text "{%- endif %}"))
(iframe
("id" "browser_iframe")
("frameborder" "0")
("src" "{% if path -%} {{ config.lw_host }}/api/v1/net/{{ path }}?s={{ session }} {%- endif %}"))
(style
("data-turbo-temporary" "true")
(text ":root {
--panel-height: 45px;
}
html,
body {
padding: 0;
margin: 0;
overflow: hidden;
}
#panel {
width: 100dvw;
height: var(--panel-height);
padding: var(--pad-2);
}
#panel input {
border: none;
background: var(--color-lowered);
transition: background 0.15s;
}
#panel input:focus {
background: var(--color-super-lowered);
}
@media screen and (max-width: 900px) {
#panel input:focus {
position: fixed;
width: calc(100dvw - (62px + var(--pad-2) * 2)) !important;
left: var(--pad-2);
z-index: 2;
}
}
#panel button:not(.inner *),
#panel a.button:not(.inner *),
#panel input {
--h: 28.2px;
height: var(--h);
min-height: var(--h);
max-height: var(--h);
font-size: 16px;
}
#panel button:not(.inner *),
#panel a.button:not(.inner *) {
padding: var(--pad-1) var(--pad-2);
}
iframe {
width: 100dvw;
height: calc(100dvh - var(--panel-height));
}"))
(script
(text "globalThis.SECRET_SESSION = \"{{ session }}\";
function littleweb_navigate(uri) {
if (!uri.includes(\".html\")) {
uri = `${uri}/index.html`;
}
// ...
console.log(\"navigate\", uri);
document.getElementById(\"browser_iframe\").src = `{{ config.lw_host|safe }}/api/v1/net/${uri}?s={{ session }}`;
if (!uri.includes(\"atto://\")) {
document.getElementById(\"uri\").setAttribute(\"true_value\", `atto://${uri}`);
} else {
document.getElementById(\"uri\").setAttribute(\"true_value\", uri);
}
document.getElementById(\"uri\").value = uri.replace(\"atto://\", \"\").split(\"/\")[0];
}
document.getElementById(\"browser_iframe\").addEventListener(\"load\", (e) => {
console.log(\"web content loaded\");
});
window.addEventListener(\"message\", (e) => {
if (typeof e.data !== \"string\") {
console.log(\"refuse message (bad type)\");
return;
}
const data = JSON.parse(e.data);
if (!data.t) {
console.log(\"refuse message (not for tetratto)\");
return;
}
console.log(\"received message\");
if (data.event === \"change_url\") {
const uri = new URL(data.target).pathname.slice(\"/api/v1/net/\".length);
window.history.pushState(null, null, `/net/${uri.replace(\"atto://\", \"\")}`);
if (!uri.includes(\"atto://\")) {
document.getElementById(\"uri\").setAttribute(\"true_value\", `atto://${uri}`);
} else {
document.getElementById(\"uri\").setAttribute(\"true_value\", uri);
}
document.getElementById(\"uri\").value = uri.replace(\"atto://\", \"\").split(\"/\")[0];
}
});
function back() {
post_message({ t: true, event: \"back\" });
}
function forward() {
post_message({ t: true, event: \"forward\" });
}
function reload() {
post_message({ t: true, event: \"reload\" });
}
function post_message(data) {
const origin = new URL(document.getElementById(\"browser_iframe\").src).origin;
document.getElementById(\"browser_iframe\").contentWindow.postMessage(JSON.stringify(data), origin);
}
// handle dropdowns
window.addEventListener(\"blur\", () => {
trigger(\"atto::hooks::dropdown.close\");
});
// url bar focus
document.getElementById(\"uri\").addEventListener(\"input\", (e) => {
e.target.setAttribute(\"true_value\", e.target.value);
});
let is_focused = false;
document.getElementById(\"uri\").addEventListener(\"mouseenter\", (e) => {
e.target.value = e.target.getAttribute(\"true_value\").replace(\"/index.html\", \"\");
});
document.getElementById(\"uri\").addEventListener(\"mouseleave\", (e) => {
if (is_focused) {
return;
}
e.target.value = e.target.getAttribute(\"true_value\").replace(\"atto://\", \"\").split(\"/\")[0];
});
document.getElementById(\"uri\").addEventListener(\"focus\", (e) => {
e.target.value = e.target.getAttribute(\"true_value\").replace(\"/index.html\", \"\");
is_focused = true;
});
document.getElementById(\"uri\").addEventListener(\"blur\", (e) => {
e.target.value = e.target.getAttribute(\"true_value\").replace(\"atto://\", \"\").split(\"/\")[0];
is_focused = false;
});
// navigate
if ({{ path|length }} > 0) {
littleweb_navigate(\"{{ path|safe }}\");
}"))
(text "{% endblock %}")