feat: slideout editor new new new!

This commit is contained in:
Your Name 2026-08-03 01:24:54 -04:00
parent c175a610da
commit 48ac1249c8
10 changed files with 482 additions and 112 deletions

View file

@ -0,0 +1,76 @@
<aside id="bliss-live-editor" data-bliss-ui
class="fixed inset-y-0 left-0 z-[2147483646] w-[min(28rem,90vw)] overflow-auto border-r-4 border-black bg-white text-black shadow-[8px_0_0_#000]"
style="font-family:ui-sans-serif,system-ui">
<header class="sticky top-0 flex items-center justify-between border-b-4 border-double border-black bg-white p-3">
<div><div class="text-lg font-black tracking-tight">Live Page</div><div class="text-xs">Objects currently on this card</div></div>
<button type="button" data-bliss-close class="h-7 w-7 border-2 border-black bg-white text-lg font-black leading-none shadow-[2px_2px_0_#000] active:translate-x-0.5 active:translate-y-0.5 active:shadow-none">×</button>
</header>
<div class="grid min-h-[calc(100vh-61px)] grid-rows-[auto_1fr]">
<nav data-bliss-tree class="border-b-4 border-double border-black p-2"></nav>
<section data-bliss-editor class="min-h-0 p-3 text-sm">Select a route or template to edit it.</section>
</div>
</aside>
<script>
(function () {
const root = document.getElementById("bliss-live-editor");
if (!root) return;
const tree = root.querySelector("[data-bliss-tree]");
const editor = root.querySelector("[data-bliss-editor]");
root.querySelector("[data-bliss-close]").onclick = () =>
document.dispatchEvent(new CustomEvent("bliss:inspector-close"));
function sourceButton(label, kind, id, count) {
const button = document.createElement("button");
button.type = "button";
button.className = "block w-full border border-transparent px-2 py-1 text-left hover:border-black hover:bg-black hover:text-white";
button.textContent = label + (count > 1 ? ` (${count})` : "");
button.onmouseenter = () => document.dispatchEvent(new CustomEvent("bliss:highlight", { detail: { kind, id, on: true } }));
button.onmouseleave = () => document.dispatchEvent(new CustomEvent("bliss:highlight", { detail: { kind, id, on: false } }));
if (kind !== "structure") button.onclick = () => {
editor.textContent = "Loading…";
htmx.ajax("GET", `/_bliss/editor/${kind}/${id}`, { target: editor, swap: "innerHTML" });
};
return button;
}
function render(items) {
tree.innerHTML = "";
const structures = new Map();
for (const item of items) {
if (!structures.has(item.structureId)) structures.set(item.structureId, { name: item.structureName, routes: new Map(), templates: new Map(), count: 0 });
const structure = structures.get(item.structureId);
structure.count++;
if (!structure.routes.has(item.routeId)) structure.routes.set(item.routeId, { name: item.routeName, count: 0 });
structure.routes.get(item.routeId).count++;
if (item.templateId) {
if (!structure.templates.has(item.templateId)) structure.templates.set(item.templateId, { name: item.templateName, count: 0 });
structure.templates.get(item.templateId).count++;
}
}
for (const [structureId, structure] of structures) {
const group = document.createElement("details");
group.open = true;
const heading = document.createElement("summary");
heading.className = "cursor-pointer list-none font-semibold";
heading.append(sourceButton(structure.name, "structure", structureId, structure.count));
group.append(heading);
for (const [label, kind, entries] of [["Routes", "route", structure.routes], ["Templates", "template", structure.templates]]) {
if (!entries.size) continue;
const section = document.createElement("details");
section.open = true;
section.className = "ml-3";
const summary = document.createElement("summary");
summary.className = "cursor-pointer py-1 text-xs font-bold uppercase tracking-widest";
summary.textContent = label;
section.append(summary);
for (const [id, entry] of entries) section.append(sourceButton(entry.name, kind, id, entry.count));
group.append(section);
}
tree.append(group);
}
}
document.addEventListener("bliss:inventory", (event) => render(event.detail));
document.dispatchEvent(new CustomEvent("bliss:inventory-request"));
})();
</script>